http://www.codeguru.com/csharp/csharp/cs_data/streaming/article.php/c4223/Streams-and-NET.htm

In this article I will show you the classes the .NET provides to use streams. I will start by looking at basic stream access, which will lead me to explain encoding and stream readers and writers, serializing objects to streams and finally I will explain how to create a stream.

Row, row, row the boat...

Let's start right at the beginning: the System.IO.Stream class. This is an abstract class that defines the basic functionality that should be implemented by a concrete stream class. This class has properties to determine what you can do to the stream (is it readable, writeable; does it support random access?), and information about the size of the stream and the current seek position in the stream. Stream has methods to read and write single bytes and arrays of bytes; if you chose to read or write arrays of bytes, this can be done synchronously or asynchronously.

A stream can be used like this (assuming that GetInputStream() and GetOutputStream() are methods that return stream references):

  1. Stream outStr = GetOutputStream();
  2. byte[] outBuf = new byte[7]{82, 105, 99, 104, 97, 114, 100};
  3. outStr.Write(outBuf, 0, outBuf.Length);
  4. outStr.Close(); // don't need it any more
  5. Stream inStr = GetInputStream();
  6. byte[] inBuf = new byte[(int)inStr.Length];
  7. inStr.Read(inBuf, 0, inBuf.Length);
  8. inStr.Close(); // don't need it any more

(Out of interest, notice that Stream.Length is a long, whereas the value passed to Array.CreateInstance() when declaring the size of the array with new, is an int, so I have to do an explicit cast.)

 
Dysfunction Junction: A Pragmatic Guide to Getting Started with DevOps
Download Now
 

In this code, it does not matter what the stream is based on (it could be a file or a socket, for example), the same methods are used. Notice that I call Close() as soon as I have finished using the stream. This is generally a good practice with .NET because it ensures that resources are not held longer than they are required.

For example, if the outStr reference is a stream based upon a file, the file will be open until Close() is called, and typically this will mean that there will be an exclusive lock on the file, preventing other code from accessing the file. Also, file streams are buffered, and so any data you write will not be written to the file until the stream is flushed - Close() will do this. You could argue that when the stream is garbage collected the lock on the file will be released, however, in most cases you do not know when the stream will be garbage collected, and unless you explicitly tell the garbage collector to do its work, this will be when an allocation fails due to a lack of memory - hopefully an event that will occur only occasionally. It is far better to explicitly indicate that you are finished with the stream by calling Close().

Byte-ing the Bullet

Dealing in bytes is a bit of a pain: have you noticed what the data in the outBuf example above represents? It is the ASCII text "Richard". The base class library helps you to create buffers of bytes, but before I talk about these classes I need to point out that byte and char are not the same, a C# byte (System.Byte) is a single, unsigned byte, whereas a C# char (System.Char) is a UNICODE character, 2 bytes. As you can see from the code above, this makes writing strings to streams very inconvenient, what is needed is a class to convert a string to a byte array.

The System.Text namespace has a class called Encoding that allows you to convert between chars, bytes and strings. There are actually several encoding classes, used to convert to ASCII, UNICODE (big endian and little endian), UTF7 and UTF8. The Encoding class has static properties that will return a reference to one of these classes. For example, if I am interested in converting an array of bytes that represents an ASCII string to a System.String I can use the ASCII property of Encoding to return a reference to an ASCIIEncoding class:

  1. byte[] buf = new byte[7]{82, 105, 99, 104, 97, 114, 100};
  2. string str;
  3. str = System.Text.Encoding.ASCII.GetString(buf);

In this code, str will be initialised with the string "Richard". This covers making the data read from a stream useful, but what about writing data to streams? The appropriate Encoding classes have methods for that too:

  1. string str = "Grimes";
  2. byte[] b = new byte[str.Length];
  3. Encoding.ASCII.GetBytes(str.ToCharArray(),
  4. 0,
  5. str.Length,
  6. b,
  7. 0);

So, now you are happy, you can read strings from streams and write strings to streams. But are you completely happy? The code looks rather cluttered, and anyway what about other data types?

Readers and Writers

To make life much easier for you the designers of the base class library have provided reader and writer classes. These are based on streams and allow you to read and write data types other than arrays of bytes. These classes can be found in the System.IO namespace:

