文件I/O (二)

   2017-01-06 0
核心提示:前言当前的计算系统除了包括对数据有加工和处理以外还有搬运这个搬运代表着输入和输出,及input/output,简称I/OUNIX/Linux的缔造者们将数据的来源和目标都抽象为文件,所以在UNIX/Linux系统中一切皆文件一切皆文件不仅仅对磁盘,还包括鼠标,键盘,显示器这

前言

当前的计算系统除了包括对数据有 加工和处理 以外还有 搬运

这个 搬运 代表着 输入和输出 ,及 input/output ,简称 I/O

UNIX/Linux的缔造者们将数据的 来源和目标 都抽象为 文件 ,所以在 UNIX/Linux 系统中 一切皆文件

一切皆文件不仅仅对磁盘,还包括鼠标,键盘,显示器这些设备,那么对这些设备的操作也都抽象成了对 文件的I/O操作

关于 标准I/O 可以参看之前的文章 《标准I/O (一)》 ,类Unix系统中除了 标准I/O 还有 文件I/O ,可以完成相同工作,关于 文件I/O 还有它们之间的区别可以参看之前的文章 《文件I/O (一)》 ,关于C语言的API(linux)可以参看 Linux C API 参考手册 在线文档

这里分享一下我在学习 文件 I/O 库过程中的笔记和心得

概要

文件IO库的常用函数

下面是一些 文件IO库 中的常用函数

int open( const char *pathname, int flags)
int open( const char *pathname, int flags, mode_t mode)
ssize_t read(int fd, void *buf, size_t count)
ssize_t write(int fd, const void *buf, size_t count)
off_t lseek(int fildes, off_t offset, int whence)
int close(int fd)

代码示例

要求

结构体定义

struct stu
{
  int id;
  char name[5];
  int score;
};
  • 1)手工输入5个学生信息,并将结果存入文件f1中
  • 2)找出f1中学生分数最高的那个人(有可能多个并列第一),将这个人的信息写入文件f2.

要求:用非缓冲IO实现

代码示例

#include <stdio.h> 
#include <unistd.h> //文件IO函数包含其中,缺少这个头文件read,write,close 会报错
#include <fcntl.h> //open函数包含其中,还有一些重要的宏定义

typedef struct student  //student 结构体定义
{
  int id;
  char name[5];
  int score;
}ST;

