在 C# 中提供了 Stream 类, 用于流的操作, 比如读取和写入.

流实际是一个被激活的数据源连接, 或者说它是一个有序的字节序列. 在使用时首先需要打开流, 在使用完毕后需要关闭流. 如果使用结束后没有关闭针对某个数据源的流, 就可能造成内存泄漏, 线程饥饿等问题(因为资源一直被占用), 以及其他的性能问题.

在 C# 中有 using 表达式, 可以在结束这个表达式的 scope 的时候自动释放 disposable. 在 C# 8.0 之后, 更是提供了单行的 using 表达式, 它会在所在方法结束后自动释放 disposable, 而不是 using 所定义的 scope 中. 这样可以避免过深的嵌套代码出现, 不过需要在写代码的时候注意 scope 的范围.

对流进行写入

下面演示将某个文本写入到文件:

cs
static void WriteToFile()
{
    var msg = "Hello all, welcome to the c sharp world!";
    var bytes = Encoding.UTF8.GetBytes(msg);
    using (var stream = new FileStream("1.txt", FileMode.OpenOrCreate))
    {
        if (stream.CanWrite)
        {
            stream.Write(bytes);
        }
        else
        {
            System.Console.WriteLine("写入失败!");
        }
    }
}

如果支持 C# 8.0, 则可以简化为如下(使用 using 单行表达式):

cs
static void WriteToFile()
{
    var msg = "Hello all, welcome to the c sharp world!";
    var bytes = Encoding.UTF8.GetBytes(msg);
    using var stream = new FileStream("1.txt", FileMode.OpenOrCreate);
    if (stream.CanWrite)
    {
        stream.Write(bytes);
    }
    else
    {
        System.Console.WriteLine("写入失败!");
    }
}

使用 seek 方法进行随机访问

seek 方法的用法也非常直观, 使用上面的代码, 加上 Seek, 则每次都会把 msg 附加到之前内容的后面:

cs
if (stream.CanWrite)
{
    // 将写指针指向字节序列的尾部
    stream.Seek(0, SeekOrigin.End);
    stream.Write(bytes);
}
else
{
    System.Console.WriteLine("写入失败!");
}

从流中读取数据

还是以本地文件流为例, 读取我们之前写入的文件:

cs
static void ReadFromFile()
{
    try
    {

        using var stream = File.OpenRead("1.txt");
        if (stream.CanRead)
        {
            byte[] arr = new byte[stream.Length];
            stream.Read(arr);
            var result = Encoding.UTF8.GetString(arr);
            System.Console.WriteLine($"读取的字符串: {result}");
        }
        else
        {
            System.Console.WriteLine("文件不可读");
        }
    }
    catch
    {
        System.Console.WriteLine("打开文件失败");
    }
}

从上面的代码中可以看到 C# 中的 Stream 接口非常简单易用。同时,后续在网络编程中会经常用到。