Class

Base Class

Description

BinaryReader

Object

Allows you to read data from a stream as the various base class data types

BinaryWriter

Object

Allows you to write data to a stream as the various base class data types

StreamReader

TextReader

Allows you to read data from a stream as lines or characters, you can specify the encoding or allow the class to determine it. The class can also open a stream based on a file.

StreamWriter

TextWriter

Allows you to write data to a stream as lines or characters. The class can also open a stream based on a file.

The names are a little misleading because they are both used with streams, and they both convert between binary data and .NET data types. The StreamReader/Writer classes allow you to treat a stream as a series of characters arranged in lines, thus, given a stream reference in the variable stm, you can do this:

  1. StreamReader reader;
  2. reader = new StreamReader(stm, Encoding.ASCII);
  3. string str;
  4. do
  5. {
  6. str = reader.ReadLine();
  7. Console.WriteLine(str);
  8. }while (str != null);

The StreamReader class has many constructors, and the one that I have chosen takes a stream and an encoding class. The StreamReader class doesn't have to be created on a stream, indeed, you can create the object based on a file by giving the name of the file:

  1. StreamReader file;
  2. file = new StreamReader("test.txt");

This opens the file test.txt and provides access to it via the StreamReader. I will return later to the issue of opening a stream based on a file. If you want, you can allow the StreamReader class to determine the encoding of the stream. To do this you should call the constructor that takes four parameters:

  1. StreamReader file;
  2. file = new StreamReader("test.txt",
  3. Encoding.ASCII,
  4. 1024,
  5. true);

The first parameter is either an existing stream or the name of the file (as in this example), the second parameter is the default encoding that should be used if the class cannot determine the encoding to use, the third specifies the size of the buffer, and the final parameter is a boolean. If this boolean is true, the StreamReader class will read the first three bytes of the stream to determine the encoding to use. If it cannot determine the encoding it uses the default that you pass to the constructor.

The StreamWriter works similar to the StreamReader. The writer class, however, allows you to write data as lines, characters and as strings to the stream.

The BinaryReader and BinaryWriter classes are created only on streams, so if you want to base them on a file, you have to open a stream on a file using IO.File.OpenRead() or IO.File.OpenWrite() as explained later. These classes have a plethora of Read and Write methods, one for each of the .NET basic data types. There are a few things to note about these classes.

The BinaryWriter class has two methods for writing strings: the overloaded Write() method and the WriteString() method. The former writes the string as a stream of bytes according to the encoding the class is using. The WriteString() method also uses the specified encoding, but it prefixes the string's stream of bytes with the actual length of the string. Such prefixed strings are read back in via BinaryReader.ReadString().

The interesting thing about the length value it that as few bytes as possible are used to hold this size, it is stored as a type called a 7-bit encoded integer. If the length fits in 7 bits a single byte is used, if it is greater than this then the high bit on the first byte is set and a second byte is created by shifting the value by 7 bits. This is repeated with successive bytes until there are enough bytes to hold the value. This mechanism is used to make sure that the length does not become a significant portion of the size taken up by the serialized string. BinaryWriter and BinaryReader have methods to read and write 7-bit encoded integers, but they are protected and so you can use them only if you derive from these classes.

Formatters

Using the readers and writers mentioned in the last section you can read and write the various .NET data types to and from streams. However, they do not take into account perhaps the most important data type to pass through a stream: objects.

How do you serialize an object to a stream? One option would be to add a ToString() method onto your object that converts the object's state to a string by converting each field to a string and concatenating them. You can then use BinaryWriter.WriteString() to serialize the object's state to a stream. To allow you to read an object from a stream you will have to have a constructor on the object that takes a System.String parameter and in this constructor extract the values from the string to initialize the object's fields. There are two main problems to this: firstly, the constructor will have to parse the string to extract the field values and this is not a trivial task; secondly, it uses up a constructor and the ToString() method, so a naove reader could try to pass their own string to the constructor or pass an object to a method that requires a string, like Console.WriteLine(), which will trigger a call to ToString(); in both cases the your code will not be used in the way it is intended.

