一、流(Stream)
C#中文件和流I/O(输入/输出)是指在存储媒介中传入或传出数据。在.NET中,System.IO命名空间包含允许以异步方式和同步方式对数据流和文件进行读取和写入操作的类型。这些命名空间还包含对文件执行压缩和解压缩的类型,以及通过管道和串端口启用通信的类型。
抽象基类Steam支持读取和写入字节。所有表示流的类都继承基类Stream类。Stream类及其派生类提供数据源和存储库的常见视图,使程序员不必了解操作系统和基础设备的具体细节。
流(Stream):System.IO.Stream
是一个抽象类,提供了将字节,提供了将字节(读,写等)传输到源的标准方法。就像包装器类一样传输字节。需要从特定源读取/写入字节的类必须实现Stream类。
根据Stream
类图,以提供从特定源读取/写入字节的功能:
1.1 FileStream类
用于对文件进行读取和写入操作。
1.2 IsolatedStorageFileStream类
用于对独立存储中的文件进行读取或写入操作。IsolatedStroageFileStream
l是由FileStream
扩展来。在使用完类型后,直接或间接释放类型。若要直接释放,请使用 try/cath块中调用其Dispose
方法。若要间接释放类型,使用using
。
IsolatedStorageFile isoFile =IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly |IsolatedStorageScope.Domain, null,null); IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream("substituteUsername", System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read); try { SafeFileHandle aFileHandle = isoStream.SafeFileHandle; Console.WriteLine("A pointer to a file handle has been obtained. " + aFileHandle.ToString() + " " + aFileHandle.GetHashCode()); } catch (Exception e) { Console.WriteLine("Expected exception"); Console.WriteLine(e); }
1.3 MemoryStream类
用于作为后备存储对内存进行读取和写入操作。MemoryStream是内存流,为系统内存提供读写操作,由于MemoryStream是通过无符号字节数组组成,可以说MenoryStream的性能比较出色,所以它担当起了一些其他进行数据交换时的中间工作,同时可降低应用程序中对临时缓冲区和临时文件的需要,其实MemoryStream的重要性不亚FileStream,很多场合我们必须使用它来提高性能。
/// <summary> /// 数据类对象转成字节流 /// </summary> /// <param name="obj"></param> /// <returns></returns> /// MemoryStream: 创建其支持存储区为内存的流。 ///IFormatter : 提供将序列化对象格式化的功能。 public static byte[] ObjectToBytes(object obj) { using (MemoryStream ms = new MemoryStream()) { //以二进制格式将对象或整个连接对象图形序列化和反序列化。 IFormatter formatter = new BinaryFormatter(); //把字符串以二进制放进memStream中 formatter.Serialize(ms, obj); //返回从其创建此流的无符号字节数组。 是会返回所有分配的字节,不管用没用到。 //返回无符号字节数组 ,无符号字节数组 其实就是byte(0~255),有符号字节sbyte(-128~127) return ms.GetBuffer(); } } /// <summary> /// 字节流转成数据类对象 /// </summary> /// <param name="bytes"></param> /// <returns></returns> public static object BytesToObject(byte[] bytes) { using (MemoryStream ms = new MemoryStream(bytes)) { //以二进制格式将对象或整个连接对象图形序列化和反序列化。 IFormatter formatter = new BinaryFormatter(); //把字符串以二进制放进memStream中 return formatter.Deserialize(ms); } }
1.4 BufferedStream类
BufferedStream常用于对其他流的一个封装,它必须和其他流结合一起使用。MemoryStream将所有的内容都放入内存中,而BufferedStream不是。BufferedStream在基础流吸入内存中能够提高读取与写入速度。但是缓冲区设置的大小对性能也有影响,默认值是4096字节,并能够根据需要自动增长,并且很多属性都与基础流一致,缓冲数据能够减少对操作系统的调用次数,缓冲数据主要存储在缓冲区中,缓冲区是内存中的字节块。BufferedStream类提供从基础数据源或存储库读取字节以及将字节写入基础数据源或存储库的实现,在不需要缓冲区时可以防止缓冲区降级输入和输出速度。
缓冲类型下,会在后台自动下载定长的内容,读的时候是从缓冲区中拿东西。这种模式最大的特点是半阻塞式,大部分情况下能大幅度提高处理速度。
在程序逻辑速度大大慢于IO速度时,此方法效率明显。最好是在大文件的情况下,分块读,分块写。
示例:
private static void Buf(string oPath, string copyPath) { Stream s1, s2; BufferedStream bs1, bs2; byte[] b = new byte[1024]; int i; //分别以读、写方式打开两个文件 s1 = File.OpenRead(oPath); s2 = File.OpenWrite(copyPath); //使用缓冲流 bs1 = new BufferedStream(s1); bs2 = new BufferedStream(s2); i = bs1.Read(b,0,1024); //从文件1中读取,写入到文件2中 while (i > 0) { bs2.Write(b,0,i); i = bs1.Read(b,0,1024); } bs2.Flush(); s1.Close(); bs2.Close(); }
1.5 NetworkStream类
NetworkStream类主要是提供用于网络访问的基础数据流,它主要是网络数据传输的载体,并提供同步,异步方法来访问网络数据流。虽然NetworkStream类有构造函数,但在实际情况中更多是通过TcpClient实例的GetStream方法来初始化NetworkStream实例。
NetworkStream使用步骤
- 在tcp连接中,Networkstream可以重复读取,重复写入,不用关掉连接。
- 关掉NetworkStream会自动关闭掉Tcp连接。
- NetworkStream不需要使用Flush方法,数据会自动发送。
- NetworkStream.read会阻塞线程直到有新的数据过来,所以,发送端不释放,接收端不能接收到数据。接收前先判断 DataAvailable 没有数据的不进行Read就能实时收到数据了。
- 如果发送端发送快,接收端接收慢,会造成数据堆叠,即接收端一次可能接收到发送端多次发送的数据流,可以在接收端返回确认接收完成后,再让发送端发送新数据。
NetworkStream示例
if(stream.DataAvailable) { int receiveByteCount=0;//当前已接收数据量 var headerByte = new byte[8]; await stream.ReadAsync(headerByte,0,8);//数据前8位为真实文件长度 long pictureByteLength = BitConverter.ToInt64(headerByte,0);//实际文件大小 var buffer = new byte[1024]; System.IO.MemoryStream ms = new System.IO.MemoryStream(); int tempCount =0; do { tempCount = await stream.ReadAsync(buffer,0,buffer.Length); await ms.WriteAsync(buffer,0,tempCount); receiveByteCount+=tempCount; }while(recevieByteCount<pictureByteLength);//直到整个文件接收完成 }
1.6 pipeStream类
管道(pipeStream)是一种特殊的流,它可以用于在不同的线程之间传送数据。一个线程将数据输出到管道中,另一个线程从管道中读取需要的数据,实现不同线程之间的通信而无需通过临时文件。管道通信可以达到解耦的目的,产生数据的线程不需要直接调用处理数据的方法并等待返回结果,只需要将数据放入管道,接着继续执行自己的任务;而处理数据的线程直接从管道中拿出数据进行处理,不需要进行轮询来获取数据。
pipeStream使用步骤
- 创建 Pipe:创建一个缓冲区,用于读取和写入数据。
- 写入数据:使用 PipelineWriter 将数据写入缓冲区。
- 读取数据并处理:使用 PipelineReader 读取缓冲区中的数据,并进行处理。
pipeStream示例
public static async Task Main(string[] args) { var data = new byte[] { 1, 2, 3, 4, 5 }; // 创建缓冲区 var pipe = new Pipe(); // 写入数据到缓冲区 await pipe.Writer.WriteAsync(data); // 读取数据并处理 while (true) { var result = await pipe.Reader.ReadAsync(); var buffer = result.Buffer; try { if (buffer.IsEmpty && result.IsCompleted) { break; } // 处理数据 foreach (var segment in buffer) { Console.WriteLine(segment.Span[0]); } } finally { // 将已处理的数据从缓冲区中删除 pipe.Reader.AdvanceTo(buffer.End); } } }
1.7 CryptoStream类
公共语言运行时使用面向流的设计进行加密。 此设计的核心是CryptoStream。 任何加密对象实现CryptoStream可以链接在一起实现的任何对象Stream,因此,一个对象的流式处理的输出可以将其填充到另一个对象的输入。 中间结果 (从第一个对象的输出) 不需要进行单独存储。
应始终显式关闭你CryptoStream对象完成后使用它通过调用Clear方法。 执行此操作刷新基础流并使所有剩余的数据块由处理CryptoStream对象。 但是,如果在调用之前,会发生异常Close方法,CryptoStream对象可能不会关闭。 若要确保Close始终调用方法,将置于调用Clear方法内的finally块try / catch语句。
CrytoStream的加密方法
public static string ToEncrypt(string encryptKey, string str) { byte[] byte_key = Encoding.Unicode.GetBytes(encryptKey); //将密钥字符串转换为字节序列 byte[] byte_data = Encoding.Unicode.GetBytes(str); //将字符串转换为字节序列 using var des = DES.Create(); //创建加密流对象 using var memory_stream = new MemoryStream(); //创建内存流对象 using var crypto_stream = new CryptoStream(memory_stream, des. CreateEncryptor(byte_key, byte_key), CryptoStreamMode.Write); //创建加密流对象 crypto_stream.Write(byte_data, 0, byte_data.Length); //向加密流中写入字节序列 crypto_stream.FlushFinalBlock(); //将数据压入基础流 crypto_stream.Close(); //关闭加密流 memory_stream.Close(); //关闭内存流 return Convert.ToBase64String(memory_stream.ToArray()); //从内存流中获取并返回加密后的字符串 }
CrytoStream的解密方法
public static string ToDecrypt(string encryptKey, string str) { byte[] byte_key = Encoding.Unicode.GetBytes(encryptKey); //将密钥字符串转换为字节序列 byte[] byte_data = Convert.FromBase64String(str); //将加密后的字符串转换为字节序列 using var des = DES.Create();//创建加密流对象 using var memory_stream = new MemoryStream(byte_data);//创建内存流对象并写入数据 using var crypto_stream = new CryptoStream(memory_stream, des. CreateDecryptor(byte_key, byte_key), CryptoStreamMode.Read); //创建加密流对象 byte[] bt_temp = new byte[200];//创建字节序列对象 MemoryStream memory_stream_temp = new();//创建内存流对象 int i = 0;//创建记数器 while ((i = crypto_stream.Read(bt_temp, 0, bt_temp.Length)) > 0) //使用while循环得到解密数据 { memory_stream_temp.Write(bt_temp, 0, i);//将解密后的数据放入内存流 } crypto_stream.Close(); //关闭加密流 memory_stream.Close(); //关闭内存流 return Encoding.Unicode.GetString(memory_stream_temp.ToArray()); //方法返回解密后的字符串 }