zoukankan      html  css  js  c++  java
  • C# 文件读写系列二

    读取文件原则上非常简单,但它不是通过FileInfo和DirectoryInfo来完成的,关于FileInfo和DirectoryInfo请参考C# 文件操作系列一,在.Net Framework4.5中可以通过File类来读写文件,在.Net Framework2.0推出之前,读写文件是相当费劲的,但是在.Net Framework2.0推出之后,它对File类进行了扩展,只要编写一行代码,就能对文件进行读写,下面通过一个窗体应用程序来展示文件的读写功能。

    一、文件读取

    1、通过File类的静态方法ReadAllText()进行文件的读取。

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace FileReadWrite
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                ReadFile();
            }
            protected void ReadFile()
            {
                if (string.IsNullOrEmpty(textBox1.Text))
                {
                    MessageBox.Show("请输入文件路径!");
                }
                else
                {
                    string res = File.ReadAllText(textBox1.Text,Encoding.Default);
                    textBox2.Text = res;
                }
            }
        }
    }

    2、通过File类的静态方法ReadAllLines()进行文件的读取,代码如下:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace FileReadWrite
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                ReadFile();
            }
            protected void ReadFile()
            {
                if (string.IsNullOrEmpty(textBox1.Text))
                {
                    MessageBox.Show("请输入文件路径!");
                }
                else
                {
                    string[] res = File.ReadAllLines(textBox1.Text,Encoding.Default);
                    StringBuilder result = new StringBuilder();
                    foreach (string re in res)
                    {
                        result.Append(re);
                    }
                    textBox2.Text = result.ToString();
                }
            }
        }
    }

    3、通过File类的静态方法ReadAllBytes()进行文件的读取,代码如下:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace FileReadWrite
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                ReadFile();
            }
            protected void ReadFile()
            {
                if (string.IsNullOrEmpty(textBox1.Text))
                {
                    MessageBox.Show("请输入文件路径!");
                }
                else
                {
                    byte[] res = File.ReadAllBytes(textBox1.Text);
                    StringBuilder sb = new StringBuilder();
                    foreach (var b in res)
                    {
                        sb.Append(b.ToString());
                    }
                    textBox2.Text = sb.ToString();
                }
            }
        }
    }

    二、文件写入

    File提供了ReadAllBytes()、ReadAllLines()、ReadAllText()等读取文件的方式,所以对应的就会有相对应的写入文件的方式WriteAllBytes()、WriteAllLines()、WriteAllText()。

    这里就不多做阐述.

    三、通过流来操作文件

    1、流的概念相信大家都不陌生,无论是哪种语言、哪种平台都会有流的存在,流是一个用于传输数据的对象,流可以双向传输,分为读取流和写入流。

    a、读取流:数据从外部源传输到程序中

    b、写入流:数据从程序传输到外部源中

    外部源通常是一个文件,但也不都是一个文件,它也可能是:

    a、网络上的数据(可通过一些网络协议进行读写)

    .Net提供了一个System.Net.Sockets.NetworkStream类,可以通过它来处理网络数据。

    b、读写到内存区域上的数据

    .Net提供了一个System.IO.MemoryStream类,可以通过它来读取内存.

    c、读写到命名管道上的数据

    读写管道没有提供基本的流类,但是有一个泛型流类System.IO.Stream,如果要编写这样一个类,就可以从这个基类继承.

    d、另一个计算机上发送的数据

    e、外部源甚至可以代码中的一个变量,使用流在变量之间传输数据是一个常用的技巧,可以在数据类型之间转换数据。

    使用一个独立的对象来传输数据,比使用FileInfo和DirectoryInfo类更好,应为把传输数据(名词)的概念和特定数据源分离开来,可以更容易的交换数据源。流对象本身包含许多通用的代码,可以在外部数据源和代码中的变量之间移动数据,把这些代码与特定数据源的概念区分开来,可以实现不同环境下代码的重用(通过继承).例如像StringReader和StringWriter,StreamReader和StreamWriter一样,都是同一继承树的一部分,这些类的后台一定共享很多的代码.

    2、FileStream类

    (1)、FileStream类的作用

    a、这个类只要用于读取二进制文件中的二进制数据,当然也可以使用它读取任何文件,通常读取二进制文件要使用FileStream

    b、FileStream对象实例表示在磁盘或网络路径上指向文件的流,这个类提供了在文件中读取字节的方法,但是经常使用StreamReader和StreamWriter来执行这些功能,因为FileStream操作的是字节和字节数组,而Stream类操作的是字符数据.

    (2)、FileStream的构造方式

    虽然.Net 提供了15种方式构建一个FileStream对象实例,但是构建一个FileStream对象实例,主要需要以下4条信息(也就是构造函数的参数):

    a、要访问的文件

    b、表示如何打开文件的模式。例如,新建一个文件或者打开一个现有的文件。如果打开一个现有的文件,写入操作是覆盖文件,还是追加到文件的末尾。

    c、表示文件的访问方式------是只读,只写,还是读写?

    d、共享访问------表示访问是否独占文件.如果允许其他流同时访问文件,则这些流是只读还是只写还是读写文件。

    .Net提供的FileStream构造函数主要分为两类

    一类是构造函数的第一个参数是一个文件的完整路径的字符串,其余的参数大致是FileMode、FileAcess、FileShare等...

    一类是构造函数用老式的windows-api分格的windows句柄来处理文件...

    本文主要用的是第一类,第一个参数是文件的完整路径的字符串的这一类的构造函数,构造形式如下:

    接下来主要介绍如上构造函数中的三个参数:FileMode、FileAcess、FileShare

    a、FileAccess枚举

    注意,该枚举有一个Flags特性标签,说明FileAccess的枚举值可以进行按位的"|"或者按位的"&"运算,关于这个不清楚,请参考C# 特性(Attribute)之Flag特性

    b、FileMode枚举

    namespace System.IO
    {
        using System;
        using System.Runtime.InteropServices;
    
        [Serializable, ComVisible(true)]
        public enum FileMode
        {
            /*
             * 创建新文件,此操作需要 System.Security.Permissions.FileIOPermissionAccess.Write权限
             * 如果文件已存在,将引发System.IO.IOException
             */
            CreateNew = 1,
    
            /*
             * 指定操作系统打开现有文件,文件一旦打开,就会截断成零字节大小
             * 此操作需要 System.Security.Permissions.FileIOPermissionAccess.Write
             * 尝试读取通过Truncate打开文件将导致异常.
             */
            Truncate = 5,
    
            /*
             * 指定操作系统创建新文件.如果文件已存在,那么它将被覆盖
             * 此操作需要 System.Security.Permissions.FileIOPermissionAccess.Write
             * FileMode.Create<=>相当于如果文件不存在则使用System.IO.FileMode.CreateNew
             * 如果文件存在,则使用System.IO.FileMode.Truncate.
             * 注:如果文件已存在但是为隐藏文件,则将引发System.UnauthorizedAccessException
             */
            Create = 2,
    
            /*
             * 若文件存在,则找到文件并定位到文件结尾.如果文件不存在,则创建一个新文件
             * FileMode.Append只能与FileAccess.Write一起使用.尝试查找文件尾之前的位置会引发System.IO.IOException
             * 并且任何尝试读取的操作都会失败并引发 System.NotSupportedException
             */
            Append = 6,
    
            /*
             * 指定操作系统打开现有的文件,打开文件的能力取决于System.IO.FileAccess.
             * 如果文件不存在,则引发System.IO.FileNotFoundException.
             */
            Open = 3,
    
            /*
             * 指定操作系统应打开文件.打开文件的能力取决于 System.IO.FileAccess 所指定的值.
             * 如果该文件不存在,则引发System.IO.FileNotFoundException.
             */
            OpenOrCreate = 4,
    
        }
    }

    c、FileShare枚举

    这个枚举主要实现的是"文件读写锁"的功能,在开发过程中,我们往往需要大量读写文件的操作,在本地往往能完美运行(单线程),但是项目上线后,就会出现一系列的问题.(.Net本身是多线程环境),下面简单列举一些在多线程环境中会出现的问题.

    i、写入一些内容到一个文件中,另一个线程/进程要读取文件的内容时报异常,提示System.IO.IOException:文件真由另一进程使用,因此该进程无法访问该文件.

    ii、和上面i的顺序相反,在对一个文件进行读操作时,此时另一个线程/进程向该文件进行追加内容操作,也会报i中的异常.

    iii、对一个文件进行简单的读写操作后,想删除文件,依然报上述的错误.

    出现上述异常有两种可能的原因

    第一种是对文件进行完一个操作后,没有进行资源的释放

    第二种是当进行完一个操作后,可能要到某个时刻才真正结束,亦或是需要指定某个触发机制,操作才会真正结束

    下面开始逐个解析FileShare的每个枚举值

    // Generated by .NET Reflector from C:WindowsMicrosoft.NetassemblyGAC_64mscorlibv4.0_4.0.0.0__b77a5c561934e089mscorlib.dll
    namespace System.IO
    {
        using System;
        using System.Runtime.InteropServices;
        
        [Serializable, Flags, ComVisible(true)]
        public enum FileShare
        {
            /*
             * 谢绝共享当前文件.文件关闭前,打开该文件的任何请求(由此进程或者另一进程)都将抛出异常
             */
            None = 0,
    
            /*
             * 允许随后打开文件读取,如果未指定此标记,则文件关闭前,,打开该文件的任何请求(由此进程或者另一进程)都将抛出异常
             * 但是,即使指定了该标记,仍可能需要附加权限才能够访问该文件.
             */
            Read = 1,
    
            /*
             * 允许随后打开文件写入,如果未指定此标记,则文件关闭前,,打开该文件的任何请求(由此进程或者另一进程)都将抛出异常
             * 但是,即使指定了该标记,仍可能需要附加权限才能够访问该文件.
             */
            Write = 2,
    
            /*
             * 允许随后打开文件写入或读取,如果未指定此标记,则文件关闭前,,打开该文件的任何请求(由此进程或者另一进程)都将抛出异常
             * 但是,即使指定了该标记,仍可能需要附加权限才能够访问该文件.
             */
            ReadWrite = 3,
    
            /*
             * 允许随后删除文件
             */
            Delete = 4,
    
            /*
             * 使文件可由子进程继承,Win32不直接支持此功能.
             */
            Inheritable = 0x10
        }
    }

    在讲解各个枚举值之前,先提供两个方法,用于测试,一个是写文件的方法

    static void WriteFile(FileMode filemode,FileAccess fileacess,FileShare fileshare)
    {
          var content = "测试";
          FileStream fs = new FileStream(FilePath, filemode, fileacess, fileshare);
          var buffer=Encoding.Default.GetBytes(content);
          fs.Write(buffer, 0,buffer.Length);
          fs.Flush();
    }

    一个是读文件的方法

    static void ReadFile(FileAccess fileaccess,FileShare fileshare)
    {
        FileStream fs=default(FileStream);
        try
        {
             fs = new FileStream(FilePath, FileMode.Open, fileaccess, fileshare);
             var buffer = new byte[fs.Length];
             fs.Position = 0;
             fs.Read(buffer, 0, buffer.Length);
             Console.WriteLine(Encoding.Default.GetString(buffer));
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }

    FileShare.Read

    允许随后打开文件读取,所以有如下示例代码:

    WriteFile(FileMode.Create, FileAccess.Write, FileShare.Read);
    ReadFile(FileAccess.Read, FileShare.Read);
    Console.ReadKey();

    按照FileShare.Read的关法用法说明,出现上述问题只有一个原因,就是"允许随后打开文件读取"之前的操作可能不能是WriteFile,亦或只能是ReadFile,修改代码如下:

    ReadFile(FileAccess.Read, FileShare.Read);
    ReadFile(FileAccess.Read, FileShare.Read);
    Console.ReadKey();

    到这一步还不能确认上面的假设,现在修改代码如下:

    ReadFile(FileAccess.Read, FileShare.Read);
    WriteFile(FileMode.Create, FileAccess.Write, FileShare.Read);
    Console.ReadKey();

    ok,通过上面的3个demo说明FileShare.Read(只读共享)只有在连续读取文件才有效.

    FileShare.Write

    允许随后打开文件写入,和FileShare.Read一样,FileShare.Write(只写共享)只有在连续写入文件是才有效,代码如下:

    WriteFile(FileMode.Create, FileAccess.Write, FileShare.Write,"测试");
    WriteFile(FileMode.Append, FileAccess.Write, FileShare.Write,"追加内容");

    注:设置文件的共享方式为Write后,使用windows记事本也无法打开了.

    FileShare.ReadWrite

    综合FileShare.Read和FileShare.Write的特性

    FileShare.None/FileShare.Delete

    有了上面的经验,相信这两个你也很容易的就理解了,None则为不允许后续有任何操作,而Delete则是允许你随后进行删除操作。

    (3)、通过FileInfo构建FileStream

    a、通过FileInfo的实例方法OpenRead构建FileStream,代码如下:

    FileInfo fi = new FileInfo(FilePath);
    FileStream fs = fi.OpenRead();
    byte[] bt = new byte[fs.Length];
    fs.Read(bt, 0, bt.Length);
    Console.WriteLine(Encoding.Default.GetString(bt));

    查看OpenRead()的源代码,代码如下:

    public FileStream OpenRead()
    {
       return new FileStream(base.FullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 0x1000, false);
    }

    现在一目了然,其实FileInfo的实例方法OpenRead其实是构建了一个FileStream的实例.

    b、通过FileInfo的实例方法OpenWrite构建FileStream,代码如下:

    FileInfo fi = new FileInfo(FilePath);
    FileStream fs = fi.OpenWrite();
    byte[] bt = Encoding.Default.GetBytes("测试");
    fs.Write(bt, 0, bt.Length);

    查看OpenWrite方法的源码,如下:

    public FileStream OpenWrite()
    {
        return new FileStream(base.FullPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
    }

    和OpenRead()一样,它的内部原理也是构建了一个FileStream实例.

    c、通过FileInfo的实例方法Open构建FileStream,代码如下:

    FileInfo fi = new FileInfo(FilePath);
    FileStream fs = fi.Open(FileMode.Create, FileAccess.Write, FileShare.None);
    byte[] bt = Encoding.Default.GetBytes("测试");
    fs.Write(bt, 0, bt.Length);

    和OpenRead()、OpenWrite()一样,它的内部原理也是构建了一个FileStream实例.但是Open的参数是可配置的,不像OpenRead()、OpenWrite()

    Open源码如下:

            public FileStream Open(FileMode mode)
            {
                return this.Open(mode, FileAccess.ReadWrite, FileShare.None);
            }
            
            public FileStream Open(FileMode mode, FileAccess access)
            {
                return this.Open(mode, access, FileShare.None);
            }
            
            public FileStream Open(FileMode mode, FileAccess access, FileShare share)
            {
                return new FileStream(base.FullPath, mode, access, share);
            }

    (4)、关闭流

    通过上面的内容了解了构建一个FileStream其实就是构建了一个文件流,通过查阅源码可以发现FileStream继承了Stream,而Stream实现了IDisposable接口,所以当我们使用完FileStream,必须显示的调用Dispose或者使用using语句块,来释放资源.

    FileStream提供了一个Close实例方法,来释放资源,和完成GC的回收,Close源码如下:

    public virtual void Close()
    {
         this.Dispose(true);
         GC.SuppressFinalize(this);
    }

    关闭流会释放与它相关的资源,允许其他应用程序为同一个文件设置流,这个操作也会刷新缓冲区.在打开和关闭流之间,可以读写其中的数据.

    (4)、通过FileStream的实例方法读取流

    a、ReadByte()

    ReadByte()是读取流数据的最简单的方式,他从流中读取一个字节,把结果转换成0~255之间的整数.如果达到该流的末尾,就返回-1,ReadByte()返回的是下一个流中的下一个字节,代码如下:

    int nextByte = fs.ReadByte();

    b、Read()

    如果要一次读取多个字节,就调用Read(),把特定数量的字节读入到一个数组中,Read()返回实际读取的字节数,如果这个数是0,就表示达到了流的末尾,代码如下:

    int nBytes=10;
    int nextByte = fs.Read(bt, 0, nBytes);

    (5)、通过FileStream的实例方法写入流(给文件写入内容流)

    a、WriteByte()

    将一个字节写入流中,代码如下:

    FileStream fs = new FileStream(FilePath, FileMode.Create, FileAccess.Write, FileShare.None);
    byte bt = 100;
    fs.WriteByte(bt);

    b、Write()

    将一个字节数组写入流中,代码如下:

    FileStream fs = new FileStream(FilePath, FileMode.Create, FileAccess.Write, FileShare.None);
    byte[] bytes = Encoding.Default.GetBytes("测试");
    fs.Write(bytes, 0, bytes.Length);

    (6)、二进制文件读取器

    新建一个windows窗体应用程序,主要是将选中的文件,转换成二进制形式.

    代码如下:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace BinaryFileReader
    {
        public partial class Form1 : Form
        {
            private readonly FileDialog choseOpenFileDialog = new OpenFileDialog();
            private string choseFilePath;
            public Form1()
            {
                InitializeComponent();
                menuFileOpen.Click += menuFileOpen_Click;
                choseOpenFileDialog.FileOk += choseOpenFileDialog_FileOk;
            }
    
            private void choseOpenFileDialog_FileOk(object sender, CancelEventArgs e)
            {
                choseFilePath = choseOpenFileDialog.FileName;
                this.Text = Path.GetFileName(choseFilePath);
                textBox1.Text = choseFilePath;
                DisplayFileName();
            }
    
            private void menuFileOpen_Click(object sender, EventArgs e)
            {
                choseOpenFileDialog.ShowDialog();
            }
    
            private void DisplayFileName()
            {
                int sizePerRow = 16;//每行显示的字符数
                FileStream fs = new FileStream(choseFilePath, FileMode.Open, FileAccess.Read);
                long nBytesToRead = fs.Length;
                if (nBytesToRead > 65536 / 4)
                    nBytesToRead = 65536 / 4;//需要显示的字符数
                int nLines = (int)(nBytesToRead / sizePerRow) + 1;//总行数
                string[] lines = new string[nLines];
                int nBytesRead = 0;
                for (int i = 0; i < nLines; i++)
                {
                    StringBuilder nextLine = new StringBuilder();
                    nextLine.Capacity = 4 * sizePerRow;
                    for (int j = 0; j < sizePerRow; j++)
                    {
                        int nextByte = fs.ReadByte();
                        nBytesRead++;
                        if (nextByte == 0 || nBytesRead > 65535)
                            break;
                        char nextChar = (char)nextByte;
                        if (nextChar < 16)//char值小于16的字符都不是可打印字符
                            nextLine.Append(" x0" + string.Format("{0,1:X}", (int)nextChar));
                        else if (char.IsLetterOrDigit(nextChar) || char.IsPunctuation(nextChar))//字母数字十进制标点正常显示
                            nextLine.Append(" " + nextChar + " ");
                        else
                            nextLine.Append(" x" + string.Format("{0,2:X}", (int)nextChar));
                    }
                    lines[i] = nextLine.ToString();
                }
                fs.Close();
                this.textBox2.Lines = lines;
    
            }
        }
    }

    3、关于流缓存的问题

    如果一个C#或者.Net程序需要读取Windows操作系统下面的一个文件,那么就可以通过文件流的方式,而如果需要读取文件流中的两个字节,那么该流则会把请求传递给Windows,注意此时Windows不会直接连接文件系统,在定位文件,并完成读取操作。而是在一次读取过程中,检索文件中的一个大块,并把该块保存到一个内存区域即缓冲区上。(后面系列的StreamReader和StreamWriter将会用到缓冲区.)以后对流中数据的请求,就会从该缓冲区中读取,直到读取完该缓冲区位置。此时windows会从文件中在获取另一个数据块.写入文件的方式与此相同,对于文件,操作系统会自动完成读写操作。

    注:如果需要编写一个流类从没有缓存的设备中读取数据。如果是这样,就应从BufferedStream派生一个类,它实现一个缓冲区(但BufferedStream并不适用于应用程序频繁的切换读数据和写数据的情况).

    C# 文件读写系列三

     

  • 相关阅读:
    Linux 常见命令使用
    Spring Cloud学习03--OpenFeign基本使用
    Spring Cloud学习02--Ribbon基本使用
    Spring Cloud学习01--Eureka基本使用
    两个都不可对角化的矩阵判断相似
    利用相似转化研究对象
    分段函数的应用
    0820. Short Encoding of Words (M)
    0637. Average of Levels in Binary Tree (E)
    0160. Intersection of Two Linked Lists (E)
  • 原文地址:https://www.cnblogs.com/GreenLeaves/p/7192078.html
Copyright © 2011-2022 走看看