int main()
{
  int res=-1,fa=0,fb=0,maxscore=0,tscore=0,i=0;
  char *fileA="f1";
  char *fileB="f2"; //变量定义与初始化
  ST stutmp,stu[5]={
    {11,"xiao",18},
    {13,"hong",88},
    {16,"tuna",98},
    {19,"tony",60},
    {90,"duno",98}
  }; 
  
  if (-1==(fa=open(fileA,O_RDWR|O_CREAT|O_TRUNC,0644))) //以读写方式打开文件A
  {
    printf("cannot open file:%s\n",fileA);
    return res;
  }
  if (-1==(fb=open(fileB,O_RDWR|O_CREAT|O_TRUNC,0644))) //以读写方式打开文件B
  {
    printf("cannot open file:%s\n",fileB);
    return res;
  }
  if(sizeof(ST)*5 != write(fa,stu,sizeof(ST)*5)) //将结构体数组所有内容写到文件A ,顺利的情况下会返回实际写入的字节数,利用这个特性来判断有没写成功
  {
    printf("write error on:%s\n",fileA);
    return res;
  }

  lseek(fa,sizeof(ST)-sizeof(int),SEEK_SET); //将文件指针定位到分数的部分,SEEK_SET 代表的是0,这个宏是在fcntl.h中定义的,意思是偏移量相对位置为文件的开头
  if(sizeof(int)!=read(fa,&maxscore,sizeof(int))) //将分数读到maxscore中,作为初始的最大分数
  {
    printf("read error on:%s\n",fileA);
    return res;
  }
  lseek(fa,sizeof(ST)-sizeof(int),SEEK_SET); //重新将文件指针定位到分数的部分
  for(i=0;i<5;i++)
  {
    if(sizeof(int) != read(fa,&tscore,sizeof(int))) //将分数写到tscore中
    {
      printf("read error on:%s\n",fileA);
      return res;
    }
    if(maxscore < tscore) maxscore=tscore; //将分数与初始的maxscore进行比较,如果当前分数较大,则替换掉maxscore中的值
    lseek(fa,sizeof(ST)-sizeof(int),SEEK_CUR); //从当前位置开始,定位到下一个分数处,SEEK_CUR代表的是1,这个宏是在fcntl.h中定义的,意思是偏移量相对位置为当前位置
  }

  lseek(fa,sizeof(ST)-sizeof(int),SEEK_SET); //重新将文件指针定位到第一个分数的位置
  for(tscore=0,i=0;i<5;i++)
  {
    if(sizeof(int) != read(fa,&tscore,sizeof(int))) //将分数写到tscore中
    {
      printf("read error on:%s\n",fileA);
      return res;
    }
    if(maxscore == tscore) //如果tscore与maxscore相等,就读取这个结构体的内容,并将这个结构体的内容写到文件B中
    {
      lseek(fa,-1*sizeof(ST),SEEK_CUR); //从当前位置后退一个结构体的长度,注意 -1*与SEEK_CUR的用法
      if (sizeof(ST) != read(fa,&stutmp,sizeof(ST))) //读取这个结构体的内容到stutmp中
      {
	printf("read error on:%s\n",fileA); 
	return res;
      }
      if(-1 == write(fb,&stutmp,sizeof(ST))) //将stutmp中的内容写到文件B中
      {
	printf("write error on:%s\n",fileB); 
	return res;
      }
    }  
    lseek(fa,sizeof(ST)-sizeof(int),SEEK_CUR); //定位到下一个分数的部分
  }

  close(fa);
  close(fb);
  res=0;
  return res;
}

Note:文件打开数是一种系统资源,是有上限的,虽然程序退出后,系统会帮忙清理,但在程序设计中,打开文件,使用完后进行手动关闭是一种很好的习惯,这样可以有效避免缓存未刷新的潜在隐患,也可以更加节约资源

编译执行

emacs@ubuntu:~/c$ ll f1
ls: 无法访问f1: 没有那个文件或目录
emacs@ubuntu:~/c$ ll f2
ls: 无法访问f2: 没有那个文件或目录
emacs@ubuntu:~/c$ alias gtc
alias gtc='gcc -Wall -g -o'
emacs@ubuntu:~/c$ gtc savetofile.x savetofile.c
emacs@ubuntu:~/c$ ./savetofile.x 
emacs@ubuntu:~/c$ ll f1
-rw-r--r-- 1 emacs emacs 80 2016-12-30 04:08 f1
emacs@ubuntu:~/c$ ll f2
-rw-r--r-- 1 emacs emacs 32 2016-12-30 04:08 f2
emacs@ubuntu:~/c$ xxd f1
0000000: 0b00 0000 7869 616f 0000 0000 1200 0000  ....xiao........
0000010: 0d00 0000 686f 6e67 0000 0000 5800 0000  ....hong....X...
0000020: 1000 0000 7475 6e61 0000 0000 6200 0000  ....tuna....b...
0000030: 1300 0000 746f 6e79 0000 0000 3c00 0000  ....tony....<...
0000040: 5a00 0000 6475 6e6f 0000 0000 6200 0000  Z...duno....b...
emacs@ubuntu:~/c$ xxd f2
0000000: 1000 0000 7475 6e61 0000 0000 6200 0000  ....tuna....b...
0000010: 5a00 0000 6475 6e6f 0000 0000 6200 0000  Z...duno....b...
emacs@ubuntu:~/c$

编译执行过程中没有报错,从结果来看,f1、f2文件中的内容变化也符合预期

小技巧

宏定义

在写代码的过程偶尔会用到一些宏,这些宏多定义在头文件中,通过查看头文件,就可以获取相关信息

如我们想知道 O_RDWR 的定义

