UD4L3——文件流相关

本章代码关键字

1
2
3
4
5
6
7
8
9
10
11
FileStream              //文件流类,用于读写文件的细节
new FileStream() //文件流的构造函数,需要传入要读取文件来构造文件流
fileStream.Length //文件流的字节长度
fileStream.CanWrite //文件流是否可写
fileStream.CanRead //文件流是否可读
fileStream.Flush() //将文件流缓存内的字节全部写入到文件
fileStream.Close() //关闭字节流
fileStream.Dispose() //将字节流的缓存资源销毁回收
fileStream.Write() //文件流写入字节
fileStream.Read() //文件流读取字节
using () { } //在using括号内声明的变量将在代码块执行完毕后,会自动执行Dispose方法,并被销毁,用于安全的文件操作

文件流

在C#中提供了一个文件流类FileStream​​类
它主要作用是用于读写文件的细节
我们之前学过的File​只能整体读写文件,而FileStream​​可以以读写字节的形式处理文件

说人话:
文件里面存储的数据就像是一条数据流(数组或者列表)
我们可以通过FileStream​​一部分一部分的读写数据流
比如我可以先存一个int​​(4个字节)再存一个bool​​(1个字节)再存一个string​​(n个字节)
利用FileStream​​可以以流式逐个读写

通过FileStream读写时一定要注意,读的规则一定是要和写是一致的
我们存储数据的先后顺序是我们制定的规则,只要按照规则读写就能保证数据的正确性

FileStream文件流类常用方法