The solution is to use .NET serialization and a formatter object. As the name suggests serialization means that an item is serialized into a stream of bytes - just what we are looking for. It is the formatter class that does this work, but it needs some information to know what it should serialize. This information is metadata, and is controlled by the attributes [Serializable] and [NonSerialized]. The [Serializable] attribute can be applied to classes, delegates, enums and structs, it effectively sets the serializable metadata for all fields in the item that it is applied. If you decide that some fields should not be serialized (for example they correspond to temporary or intermediate values) then you can turn off the serializable metadata by applying the [NonSerialized] attribute to the field. For example:

  1. [Serializable]
  2. public class Point
  3. {
  4. private double xVal;
  5. private double yVal;
  6. [NonSerialized] private double len = 0;
  7. public Point(int x, int y)
  8. {
  9. xVal = x;
  10. yVal = y;
  11. }
  12. public double x{get{return xVal;}}
  13. public double y{get{return xVal;}}
  14. public double Length{
  15. get{
  16. if (len == 0)
  17. len = Math.Sqrt(x*x + y*y);
  18. return len;
  19. }
  20. }
  21. }

This represents a read-only class that represents a point; it has three properties, the x, and y coordinate and the length of the vector from the origin to the point. These properties are based on three fields: xVal, yVal and len. When the Length property is accessed the code checks to see if it is zero, in which case, the length is calculated and cached in the field len. Because the vector length is calculated, there is no reason to serialize it and so it is marked with the [NonSerialized] attribute. The class is used like this:

  1. Point p1 = new Point(1, 2);
  2. Point p2 = new Point(3, 4);
  3. Point p3 = new Point(5, 6);
  4. BinaryFormatter bf = new BinaryFormatter();
  5. bf.Serialize(stm, p1);
  6. bf.Serialize(stm, p2);
  7. bf.Serialize(stm, p3);
  8. str.Close();

stm represents some stream that has been opened for writing. The BinaryFormatter object reads the metadata on the fields of an object and if the serializable metadata is set, the field is serialized to the stream. The interesting point to note is that the fields can be private, and yet the BinaryFormatter class can still obtain the value of the field and serialize it.

Reading an object from a stream also involves a BinaryFormatter object:

  1. Point p4, p5, p6;
  2. p4 = (Point)bf.Deserialize(str);
  3. p5 = (Point)bf.Deserialize(str);
  4. p6 = (Point)bf.Deserialize(str);

The Deserialize() method creates an instance of the object that was serialized into the stream and initializes the fields that are not marked with [NonSerialized] with the serialized values. The fields that are marked with [NonSerialized] are given a value of zero appropriate to that data type. Again, the fields of the class can be private, and yet the BinaryFormatter is still able to write to them. Clearly this class has code which normal C# programmers are not permitted to write.

The question remains, how does Deserialize() know what class the stream holds? The reason is that the Serialize() method places in the stream the name of the assembly, the complete name of the class and its version, the names of the fields that are serialized and finally, the values of those fields. This information is determined by a class called SerializationInfo, and if you want to control this you can implement the ISerializable interface on your object. This interface has a single method called GetObjectData() which allows you to determine the information that is serialized to the stream. This interface is called during serialization. It is unusual because as an interface it requires that your class implements the items in the interface and it requires that your class also implements a specific constructor. This constructor takes a SerializationInfo reference that contains the values that were serialized, and this constructor is called during deserialization. The issue of object serialization and deserialization is very interesting, but unfortunately I don't have the space to go into further details.

As I have already mentioned, the stream that you use can be one of many types, and if it is based on a HTTP socket the stream could be used to pass the object via SOAP. To accommodate this, System.Runtime.Serialization.Formatters.Soap namespace provides the SoapFormatter class. Instead of serializing an object as a stream of bytes in a binary format, this class provides a SOAP-compliant XML representation of the object. To use this all you have to to the previous code is replace BinaryFormatter with SoapFormatter.

Formatters will serialize an entire graph of objects. By graph I mean that if the object you pass to Serialize() has fields that are references to other objects and those objects have references to other objects, then all objects will be serialized. A graph is not always as simple as this, because two objects may refer to the same object and this presents a problem when the base object is deserialized, because it should only create one instance for this shared object. This is accomplished with a class called the ObjectManager which keeps track of all objects as they are deserialized, and so if a request is made to deserialize an object that has already been deserialized, the existing instance will be used. The ObjectManager and its associated classes are flexible and configurable, and again, I will leave a complete description to another time.