emacs@ubuntu:~$ grep O_RDWR  /usr/include/* -r
/usr/include/asm-generic/fcntl.h:#define O_RDWR		00000002
/usr/include/bits/fcntl.h:#define O_RDWR		     02
/usr/include/linux/fs.h: * to O_WRONLY and O_RDWR via the strange trick in __dentry_open()
/usr/include/linux/smbno.h:#define SMB_O_RDWR	0x0002
emacs@ubuntu:~$

我们想知道 SEEK_SET、SEEK_CUR、SEEK_END 的宏定义

emacs@ubuntu:~$ grep SEEK_SET  /usr/include/* 
/usr/include/fcntl.h:# define SEEK_SET	0	/* Seek from beginning of file.  */
/usr/include/libio.h:   beginning of the file (if W is SEEK_SET),
/usr/include/stdio.h:#define SEEK_SET	0	/* Seek from beginning of file.  */
/usr/include/unistd.h:# define SEEK_SET	0	/* Seek from beginning of file.  */
/usr/include/unistd.h:# define L_SET		SEEK_SET
/usr/include/unistd.h:   beginning of the file (if WHENCE is SEEK_SET),
emacs@ubuntu:~$ grep SEEK_CUR  /usr/include/* 
/usr/include/fcntl.h:# define SEEK_CUR	1	/* Seek from current position.  */
/usr/include/libio.h:   the current position (if W is SEEK_CUR),
/usr/include/stdio.h:#define SEEK_CUR	1	/* Seek from current position.  */
/usr/include/unistd.h:# define SEEK_CUR	1	/* Seek from current position.  */
/usr/include/unistd.h:# define L_INCR		SEEK_CUR
/usr/include/unistd.h:   the current position (if WHENCE is SEEK_CUR),
emacs@ubuntu:~$ grep SEEK_END  /usr/include/* 
/usr/include/fcntl.h:# define SEEK_END	2	/* Seek from end of file.  */
/usr/include/libio.h:   or the end of the file (if W is SEEK_END).
/usr/include/stdio.h:#define SEEK_END	2	/* Seek from end of file.  */
/usr/include/unistd.h:# define SEEK_END	2	/* Seek from end of file.  */
/usr/include/unistd.h:# define L_XTND		SEEK_END
/usr/include/unistd.h:   or the end of the file (if WHENCE is SEEK_END).
emacs@ubuntu:~$

我们还可以使用这种方式来查看函数原型

如我们想知道 lseek 函数的原型

emacs@ubuntu:~$ grep lseek  /usr/include/* 
/usr/include/_G_config.h:#define _G_LSEEK64	__lseek64
/usr/include/unistd.h:/* Values for the WHENCE argument to lseek.  */
/usr/include/unistd.h:extern __off_t lseek (int __fd, __off_t __offset, int __whence) __THROW;
/usr/include/unistd.h:extern __off64_t __REDIRECT_NTH (lseek,
/usr/include/unistd.h:				 lseek64);
/usr/include/unistd.h:#  define lseek lseek64
/usr/include/unistd.h:extern __off64_t lseek64 (int __fd, __off64_t __offset, int __whence)
emacs@ubuntu:~$

Tip:如果我们事先知道一个函数来自于哪一个头文件,就可以进一步地缩小范围,有时一个函数的头文件里并没有直接包含,可能是这个头文件所include的文件中包含,多时可能达到4到5层

内存对齐

在定义有结构体的代码中,要留意内存对齐的问题

哪什么是内存对齐呢,我们可以看看下面的一个例子:

#include <stdio.h>

int main()
{
  struct st1
  {
    char a;
    int b;
    char c;
  };

  struct st2
  {
    char a;
    char c;
    int b;
  };
  
  printf("size of st1:%d\nsize of st2:%d\n",sizeof(struct st1),sizeof(struct st2));
  return 0;
}

我们将它编译运行