类名:FileStream
命名空间:System.IO

  1. 打开或创建指定文件

    • new FileStream()​​​

      使用构造函数来构造一个文件流

      • 参数一:路径

      • 参数二:打开模式

        • CreateNew​​​:创建新文件 如果文件存在 则报错
        • Create​​​:创建文件,如果文件存在 则覆盖
        • Open​​​:打开文件,如果文件不存在 报错
        • OpenOrCreate​​​:打开或者创建文件根据实际情况操作
        • Append​​​:若存在文件,则打开并查找文件尾,或者创建一个新文件
        • Truncate​​​:打开并清空文件内容
      • 参数三:访问模式

        • Read​​​:只读
        • Write​​​:只写
        • ReadWrite​​​:读写
      • 参数四:共享权限(一般可以不填)

        • None​​​:谢绝共享
        • Read​​​:允许别的程序读取当前文件
        • Write​​​:允许别的程序写入该文件
        • ReadWrite​​​:允许别的程序读写该文件
      1
      FileStream fileStream = new FileStream(Application.dataPath + "/Lesson3.uni", FileMode.Create, FileAccess.ReadWrite);
    • File.Create

      • 参数一:路径

      • 参数二:缓存大小(可不写)

      • 参数三:描述如何创建或覆盖该文件(不常用)

        • Asynchronous​​​ 可用于异步读写
        • DeleteOnClose​​​ 不在使用时,自动删除
        • Encrypted​​​ 加密
        • None​​​ 不应用其它选项
        • RandomAccess​​​ 随机访问文件
        • SequentialScan​​​ 从头到尾顺序访问文件
        • WriteThrough​​​ 通过中间缓存直接写入磁盘
      1
      FileStream fileStream = File.Create(Application.dataPath + "/Lesson3.uni");
    • File.Open​​​

      • 参数一:路径

      • 参数二:打开模式

        • CreateNew​​:创建新文件 如果文件存在 则报错
        • Create​​:创建文件,如果文件存在 则覆盖
        • Open​​:打开文件,如果文件不存在 报错
        • OpenOrCreate​​:打开或者创建文件根据实际情况操作
        • Append​​:若存在文件,则打开并查找文件尾,或者创建一个新文件
        • Truncate​​:打开并清空文件内容
      1
      FileStream fileStream = File.Open(Application.dataPath + "/Lesson3.uni", FileMode.Open);
  2. 重要属性和方法

    • 文本字节长度

      即该文件流所有内容的长度,注意,返回的是****​long​​类型的整数! ,这意味着一个文件流的内的字节长度可以非常大

      1
      2
      FileStream fileStream = File.Open(Application.dataPath + "/Lesson3.uni", FileMode.OpenOrCreate);
      print(fileStream.Length);
    • 是否可写

      该文件流是否可以写入内容

      1
      2
      3
      4
      5
      FileStream fileStream = File.Open(Application.dataPath + "/Lesson3.uni", FileMode.OpenOrCreate);
      if (fileStream.CanWrite)
      {
      print("可写");
      }
    • 是否可读

      该文件流是否可以读取内容

      1
      2
      3
      4
      5
      FileStream fileStream = File.Open(Application.dataPath + "/Lesson3.uni", FileMode.OpenOrCreate);
      if (fileStream.CanRead)
      {
      print("可读");
      }
    • 将缓存内字节写入文件

      将缓存内的字节数据全部写入文件,当在写入以后一定要执行一次!

      1
      2
      FileStream fileStream = File.Open(Application.dataPath + "/Lesson3.uni", FileMode.OpenOrCreate);
      fileStream.Flush();
    • 关闭流

      将文件流关闭,当文件读写完毕后一定要执行

      1
      2
      FileStream fileStream = File.Open(Application.dataPath + "/Lesson3.uni", FileMode.OpenOrCreate);
      fileStream.Close();
    • 缓存资源销毁回收

      1
      2
      FileStream fileStream = File.Open(Application.dataPath + "/Lesson3.uni", FileMode.OpenOrCreate);
      fileStream.Dispose();
  3. 写入字节

    • 参数一:写入的字节数组
    • 参数二:数组中的开始索引(数组从哪个索引的元素开始写入到文件内)
    • 参数三:写入多少个字节
    1
    2
    3
    4
    5
    6
    7
    8
    9
    FileStream fileStream = new FileStream(Application.persistentDataPath + "/Lesson3.uni",
    FileMode.OpenOrCreate,
    FileAccess.Write);
    byte[] bytes = BitConverter.GetBytes(999);
    fileStream.Write(bytes, 0, bytes.Length);
    //不要忘记将字节写入文件,并关闭流,避免数据丢失
    fileStream.Flush();
    fileStream.Close();
    fileStream.Dispose();

    写入字符串时,最好先写入长度,再写入字符串具体内容
    因为字符串的编码格式,长度都是不确定的,记录了长度以后就会更方便读取字符串的内容

    至于下面例子里的using​​是用来做什么的,可看 ——> ​using​​

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    using (FileStream fileStream = new FileStream(Application.persistentDataPath + "/Lesson3.uni",
    FileMode.OpenOrCreate,
    FileAccess.Write))
    {
    byte[] bytes = BitConverter.GetBytes(999);
    fileStream.Write(bytes, 0, bytes.Length);
    //写入字符串时
    byte[] strBytes = Encoding.UTF8.GetBytes("字符串写入测试");
    //先写入长度
    fileStream.Write(BitConverter.GetBytes(strBytes.Length), 0, 4);
    //再写入字符串具体内容
    fileStream.Write(strBytes, 0, strBytes.Length);
    //不要忘记将字节写入文件,并关闭流,避免数据丢失
    fileStream.Flush();
    fileStream.Close();
    fileStream.Dispose();
    }
  4. 读取字节

    • 参数一:用于存储读取的字节数组的容器
    • 参数二:容器中开始的位置(读取到的字节从数组的第几个元素开始装入)
    • 参数三:读取多少个字节装入容器
    • 返回值:当前流索引前进了几个位置(打个比方,这个索引你可以类比为文字编辑器里的光标)
    1. 挨个读取字节数组

      至于下面代码里的using​​​​是用来做什么的,可看 ——> using​​​​

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      using (FileStream fileStream = File.Open(Application.persistentDataPath + "/Lesson3.uni",
      FileMode.Open,
      FileAccess.Read))
      {
      //读取第一个整数
      byte[] bytes = new byte[4];
      int index = fileStream.Read(bytes, 0, 4);
      int i = BitConverter.ToInt32(bytes, 0);
      print("取出来的第一个整数:" + i);
      print("索引向前移动" + index + "个位置");

      //读取字符串的长度
      index = fileStream.Read(bytes, 0, 4);
      print("索引向前移动" + index + "个位置");
      //得到字符串的字节数组的长度
      int length = BitConverter.ToInt32(bytes, 0);
      byte[] strBytes = new byte[length];
      //读取字符串
      index = fileStream.Read(strBytes, 0, length);
      print("读出来的字符串:" + Encoding.UTF8.GetString(strBytes));
      print("索引向前移动" + index + "个位置");
      //不要忘记关闭流
      fileStream.Close();
      fileStream.Dispose();
      }

      运行结果:

      image

    2. 一次性读取再挨个读取

      就是将文件流的内容一次性读取到字节数组内,再挨个读取字节数组内的字节

      注意,文件流的长度length​​​,采用的是long​​​整数类型来计数(最大值900万兆),这意味着一个文件流可能非常大
      文件流的Read() ​​​方法,一次性最多能读取的字节数是int​​​类型整数的最大值(最大值21亿)
      这意味着,当这个文件流长度非常长,以至于超出了****​int​​​类型整数的最大值时,一次****​Read() ​​​方法将无法读完所有内容****因此这个方法不适合超大型文件流的读取

      至于下面代码里的using​​​是用来做什么的,可看 ——> using​​​

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      using (FileStream fileStream = File.Open(Application.persistentDataPath + "/Lesson3.uni",
      FileMode.Open,
      FileAccess.Read))
      {
      //以文件流的字节长度声明字节数组
      byte[] fileBytes = new byte[fileStream.Length];
      //一次读取文件流所有的字节数据,装载到字节数组内,需要注意的是,这个方法一次只能读取int最大值的字节数
      fileStream.Read(fileBytes, 0, (int)fileStream.Length);
      fileStream.Dispose();
      //读取整数
      int i = BitConverter.ToInt32(fileBytes, 0);
      print("取出来的第一个整数:" + i);
      //读取字符串长度
      int length = BitConverter.ToInt32(fileBytes, 4);
      //读取字符串
      print("读出来的字符串:" + Encoding.UTF8.GetString(fileBytes, 8, length));
      }

      运行结果

      image

