ZetCode

C# FileStream

最后修改于 2025 年 4 月 20 日

本教程介绍了如何在 C# 中使用 FileStream 类来执行文件 I/O 操作。FileStream 为文件操作提供了一个流,支持同步和异步的读取/写入操作。

FileStream 类派生自 Stream 类,并提供对标准输入、输出和错误流的访问。它允许以字节级别读取和写入文件。

FileStream 适用于处理二进制文件或当您需要对文件操作进行细粒度控制时。它支持通过查找对文件内容进行随机访问。

基本 FileStream 示例

此示例演示了使用 FileStream 的基本文件操作。我们创建一个文件,向其中写入数据,然后将数据读回。

Program.cs
using System;
using System.IO;
using System.Text;

class Program
{
    static void Main()
    {
        // Write to file
        using (FileStream fs = new FileStream("test.txt", FileMode.Create))
        {
            byte[] data = Encoding.UTF8.GetBytes("Hello FileStream!");
            fs.Write(data, 0, data.Length);
        }

        // Read from file
        using (FileStream fs = new FileStream("test.txt", FileMode.Open))
        {
            byte[] buffer = new byte[fs.Length];
            fs.Read(buffer, 0, buffer.Length);
            string text = Encoding.UTF8.GetString(buffer);
            Console.WriteLine(text);
        }
    }
}

该程序创建一个名为 "test.txt" 的文件并将一个字符串写入其中。 然后它将内容读回并显示出来。 FileStream 构造函数将文件路径和 FileMode 作为参数。 FileMode.Create 指定应创建一个新文件。

写入时,我们使用 UTF8 编码将字符串转换为字节。 Write 方法将字节数组写入文件。 对于读取,我们创建一个与文件长度相同的字节数组。 Read 方法用文件内容填充此数组,然后我们将其转换回字符串。

追加到文件

此示例演示了如何使用 FileStream 和 FileMode.Append 将数据附加到现有文件。

Program.cs
using System;
using System.IO;
using System.Text;

class Program
{
    static void Main()
    {
        string filePath = "log.txt";
        
        // Append first message
        using (FileStream fs = new FileStream(filePath, FileMode.Append))
        {
            byte[] data = Encoding.UTF8.GetBytes("First log entry\n");
            fs.Write(data, 0, data.Length);
        }

        // Append second message
        using (FileStream fs = new FileStream(filePath, FileMode.Append))
        {
            byte[] data = Encoding.UTF8.GetBytes("Second log entry\n");
            fs.Write(data, 0, data.Length);
        }

        // Display file content
        Console.WriteLine(File.ReadAllText(filePath));
    }
}

该示例演示了如何将内容添加到现有文件的末尾。 FileMode.Append 会自动将流定位到文件的末尾。 这对于日志文件或当您需要添加新数据而不覆盖现有内容时非常有用。

每次我们使用 FileMode.Append 打开文件时,写入位置都会设置为结尾。 该示例依次写入两条消息,然后显示完整的文件内容。 请注意,在编写文本行时,我们需要显式包含换行符。

读取和写入二进制数据

FileStream 通常用于二进制文件操作。 此示例演示如何写入和读取原始数据类型。

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        string filePath = "data.bin";
        
        // Write binary data
        using (FileStream fs = new FileStream(filePath, FileMode.Create))
        {
            byte[] intBytes = BitConverter.GetBytes(42);
            byte[] doubleBytes = BitConverter.GetBytes(3.14159);
            byte[] boolBytes = BitConverter.GetBytes(true);

            fs.Write(intBytes, 0, intBytes.Length);
            fs.Write(doubleBytes, 0, doubleBytes.Length);
            fs.Write(boolBytes, 0, boolBytes.Length);
        }

        // Read binary data
        using (FileStream fs = new FileStream(filePath, FileMode.Open))
        {
            byte[] buffer = new byte[4]; // Size of int
            
            fs.Read(buffer, 0, 4);
            int intValue = BitConverter.ToInt32(buffer, 0);
            
            fs.Read(buffer, 0, 8);
            double doubleValue = BitConverter.ToDouble(buffer, 0);
            
            fs.Read(buffer, 0, 1);
            bool boolValue = BitConverter.ToBoolean(buffer, 0);

            Console.WriteLine($"Int: {intValue}");
            Console.WriteLine($"Double: {doubleValue}");
            Console.WriteLine($"Boolean: {boolValue}");
        }
    }
}

该示例将整数、双精度浮点数和布尔值写入二进制文件。 我们使用 BitConverter 将这些值转换为字节数组。 读取时,我们必须知道文件中数据类型的确切大小和顺序。

对于读取,我们为每种数据类型创建一个适当大小的缓冲区。 BitConverter 类有助于将字节数组转换回其原始类型。 请注意,我们需要处理不同数据类型的不同大小(int 为 4 字节,double 为 8 字节,bool 为 1 字节)。

随机文件访问

FileStream 支持通过 Seek 方法对文件内容进行随机访问。 此示例演示了从特定位置读取和写入。

Program.cs
using System;
using System.IO;
using System.Text;