emacs@ubuntu:~/c$ alias gtc
alias gtc='gcc -Wall -g -o'
emacs@ubuntu:~/c$ gtc duiqi.x duiqi.c 
emacs@ubuntu:~/c$ ./duiqi.x 
size of st1:12
size of st2:8
emacs@ubuntu:~/c$

从结果来看,包含同样内容的两个结构体,占用的内存却是不一样的,而区别只在于它们内部元素的排列方式不一样

1-4-1的顺序占用了12个字节, 1-1-4 的顺序占用了8个字节

这就是内存对齐的效果

在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”.比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除

字节对齐的作用不仅是便于cpu快速访问,同时合理的利用字节对齐可以有效地节省存储空间。

对于32位机来说,4字节对齐能够使cpu访问速度提高,比如说一个long类型的变量,如果跨越了4字节边界存储,那么cpu要读取两次,这样效率就低了。但是在32位机中使用1字节或者2字节对齐,反而会使变量访问速度降低。所以这要考虑处理器类型,另外还得考虑编译器的类型。在vc中默认是4字节对齐的,GNU gcc 也是默认4字节对齐

xxd

xxd是一个很好用的命令,可以用来查看二进制文件

emacs@ubuntu:~/c$ xxd f1
0000000: 0b00 0000 7869 616f 0000 0000 1200 0000  ....xiao........
0000010: 0d00 0000 686f 6e67 0000 0000 5800 0000  ....hong....X...
0000020: 1000 0000 7475 6e61 0000 0000 6200 0000  ....tuna....b...
0000030: 1300 0000 746f 6e79 0000 0000 3c00 0000  ....tony....<...
0000040: 5a00 0000 6475 6e6f 0000 0000 6200 0000  Z...duno....b...
emacs@ubuntu:~/c$ xxd f2
0000000: 1000 0000 7475 6e61 0000 0000 6200 0000  ....tuna....b...
0000010: 5a00 0000 6475 6e6f 0000 0000 6200 0000  Z...duno....b...
emacs@ubuntu:~/c$

结合前面的代码,从这个二进制编码里,我们可以看出很多有价值的信息

  • 1.这是一个小端序的系统(数据的低字节保存在内存的低地址中)
  • 2.每一个结构体占用了16字节
  • 3. 0-3 对应 int 的存储位置, 4-8 对应 char[5] 的存储位置, 12-15 对应 int 的存储位置
  • 4. 9-11 被空置了

这个命令将 ASCII 可显示的部分进行了显示,无法显示的都转化成了点

ASCII 码

在Linux中使用man命令可以看到一份完整的ASCII码表

emacs@ubuntu:~/c$ man ascii

从中截取以下内容