更加安全的使用文件流对象

using​关键字重要用法,关于它的其他详细讲解可看 ——>using​ using​

1
2
3
4
5
using (申明一个引用对象)
{
使用对象
}
//在这里using()内的使用对象会被全部销毁

无论发生什么情况 当using​语句块结束后,会自动调用该对象的销毁方法 避免忘记销毁或关闭流
using​是一种更安全的使用方法
强调:目前我们对文件流进行操作 为了文件操作安全 都用using来进行处理最好

通过下面的代码,可以发现,不同using​语句块内的同名临时变量是不一样的,生命周期也不同
不过要注意,如果using​语句块外部已经声明了某个临时变量,using​内部就不能再次声明和该变量名同名的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using (FileStream fileStream = File.Open(Application.persistentDataPath + "/Lesson3.uni",
FileMode.Open,
FileAccess.Read))
{
//读取第一个整数
byte[] bytes = new byte[4];
int index = fileStream.Read(bytes, 0, 4);
int i = BitConverter.ToInt32(bytes, 0);
print("取出来的第一个整数:" + i);
print("索引向前移动" + index + "个位置");

//读取字符串的长度
index = fileStream.Read(bytes, 0, 4);
print("索引向前移动" + index + "个位置");
//得到字符串的字节数组的长度
int length = BitConverter.ToInt32(bytes, 0);
byte[] strBytes = new byte[length];
//读取字符串
index = fileStream.Read(strBytes, 0, length);
print("读出来的字符串:" + Encoding.UTF8.GetString(strBytes));
print("索引向前移动" + index + "个位置");
//不要忘记关闭流
fileStream.Close();
fileStream.Dispose();
}
//int i //如果using语句块外部已经声明了变量i,using内部就不能再次声明变量i
print("-------------------------分割线-------------------------");
using (FileStream fileStream = File.Open(Application.persistentDataPath + "/Lesson3.uni",
FileMode.Open,
FileAccess.Read))
{
//以文件流的字节长度声明字节数组
byte[] fileBytes = new byte[fileStream.Length];
//一次读取文件流所有的字节数据,装载到字节数组内,需要注意的是,这个方法一次只能读取int最大值的字节数
fileStream.Read(fileBytes, 0, (int)fileStream.Length);
fileStream.Dispose();
//读取整数
int i = BitConverter.ToInt32(fileBytes, 0);
print("取出来的第一个整数:" + i);
//读取字符串长度
int length = BitConverter.ToInt32(fileBytes, 4);
//读取字符串
print("读出来的字符串:" + Encoding.UTF8.GetString(fileBytes, 8, length));
}