Streams

Finally I come to the issue of how to obtain a stream. The IO.Stream class is abstract and the following table lists some of the more common classes derived from it:

Class

Description

FileStream

A buffered stream based on a disk file

NetworkStream

An unbuffered stream based on a socket

BufferedStream

A wrapper class that adds buffering to an existing unbuffered stream

MemoryStream

A stream based on memory

In addition to these, there are streams returned by classes in the System.Data and System.Data.SQL namespaces.

How these streams are created depend upon the class that is creating them. For example, a FileStream object is created by the static methods IO.File.OpenRead() and IO.File.OpenWrite(),

  1. StreamReader read;
  2. read = new StreamReader(File.OpenRead("sourcefile.txt"));
  3. StreamWriter write;
  4. write = new StreamWriter(File.OpenWrite("destfile.txt"));
  5. // copy one file to the other, adding line numbers
  6. int line = 0;
  7. while (true)
  8. {
  9. string str = read.ReadLine();
  10. if (str == null) break;
  11. line++;
  12. write.WriteLine("{0:D4} {1}", line, str);
  13. }
  14. read.Close();
  15. write.Close();

The read and write references are based on files and the while loop reads each line from read prefixes it with a line number and writes it to the destination file through the write reference.

A NetworkStream is returned by calling TCPClient.GetStream() on the client-side of a socket:

  1. // attach to socket 2048 on the local machine
  2. TCPClient client = new TCPClient("localhost", 2048);
  3. // get the NetworkStream and wrap it in a BinaryWriter
  4. BinaryWriter writer = new BinaryWriter(client.GetStream());

On the other hand, the developer explicitly creates a NetworkStream on the server-side of the socket by passing the underlying socket as a constructor parameter to NetworkStream:

  1. // listen on port 2048
  2. TCPListener listener = new TCPListener(2048);
  3. // Socket is returned when the client connects
  4. Socket socket = Listener.Accept();
  5. // create a stream
  6. NetWorkStream str = new NetworkStream(socket);
  7. // wrap it up in a BinaryReader
  8. BinaryReader reader = new BinaryReader(str);

One final example, the System.Net.WebResponse class represents a response from a server, once you have a WebResponse object you can ask it for a stream, and access the data of the response via the stream:

  1. WebRequest req = null;
  2. req = WebRequestFactory.Create("http://www.microsoft.com/");
  3. WebResponse resp = req.GetResponse();
  4. StreamReader reader = new StreamReader(resp.GetResponseStream());
  5. while(true)
  6. {
  7. string str;
  8. str = reader.ReadLine();
  9. if (str == null) break;
  10. Console.WriteLine(str);
  11. }

This will print out the HTML of the default page on www.microsoft.com.

And Finally...

Throughout this article you may have noticed that the stream reader and writer classes have a similarity to the static methods of the System.Console class. The reason is that Console implements three streams which are accessed through three static properties called Error, In and Out: In is a TextReader, and the other two are TextWriters. This means that you can choose at runtime where the output from your application goes, and where the input comes from, for example:

  1. TextWriter tw = null;
  2. if (bToLogFile)
  3. tw = new StreamWriter(File.OpenWrite("myapp.log"));
  4. else
  5. tw = Console.Out;
  6. tw.WriteLine("text for the current output stream");
  7. tw.Flush();

The bToLogFile can be set at runtime. The Console class has another way to set the input and output streams with methods called SetOut() (that take a TextWriter parameter) and SetIn() (that takes a TextReader parameter). Once you have called SetOut() it means that every call you make to the static Console.WriteLine() or Console.Write() will go to your specified stream. Notice that if the output stream is based on a FileStream the writes will be buffered, so you have to call either Flush() or Close() to flush the buffer to the file.