Oct   Dec   Hex   Char                        Oct   Dec   Hex   Char
       ────────────────────────────────────────────────────────────────────────
       000   0     00    NUL '\0'                    100   64    40    @
       001   1     01    SOH (start of heading)      101   65    41    A
       002   2     02    STX (start of text)         102   66    42    B
       003   3     03    ETX (end of text)           103   67    43    C
       004   4     04    EOT (end of transmission)   104   68    44    D
       005   5     05    ENQ (enquiry)               105   69    45    E
       006   6     06    ACK (acknowledge)           106   70    46    F
       007   7     07    BEL '\a' (bell)             107   71    47    G
       010   8     08    BS  '\b' (backspace)        110   72    48    H
       011   9     09    HT  '\t' (horizontal tab)   111   73    49    I
       012   10    0A    LF  '\n' (new line)         112   74    4A    J
       013   11    0B    VT  '\v' (vertical tab)     113   75    4B    K
       014   12    0C    FF  '\f' (form feed)        114   76    4C    L
       015   13    0D    CR  '\r' (carriage ret)     115   77    4D    M
       016   14    0E    SO  (shift out)             116   78    4E    N
       017   15    0F    SI  (shift in)              117   79    4F    O
       020   16    10    DLE (data link escape)      120   80    50    P
       021   17    11    DC1 (device control 1)      121   81    51    Q
       022   18    12    DC2 (device control 2)      122   82    52    R
       023   19    13    DC3 (device control 3)      123   83    53    S
       024   20    14    DC4 (device control 4)      124   84    54    T
       025   21    15    NAK (negative ack.)         125   85    55    U
       026   22    16    SYN (synchronous idle)      126   86    56    V
       027   23    17    ETB (end of trans. blk)     127   87    57    W
       030   24    18    CAN (cancel)                130   88    58    X
       031   25    19    EM  (end of medium)         131   89    59    Y
       032   26    1A    SUB (substitute)            132   90    5A    Z
       033   27    1B    ESC (escape)                133   91    5B    [
       034   28    1C    FS  (file separator)        134   92    5C    \  '\\'
       035   29    1D    GS  (group separator)       135   93    5D    ]
       036   30    1E    RS  (record separator)      136   94    5E    ^
       037   31    1F    US  (unit separator)        137   95    5F    _
       040   32    20    SPACE                       140   96    60    `
       041   33    21    !                           141   97    61    a
       042   34    22    "                           142   98    62    b
       043   35    23    #                           143   99    63    c
       044   36    24    $                           144   100   64    d
       045   37    25    %                           145   101   65    e
       046   38    26    &                           146   102   66    f
       047   39    27    ´                           147   103   67    g
       050   40    28    (                           150   104   68    h
       051   41    29    )                           151   105   69    i
       052   42    2A    *                           152   106   6A    j
       053   43    2B    +                           153   107   6B    k
       054   44    2C    ,                           154   108   6C    l
       055   45    2D    -                           155   109   6D    m
       056   46    2E    .                           156   110   6E    n
       057   47    2F    /                           157   111   6F    o
       060   48    30    0                           160   112   70    p
       061   49    31    1                           161   113   71    q
       062   50    32    2                           162   114   72    r
       063   51    33    3                           163   115   73    s
       064   52    34    4                           164   116   74    t
       065   53    35    5                           165   117   75    u
       066   54    36    6                           166   118   76    v
       067   55    37    7                           167   119   77    w
       070   56    38    8                           170   120   78    x
       071   57    39    9                           171   121   79    y
       072   58    3A    :                           172   122   7A    z
       073   59    3B    ;                           173   123   7B    {
       074   60    3C    <                           174   124   7C    |
       075   61    3D    =                           175   125   7D    }
       076   62    3E    >                           176   126   7E    ~
       077   63    3F    ?                           177   127   7F    DEL

其中的可见字符对应表

Tables
       For convenience, let us give more compact  tables  in  hex
       and decimal.

          2 3 4 5 6 7       30 40 50 60 70 80 90 100 110 120
        -------------      ---------------------------------
       0:   0 @ P ` p     0:    (  2  <  F  P  Z  d   n   x
       1: ! 1 A Q a q     1:    )  3  =  G  Q  [  e   o   y
       2: " 2 B R b r     2:    *  4  >  H  R  \  f   p   z
       3: # 3 C S c s     3: !  +  5  ?  I  S  ]  g   q   {
       4: $ 4 D T d t     4: "  ,  6  @  J  T  ^  h   r   |
       5: % 5 E U e u     5: #  -  7  A  K  U  _  i   s   }
       6: & 6 F V f v     6: $  .  8  B  L  V  `  j   t   ~
       7: ´ 7 G W g w     7: %  /  9  C  M  W  a  k   u  DEL
       8: ( 8 H X h x     8: &  0  :  D  N  X  b  l   v
       9: ) 9 I Y i y     9: ´  1  ;  E  O  Y  c  m   w
       A: * : J Z j z
       B: + ; K [ k {
       C: , < L \ l |
       D: - = M ] m }
       E: . > N ^ n ~
       F: / ? O _ o DEL

总结

以下这些函数可以应对绝大部分的IO需求

  • open
  • close
  • read
  • write
  • lseek

通过各方面资料弄懂其参数的意义和返回值的类型,是熟练掌握的基础

 
标签: Linux命令
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • 每天一条linux命令——halt
    halt命令用来关闭正在运行的Linux操作系统。halt命令会先检测系统的runlevel,若runlevel为0或6,则关闭系统,否则即调用shutdown来关闭系统。 语法:halt(选项)选项:-d:不要在wtmp中记录;-f:不论目前的runlevel为何,不调用shutdown即强制关闭系统;-i:
    02-10
  • 几个linux命令查看堆栈内存问题 几个linux命令
      公司编写代码,发现整个在程序里面调用system时候总是失败,原因是can't allocate memory ,根本原因不是内存不足,而是堆栈内存不够。使用ulimit -s 2048后有所好转但是发现一旦系统进程开启多了后仍然会挂。后来使用objdump -x 后发现了原因有个动态库
    02-09
  • Linux命令总结--rm命令 linux的rmp命令
    (来源于http://www.cnblogs.com/xqzt/p/5398919.html)1、命令简介rm(Remove file 删除目录或文件)删除文件,对于链接文件,只是删除整个链接文件,而原有文件保持不变。新手在删除文件之前一定要知道这些:很重要1.可以用mv代替rm的绝对不要用rm,不要着急
    02-09
  • Linux命令行批量创建目录详解 linux创建目录
    以前一直用-p创建目录链,觉得很方便了。在空目录/opt/app/myapp里创建src,再创建main,再创建javamkdir -p /opt/app/myapp/src/main/java没想到还可以这样玩##¥%……*(root@vm1:~/tmp# mkdir -p src/{{main,test}/{java,resources},main/webapp}root@vm1:
    02-09
  • Linux命令之乐--script和scriptplay
    script和scriptplay可以把终端会话记录到一个文件中,可以用来制作命令行教学视屏。 开始录制会话[root@new test]# script -t 2timing.log -a output.sessionScript started, file is output.session[root@new test]# echo hellohello[root@new test]# echo
    02-09
  • java 开发常用的Linux命令
    1.查找文件 find / -name filename.txt 根据名称查找/目录下的filename.txt文件。find . -name "*.xml" 递归查找所有的xml文件find . -name "*.xml" |xargs grep "hello world" 递归查找所有文件内容中包含hello world的xml文件grep -H 'spring' *.xml 查找所
    02-09
  • 常用Linux命令整理 Linux常用命令总结
    常用Linux命令整理常见系统命令export 查看或修改环境变量# 例:临时修改命令提示符为字符串$export PS1=$# 例:临时修改命令提示符显示系统时间 时间使用\t 表示export PS1="[\u@\h \t \W]\$"man 查看linux系统的手册# 例:查看ls命令如何使用man ls # 回车
    02-09 Linux
  • linux命令行—《命令行快速入门》 linux常用命令csdn
    linux命令行—《命令行快速入门》 linux常用命
    pwd print working directory 打印工作目录hostname my computer's network name 电脑在网络中的名称mkdir make directory 创建路径cd change directory 改变路径ls list directory 列出路径下的内容rmdir remove directory 删除路径pushd push directory 推
    02-09
  • Linux命令(五) 五个常用的linux命令
    Linux命令(五) 五个常用的linux命令
    jenkins触发钉钉报警机制1、安装钉钉插件钉钉报警需要安装Ding Talk插件,支持钉钉报警2、打开钉钉创建钉钉机器人2.1选择机器人类型——自定义2.2添加机器人2.3填写机器人信息填写机器人姓名—设置安全设置为加密类型,并复制出来加密值2.4创建完成保持webhoo
    02-09
  • 03_Linux基础-文件类型-主辅提示符-第1提示符-Linux命令-内外部命令-快捷键-改为英文编码-3个时间-stat-其他基础命令
    03_Linux基础-文件类型-主辅提示符-第1提示符-L
    03_Linux基础-文件类型-主辅提示符-第1提示符-Linux命令-内外部命令-快捷键-改为英文编码-3个时间-stat-{1..100}-du-cd-cp-file-mv-echo-id-shell-ln-env-set-which-rpm-/usr-/与/root-hostname-vim-cat-pwd-alias-unalias-ls-ASCII-wc-chmod博客
    02-09
点击排行