class Program
{
    static void Main()
    {
        string filePath = "random.bin";
        
        // Create and initialize file
        using (FileStream fs = new FileStream(filePath, FileMode.Create))
        {
            // Fill with 100 bytes of value 0
            for (int i = 0; i < 100; i++)
            {
                fs.WriteByte(0);
            }
        }

        // Modify specific positions
        using (FileStream fs = new FileStream(filePath, FileMode.Open))
        {
            // Write at position 10
            fs.Seek(10, SeekOrigin.Begin);
            fs.WriteByte(255);
            
            // Write at position 50 from current position (now 11)
            fs.Seek(39, SeekOrigin.Current);
            fs.WriteByte(128);
            
            // Write at position 90 from end (file length is 100)
            fs.Seek(-10, SeekOrigin.End);
            fs.WriteByte(64);
        }

        // Read modified bytes
        using (FileStream fs = new FileStream(filePath, FileMode.Open))
        {
            fs.Seek(10, SeekOrigin.Begin);
            Console.WriteLine("Position 10: " + fs.ReadByte());
            
            fs.Seek(50, SeekOrigin.Begin);
            Console.WriteLine("Position 50: " + fs.ReadByte());
            
            fs.Seek(90, SeekOrigin.Begin);
            Console.WriteLine("Position 90: " + fs.ReadByte());
        }
    }
}

该示例首先创建一个包含 100 个零字节的文件。 然后,它使用 Seek 修改特定位置。 SeekOrigin 指定定位的参考点:文件的开头、当前位置或结尾。

我们演示了所有三个 SeekOrigin 值。 第一个写入是从开头位置 10 开始。 第二个写入从当前位置移动 39 个字节(在位置 10 写入 255 之后)。 第三个写入从文件末尾向后查找 10 个字节。 最后,我们通过从这些位置读取来验证更改。

带缓冲的 FileStream

此示例演示了如何通过使用带缓冲的 FileStream 来提高性能。 该构造函数允许指定缓冲区大小。

Program.cs
using System;
using System.Diagnostics;
using System.IO;
using System.Text;

class Program
{
    static void Main()
    {
        string filePath = "largefile.bin";
        int bufferSize = 4096; // 4KB buffer
        int fileSize = 10 * 1024 * 1024; // 10MB
        
        // Write without buffering
        var sw = Stopwatch.StartNew();
        using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 1))
        {
            for (int i = 0; i < fileSize; i++)
            {
                fs.WriteByte((byte)(i % 256));
            }
        }
        Console.WriteLine($"Unbuffered write: {sw.ElapsedMilliseconds}ms");
        
        // Write with buffering
        sw.Restart();
        using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize))
        {
            for (int i = 0; i < fileSize; i++)
            {
                fs.WriteByte((byte)(i % 256));
            }
        }
        Console.WriteLine($"Buffered write: {sw.ElapsedMilliseconds}ms");
        
        File.Delete(filePath);
    }
}

该示例比较了缓冲写入与无缓冲写入的性能。 我们通过写入单个字节来创建一个 10MB 的文件。 第一个 FileStream 使用 1 字节缓冲区(实际上是无缓冲的),而第二个使用 4KB 缓冲区。

性能差异可能非常显着,尤其是在许多小型写入的情况下。 FileStream 构造函数中的 bufferSize 参数指定内部缓冲区的大小。 较大的缓冲区通过在内存中缓存数据来减少实际磁盘操作的次数。

异步文件操作

FileStream 支持异步操作,以便在 I/O 密集型场景中获得更好的性能。 此示例显示了异步读取/写入。

Program.cs
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        string filePath = "async.txt";
        
        // Asynchronous write
        using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
        {
            byte[] data = Encoding.UTF8.GetBytes("This is async file operation\n");
            await fs.WriteAsync(data, 0, data.Length);
            await fs.FlushAsync();
        }
        
        // Asynchronous read
        using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None, 4096, true))
        {
            byte[] buffer = new byte[fs.Length];
            await fs.ReadAsync(buffer, 0, buffer.Length);
            string content = Encoding.UTF8.GetString(buffer);
            Console.WriteLine(content);
        }
    }
}

该示例演示了使用 WriteAsync 和 ReadAsync 的异步文件操作。 请注意,Main 方法标记为 async 并返回 Task。 FileStream 构造函数包含设置为 true 的 useAsync 参数。

异步操作在等待 I/O 完成时不会阻塞调用线程。 这在处理多个请求的 GUI 应用程序或服务中尤其有价值。 await 关键字通过自动处理延续来简化异步操作的使用。

带有 FileShare 选项的 FileStream

此示例演示了当多个进程需要使用同一个文件时,FileShare 选项如何控制对文件的访问。

Program.cs
using System;
using System.IO;
using System.Threading;

class Program
{
    static void Main()
    {
        string filePath = "shared.txt";
        
        // First process opens file for writing with FileShare.Read
        var fs1 = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read);
        fs1.WriteByte(65); // Write 'A'
        
        // Second process can open for reading
        using (var fs2 = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
        {
            Console.WriteLine("Second process read: " + fs2.ReadByte());
        }
        
        // Try to open another writer (will fail)
        try
        {
            var fs3 = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.Read);
            Console.WriteLine("This won't be printed");
        }
        catch (IOException ex)
        {
            Console.WriteLine("Expected error: " + ex.Message);
        }
        
        fs1.Close();
        File.Delete(filePath);
    }
}

该示例演示了 FileShare 选项如何影响文件访问。 第一个 FileStream 使用 FileShare.Read 打开文件进行写入,允许其他进程同时读取该文件。

第二个 FileStream 成功打开文件进行读取。 但是,尝试打开另一个写入器失败,因为第一个写入器只允许共享读取。 FileShare 选项有助于协调多个进程之间对共享文件的访问。

来源

FileStream 类文档

本教程介绍了 C# 中使用 FileStream 进行的文件 I/O 操作,包括基本操作、二进制数据、随机访问、缓冲、异步操作和文件共享。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直撰写编程文章。 到目前为止,我撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 C# 教程