Streams and .NET的更多相关文章

  1. Java笔记——Java8特性之Lambda、方法引用和Streams

    Java8已经推出了好一段时间了,而掌握Java8的新特性也是必要的,如果要进行Spring开发,那么可以发现Spring的官网已经全部使用Java8来编写示例代码了,所以,不学就看不懂. 这里涉及三 ...

  2. salesforce 零基础学习(三十二)通过Streams和DOM方式读写XML

    有的时候我们需要对XML进行读写操作,常用的XML操作主要有Streams和DOM方式. 一.Streams方式 Streams常用到的类主要有两个XmlStreamReader 以及XmlStrea ...

  3. Released Mocked Streams for Apache Kafka

    Kafka Streams is a de­ploy­ment-ag­nos­tic stream pro­cess­ing li­brary writ­ten in Java. Even thoug ...

  4. Confluent Platform 3.0支持使用Kafka Streams实现实时的数据处理(最新版已经是3.1了,支持kafka0.10了)

    来自 Confluent 的 Confluent Platform 3.0 消息系统支持使用 Kafka Streams 实现实时的数据处理,这家公司也是在背后支撑 Apache Kafka 消息框架 ...

  5. [转]深入理解Java 8 Lambda(类库篇——Streams API,Collectors和并行)

    以下内容转自: 作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-l ...

  6. STREAMS流机制

    STREAMS流机制 基本概念 STREAMS(流)是系统V提供的构造内核设备驱动程序和网络协议包的一种通用方法,对STREAMS进行讨论的目的是为了理解系统V的终端接口,I/O多路转接中poll(轮 ...

  7. Python--Cmd窗口运行Python时提示Fatal Python error: Py_Initialize: can't initialize sys standard streams LookupError: unknown encoding: cp65001

    源地址连接: http://www.tuicool.com/articles/ryuaUze 最近,我在把一个 Python 2 的视频下载工具 youku-lixian 改写成 Python 3,并 ...

  8. Visualize real-time data streams with Gnuplot

    源文地址 (September 2008) For the last couple of years, I've been working on European Space Agency (ESA) ...

  9. [翻译]Kafka Streams简介: 让流处理变得更简单

    Introducing Kafka Streams: Stream Processing Made Simple 这是Jay Kreps在三月写的一篇文章,用来介绍Kafka Streams.当时Ka ...

  10. [Angular2 Form] Use RxJS Streams with Angular 2 Forms

    Angular 2 forms provide RxJS streams for you to work with the data and validity as it flows out of t ...

随机推荐

  1. text code

    https://github.com/Itseez/opencv/blob/master/modules/objdetect/src/erfilter.cpp

  2. shell 脚本执行日志通用模块

    目标 实现记录SHELL执行的開始时间,结束时间.执行状态,错误信息等,以函数封装日志记录的方式,脚本调用函数 源代码 通用函数脚本program_log_new.sh function init_l ...

  3. MATLAB中导入数据:importdata函数

    用load函数导入mat文件大家都会.可是今天我拿到一个数据,文件后缀名竟然是'.data'.该怎么读呢? 我仅仅好用matlab界面Workspace区域的"import data&quo ...

  4. Android M 新的运行时权限开发者需要知道的一切

    android M 的名字官方刚发布不久,最终正式版即将来临!android在不断发展,最近的更新 M 非常不同,一些主要的变化例如运行时权限将有颠覆性影响.惊讶的是android社区鲜有谈论这事儿, ...

  5. oracle用户管理实例

    oracle中的用户角色分为预定义角色和自定义角色. 角色是把常用的权限集中起来形成角色. 授权/分配角色命令 grant 权限/角色 to 用户 收回权限命令: revoke 综合案例: 创建一个用 ...

  6. RedHat7安装Tomcat

    编译安装Tomcat 下载jdk (http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.htm ...

  7. .net+easyui系列--验证框

    1.允许从 0 到 10个字符 <input id="vv" class="easyui-validatebox" data-options=" ...

  8. BootStrap入门_创建第一个例子

    一.选择合适的IDE 一般前端开发选用的都是WebStorm.Brackets等,因为本人对VS比较熟悉,索性就拿VS进行练习了,而且VS练习有些好处,就是通过nuget方式获取BootStrap可以 ...

  9. sql - 查询所有表中包含指定值

    可以直接创建sql语句: CREATE TABLE qResults (tName nvarchar(370), cname nvarchar(3630),[count] int) declare @ ...

  10. linux ssh rsa免输入密码

    A为本地主机(即用于控制其他主机的机器) ; B为远程主机(即被控制的机器Server), 假如ip为172.24.253.2 ;     在A上的命令: ssh-keygen -t rsa (连续三 ...