Stream 流操作
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Stream 流操作相关的知识,希望对你有一定的参考价值。
Stream 类
先看下面的图
Stream 是所有流的抽象基类(不能被实例化,需要使用他的派生类FileStream/MemoryStream/BufferedStream)。流是字节序列的抽象概念,例如文件、输入/输出设备、内部进程通信管道或者 TCP/IP 套接字。Stream 类及其派生类提供这些不同类型的输入和输出的一般视图,使程序员不必了解操作系统和基础设备的具体细节。
流涉及三个基本操作:
- 可以读取流。读取是从流到数据结构(如字节数组)的数据传输。
- 可以写入流。写入是从数据结构到流的数据传输。
- 流可以支持查找。查找是对流内的当前位置进行查询和修改。查找功能取决于流具有的后备存储区类型。例如,网络流没有当前位置的统一概念,因此一般不支持查找。
有些流实现执行基础数据的本地缓冲以提高性能。对于这样的流,Flush 方法可用于清除所有内部缓冲区并确保将所有数据写入基础数据源或储存库。
在 Stream 上调用 Close 将刷新所有经过缓冲处理的数据,本质上是为您调用了 Flush。Close 也会释放操作系统资源,如文件句柄、网络连接或用于任何内部缓冲的内存。BufferedStream 类提供了将一个经过缓冲的流环绕另一个流的功能,以便提高读写性能。
如果需要不带后备存储区(即位存储桶)的流,请使用 Null。
对实现者的说明: (FileStream/MemoryStream/BufferedStream)
在实现 Stream 的派生类时,必须提供 Read 和 Write 方法的实现。异步方法 BeginRead、EndRead、BeginWrite 和 EndWrite 通过同步方法 Read 和 Write 实现。同样,Read 和 Write 的实现也将与异步方法一起正常工作。ReadByte 和 WriteByte 的默认实现创建一个新的单元素字节数组,然后调用 Read 和 Write 的实现。当从 Stream 派生时,如果有内部字节缓冲区,则强烈建议重写这些方法以访问内部缓冲区,这样性能将得到显著提高。还必须提供 CanRead、CanSeek、CanWrite、Flush、Length、Position、Seek 和 SetLength 的实现。
使用 FileStream 类来读取、 写入、 打开和关闭文件系统上的文件以及处理其他包括管道、 标准输入和标准输出的文件相关的操作系统句柄。 您可以使用 Read, ,Write, ,CopyTo, ,和 Flush 方法来执行同步操作时,或 ReadAsync, ,WriteAsync, ,CopyToAsync, ,和 FlushAsync 方法来执行异步操作。 使用异步方法以在不阻塞主线程的情况下执行占用大量资源的文件。 在 Windows 8.x 应用商店 应用或 桌面 应用中一个耗时的流操作可能阻塞 UI 线程并让您的应用看起来好像不工作时,这种性能的考虑就显得尤为重要了。 FileStream 缓冲输入和输出来更好的性能。
MemoryStream 类 (创建其后备存储区为内存的流。)
MemoryStream 类创建具有内存而非磁盘或网络连接作为后备存储区的流。MemoryStream 封装作为无符号字节数组存储的数据,该数据在创建 MemoryStream 对象时初始化,或者该数组可以创建为空数组。可在内存中直接访问这些封装的数据。内存流可降低应用程序中对临时缓冲区和临时文件的需要。
用无符号字节数组创建的内存流提供无法调整大小的数据流。当使用字节数组时,虽然根据传递到构造函数中的参数可能能够修改现有内容,但既不能追加也不能收缩流。空内存流是可调整大小的,而且可以向其写入和从中读取。
BufferedStream 类 (添加缓冲层以读取和写入另一个流上的操作。 此类不能被继承。seald)
添加缓冲层以读取和写入另一个流上的操作。 此类不能被继承。
FileStream/MemoryStream/BufferedStream都是Stream的派生类,是Stream的具体实现,BufferedStream同时还是一个密封类
对stream而言仅表示到字节流这一个层面所以是没有也不需要编码方式的(构造函数里也不会需要这样的东西)
如果需要向流中写数据时则可能回涉及到编码(但如果是二进制写入仍不需要)
stream本身可以提供面向字节流的读写操作(如果需要面向字符的读写需要借助TextReader/TextWriter )
下面例子来至MSDN演示直接使用Stream读写字节流,可以看到读写的对象是字节,所以操作前需要提前编码
1 using System; 2 using System.IO; 3 using System.Text; 4 5 class Test 6 { 7 8 public static void Main() 9 { 10 string path = @"c:\\temp\\MyTest.txt"; 11 12 // Delete the file if it exists. 13 if (File.Exists(path)) 14 { 15 File.Delete(path); 16 } 17 18 //Create the file. 19 using (FileStream fs = File.Create(path)) 20 { 21 AddText(fs, "This is some text"); 22 AddText(fs, "This is some more text,"); 23 AddText(fs, "\\r\\nand this is on a new line"); 24 AddText(fs, "\\r\\n\\r\\nThe following is a subset of characters:\\r\\n"); 25 26 for (int i=1;i < 120;i++) 27 { 28 AddText(fs, Convert.ToChar(i).ToString()); 29 30 } 31 } 32 33 //Open the stream and read it back. 34 using (FileStream fs = File.OpenRead(path)) 35 { 36 byte[] b = new byte[1024]; 37 UTF8Encoding temp = new UTF8Encoding(true); 38 while (fs.Read(b,0,b.Length) > 0) 39 { 40 Console.WriteLine(temp.GetString(b)); 41 } 42 } 43 } 44 45 private static void AddText(FileStream fs, string value) 46 { 47 byte[] info = new UTF8Encoding(true).GetBytes(value); 48 fs.Write(info, 0, info.Length); 49 } 50 }
TextReader/TextWriter 类
TextReader 为 StreamReader 和 StringReader 的抽象基类,它们分别从流和字符串读取字符(读取出来就直接是字符了,实际上他们也只是stream工具类,操作时必须基于stream,他们派生类源码也表明stream是其重要成员,使用他们可以为读写提供方便,但却不是必须的,如上面的例子同样可以编码后直接写入字节,或者读出字节再解码)。使用这些派生类可打开一个文本文件以读取指定范围的字符,或基于现有的流创建一个读取器。
TextWriter 是 StreamWriter 和 StringWriter 的抽象基类,它们将字符分别写入流和字符串。创建一个 TextWriter 实例,将对象写入字符串,或将字符串写入文件,或序列化 XML。也可使用 TextWriter 的实例将文本写入自定义后备存储区(所使用的 API 与用于字符串或流的 API 相同),或者增加对文本格式化的支持。
TextWriter 的所有使用基元数据类型作为参数的 Write 方法都将值作为字符串写出。
默认情况下,TextWriter 不是线程安全的。有关线程安全包装的信息,请参见 TextWriter.Synchronized。
为了生成有用的 TextWriter 实例,派生类必须至少实现 Write 方法。
TextReader/TextWriter同样是抽象的基类,要使用他们必须通过其派生类( StreamReader 和 StringReader 或 StreamWriter 和 StringWriter)使用
StreamReader 类
实现一个 TextReader,使其以一种特定的编码从字节流中读取字符。
除非另外指定,StreamReader 的默认编码为 UTF-8,而不是当前系统的 ANSI 代码页。UTF-8 可以正确处理 Unicode 字符并在操作系统的本地化版本上提供一致的结果。
默认情况下,StreamReader 不是线程安全的。有关线程安全包装的信息,请参见 TextReader.Synchronized。
Read(Char[],Int32,Int32) 和 Write(Char[],Int32,Int32) 方法重载读取和写入 count 参数指定的字符数。这些区别于 BufferedStream.Read 和 BufferedStream.Write,后两者读写由 count 参数指定的字节数。仅将 BufferedStream 方法用于读写字节数组元素的整型数。
查看StreamReader的构造函数可以看出它其实是需要一个stream主体的即使没有要求出入,也会隐式创建一个,而它同样需要指定一个编码方式,如果没有指定则使用默认编码方式(默认值不代表没有)
来至MSDN的例子
1 using System; 2 using System.IO; 3 4 class Test 5 { 6 public static void Main() 7 { 8 try 9 { 10 // Create an instance of StreamReader to read from a file. 11 // The using statement also closes the StreamReader. 12 using (StreamReader sr = new StreamReader("TestFile.txt")) 13 { 14 String line; 15 // Read and display lines from the file until the end of 16 // the file is reached. 17 while ((line = sr.ReadLine()) != null) 18 { 19 Console.WriteLine(line); 20 } 21 } 22 } 23 catch (Exception e) 24 { 25 // Let the user know what went wrong. 26 Console.WriteLine("The file could not be read:"); 27 Console.WriteLine(e.Message); 28 } 29 } 30 }
看下例子的构造函数,通过源码查看可以知道最终调用的是
实际使用了UTF8作为编码方式,使用1024作为了初始大小
查看该构造函数可以看出确实隐式创建了一个stream(这种隐式的stream会在StreamReader释放时同时被释放)
StreamReader的Dispose方法
而查看源码可以看出来无论这个stream是外面传入的还是隐式创建的,在外面调用StreamReader的Dispose方法是,都会同时调用stream的Close方法释放stream,
对于如“xxxxxxxxxxxx\\r\\n”或“xxxxxxxxxxxx”这种尾部数据无论最后有没有\\r\\n ReadLine() 的结果都是null
StringReader 类
实现 TextReader ,使其从字符串读取。
StringReader 使您能够同步或异步读取的字符串。 可以使用的一次读取一个字符 Read 或 ReadAsync 方法时,在使用行 ReadLine 或 ReadLineAsync 方法和整个字符串使用 ReadToEnd 或 ReadToEndAsync 方法。
StringReader相对StreamReader简单,暴露出来的构造函数也只有一个
MSDN例子
1 using System; 2 using System.IO; 3 using System.Text; 4 5 namespace ConsoleApplication 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 ReadCharacters(); 12 } 13 14 static async void ReadCharacters() 15 { 16 StringBuilder stringToRead = new StringBuilder(); 17 stringToRead.AppendLine("Characters in 1st line to read"); 18 stringToRead.AppendLine("and 2nd line"); 19 stringToRead.AppendLine("and the end"); 20 21 using (StringReader reader = new StringReader(stringToRead.ToString())) 22 { 23 string readText = await reader.ReadToEndAsync(); 24 Console.WriteLine(readText); 25 } 26 } 27 } 28 } 29 // The example displays the following output://// Characters in 1st line to read// and 2nd line// and the end//
用的地方也相对少一些,有的时候想像流一样的直接处理字符串可以用
StreamWriter 类
实现一个 TextWriter,使其以一种特定的编码向流中写入字符。
StreamWriter 旨在以一种特定的编码输出字符,而从 Stream 派生的类则用于字节的输入和输出。
StreamWriter 默认使用 UTF8Encoding 的实例,除非指定了其他编码。构造 UTF8Encoding 的这个实例使得 Encoding.GetPreamble 方法返回以 UTF-8 格式编写的 Unicode 字节顺序标记。当不再向现有流中追加时,编码的报头将被添加到流中。这表示使用 StreamWriter 创建的所有文本文件都将在其开头有三个字节顺序标记。UTF-8 可以正确处理所有的 Unicode 字符并在操作系统的本地化版本上产生一致的结果。
默认情况下,StreamWriter 不是线程安全的。有关线程安全包装的信息,请参见 TextWriter.Synchronized。
MSDN例子
using System; using System.IO; public class TextToFile { private const string FILE_NAME = "MyFile.txt"; public static void Main(String[] args) { if (File.Exists(FILE_NAME)) { Console.WriteLine("{0} already exists.", FILE_NAME); return; } using (StreamWriter sw = File.CreateText(FILE_NAME)) { sw.WriteLine ("This is my file."); sw.WriteLine ("I can write ints {0} or floats {1}, and so on.", 1, 4.2); sw.Close(); } } }
其实与StringReader 是类似的就不重复说了
StringWriter 类
实现一个用于将信息写入字符串的 TextWriter。该信息存储在基础 StringBuilder 中。
类似的没有什么可以说的可以直接看MSDN
BinaryReader 类
用特定的编码将基元数据类型读作二进制值。
直接查看源码
可以看出来BinaryReader并没有继承TextReader ,但在他的成员里依然有Stream对象
构造函数也只有下面2个
名称 | 说明 |
BinaryReader (Stream) | 基于所提供的流,用 UTF8Encoding 初始化 BinaryReader 类的新实例。 由 .NET Compact Framework 支持。 |
BinaryReader (Stream, Encoding) | 基于所提供的流和特定的字符编码,初始化 BinaryReader 类的新实例。 由 .NET Compact Framework 支持。 |
MSDN例子的一部分
1 public AppSettings() 2 { 3 // Create default application settings. 4 aspectRatio = 1.3333F; 5 lookupDir = @"C:\\AppDirectory"; 6 autoSaveTime = 30; 7 showStatusBar = false; 8 9 if(File.Exists(fileName)) 10 { 11 BinaryReader binReader = 12 new BinaryReader(File.Open(fileName, FileMode.Open)); 13 try 14 { 15 // If the file is not empty, 16 // read the application settings. 17 if(binReader.PeekChar() != -1) 18 { 19 aspectRatio = binReader.ReadSingle(); 20 lookupDir = binReader.ReadString(); 21 autoSaveTime = binReader.ReadInt32(); 22 showStatusBar = binReader.ReadBoolean(); 23 } 24 } 25 26 // If the end of the stream is reached before reading 27 // the four data values, ignore the error and use the 28 // default settings for the remaining values. 29 catch(EndOfStreamException e) 30 { 31 Console.WriteLine("{0} caught and ignored. " + 32 "Using default values.", e.GetType().Name); 33 } 34 finally 35 { 36 binReader.Close(); 37 } 38 } 39 40 }
可以看到它提供了许多不同的读取方式
BinaryWriter 类
以二进制形式将基元类型写入流,并支持用特定的编码写入字符串。
类似的不多说
File 类
静态工具类
提供用于创建、复制、删除、移动和打开文件的静态方法,并协助创建 FileStream 对象
将 File 类用于典型的操作,如复制、移动、重命名、创建、打开、删除和追加到文件。也可将 File 类用于获取和设置文件属性或有关文件创建、访问及写入操作的 DateTime 信息。
许多 File 方法在您创建或打开文件时返回其他 I/O 类型。可以使用这些其他类型进一步处理文件。有关更多信息,请参见特定的 File 成员,如 OpenText、CreateText 或 Create。
由于所有的 File 方法都是静态的,所以如果只想执行一个操作,那么使用 File 方法的效率比使用相应的 FileInfo 实例方法可能更高。所有的 File 方法都要求当前所操作的文件的路径。
File 类的静态方法对所有方法都执行安全检查。如果打算多次重用某个对象,可考虑改用 FileInfo 的相应实例方法,因为并不总是需要安全检查。
默认情况下,将向所有用户授予对新文件的完全读/写访问权限。
FileMode
使用Create会先清除文件内容,从起始位置写入,Open则会直接写入,覆盖(所以原文件后面的内容可能还是会存在),OpenOrCreate同样有这样的特性(如果想擦除直接使用Create即可)
FileAccess
FileShare
以上3个属性都是Stream的必须属性(在StreamRead这种里出现了也是因为要给内建Stream使用,Stream部分构造函数不包含所有的属性,是因为使用了默认值)
对Stream来说如果FileShare如果指定None表示当前任务想要独占文件(如果FileAccess指定为Read此时如果该文件被其他进程或任务已经打开,则打开会报错,同样如果打开成功,其他进程也无法打开)
如果使用FileAccess.Write,则无论FileShare是什么值其他进程都无法再次写入,不过配合合适的FileShare其他进程还是可以Read)
读写对比
直接使用Stream进行读写
直接读写,适用用对数据的hex读写,通过读写方法参数byte[] buffer,int offset,int count,可以看出读写需要自己维护长度偏移(对于FileStream这个Stream就是文件本身,写入就是写入,补需要单独保存,不过未关闭Stream时不一定生效)
eg: output.Write(bytes, 0, numBytes);
int n = s.Read(bytes, numBytesRead, numBytesToRead);
使用StreamReader/StreamWriter继续读写
对于为本读写更加方便,读写对象直接就是字符(不是字节)提供很对方便的读写方法ReadLine/ReadEnd等 (写入后不一定保存到文件,需要调用Dispose();)
需要注意的是即使主动关闭StreamWriter内的Stream也不一定会写入成功关闭应用程序会关闭Stream(调用Flush()后,关闭Stream会让StreamWriter完成保存)
eg:sw.WriteLine("is some text");
Console.WriteLine(sr.ReadLine());
关于删除指定内容StreamWriter需要先读出再修改在写入(建议使用StringBuilder ,不要频繁操作string)
Stream可以修改指定位置(字节流层面的位置),而删除中间的一段,也相当于后面的从新写入
因为对于流或文件删除中间一段就意味着后面文件需要重新排列即重新写入
下面再来个FileStream操作的例子
1 using System; 2 using System.IO; 3 class MyStream 4 { 5 private const string FILE_NAME = "Test.data"; 6 public static void Main(String[] args) 7 { 8 // Create the new, empty data file. 9 if (File.Exists(FILE_NAME)) 10 { 11 Console.WriteLine("{0} already exists!", FILE_NAME); 12 return; 13 } 14 FileStream fs = new FileStream(FILE_NAME, FileMode.CreateNew); 15 // Create the writer for data. 16 BinaryWriter w = new BinaryWriter(fs); 17 // Write data to Test.data. 18 for (int i = 0; i < 11; i++) 19 { 20 w.Write( (int) i); 21 } 22 w.Close(); 23 fs.Close(); 24 // Create the reader for data. 25 fs = new FileStream(FILE_NAME, FileMode.Open, FileAccess.Read); 26 BinaryReader r = new BinaryReader(fs); 27 // Read data from Test.data. 28 for (int i = 0; i < 11; i++) 29 { 30 Console.WriteLine(r.ReadInt32()); 31 } 32 r.Close(); 33 fs.Close(); 34 } 35 }
以上是关于Stream 流操作的主要内容,如果未能解决你的问题,请参考以下文章
深度掌握 Java Stream 流操作,让你的代码高出一个逼格!