流动的网页
在控制台应用程序中,常用的输入输出语句为WiretLine 和ReadLine。为什么可以使用这些方法从键盘输入内容,又为什么内容会输出到屏幕上去呢?
下面我们看一下Console对象的常用成员
成员 | 说明 |
In | System.IO.TextReader 类型,表示控制台的输入了 |
Out | System.IO.TextWriter 类型,表示控制台的输出流 |
Error | System.IO.TextWriter 类型,表示控制台的错误输出流 |
SetIn | 重新设置控制台的输入流 |
SetOut | 重新设置控制台的输出流 |
SetError | 重新设置控制台的错误输出流 |
SetIn方法的定义:
public static void SetIn (
TextReader newIn
)
SetOut方法的定义:
public static void SetOut (
TextWriter newOut
)
对于控制台来所,ReadLine 和 WriteLine 方法是通过控制台的 In 和 Out 属性设置的流进行输入和输出的。这两个属性的默认值是操作系统在启动控制台应用程序时设置的,分别指向标准输入流和标准输出流,默认就是常见的键盘和显示器。在程序中,也可以通过SetIn 和 SetOut 方法进行重新设置,我们称为输入输出的重定向。
当一个程序启动的时候,操作系统将直接提供三个流供这个程序完成基本的输出和输入,这三个流分别是标准输入流,标准输出流和标准错误流。
流是对输入输出的高度抽象,通常我们将用来输入信息的流称为源,源表示信息的来源,比如,键盘、文件输入、网络输入等;将用来输出信息的流程为汇,汇表示信息流向的目标,比如:显示器、打印机、文件输出、网络输出等。
在流中,输入输出的信息排列成一个信息的序列。信息的基本单位在不同的流中是不同的,基本单位是字节的流称为字节流,基本单位是字符的流程为字符流,基本单位是Xml节点的流就是Xml流。
字节流
在信息处理中,最基本的单位是二进制的位(bit),但是这个单位太小了,所以常常使用8位组(Octec)或者字节(byte)作为基本单位。从道理是讲,一个字节不一定是8位,但是,在现代的计算机系统中,事实标准就是使用8位来表示一个字节。所以,在后面的介绍中,我们也就沿用大家所熟悉的“字节”这个概念。
在基于流的输入输出中,最基本的就是基于字节的输入和输出。
在.NET中,System.IO 命名空间中的Stream定义了基于字节的输入和输出的基类,这是一个抽象类,定义如下:
[SerializableAttribute]
[ComVisibleAttribute(true)]
public abstract class Stream : MarshalByRefObject,
IDisposable
从流中读取一个字节的方法为ReadByte,定义如下:
public virtual int ReadByte()
从流中读取一个字节,并将流内的位置向前推进一个字节,如果已到达流的末尾,则返回 -1。
ReadByte一次只呢个读取一个字节。我们经常使用的输入方法为Read,Read方法可以一次读取多个字节,定义如下:public abstract int Read(
byte[] buffer,
int offset,
int count
)
Read方法从流中以字节块的方式读取信息,读取的内容将保存到buffer指向的字节数组中保存在数组中从offset下表开始的位置,count表示从流中最多希望读取的字节数量,方法的返回值为实际从流中读取的字节数量。如果返回值为0,则表示流中已经没有内容可以读了。
主要的输出方法为Write,方法的定义如下:
public abstract void Write(
byte[] buffer,
int offset,
int count
)
表示将一组字节数据写入流总,数据保存在buffer指向的字节数组中,从offset指定的下表开始,count表示从offset开始的字节数量,这些信息将写入到流中。
在.NET下,有多个派生类派生自 Stream,如下说是:
System.IO.Stream
Microsoft.JScript.COMCharStream
System.Data.OracleClient.OracleBFile
System.Data.OracleClient.OracleLob
System.Data.SqlTypes.SqlFileStream
System.IO.BufferedStream
System.IO.Compression.DeflateStream
System.IO.Compression.GZipStream
System.IO.FileStream
System.IO.MemoryStream
System.IO.Pipes.PipeStream
System.IO.UnmanagedMemoryStream
System.Net.Security.AuthenticatedStream
System.Net.Sockets.NetworkStream
System.Printing.PrintQueueStream
System.Security.Cryptography.CryptoStream
对于常用的磁盘文件来说,我们可以使用FileStream进行操作。例如,下面的例子是一文件流打开一个文件,然后在控制台输出其内容。
System.IO.FileStream stream = new System.IO.FileStream("../../phone.txt", System.IO.FileMode.Open);
byte[] buffer = new byte[4096];
int length = 0;
while ((length = stream.Read(buffer, 0, 4096)) > 0)
{
Console.WriteLine(BitConverter.ToString(buffer, 0, length));
}
stream.Close();
Console.ReadLine();
还有一些类内部封装了针对字节流的操作,以便简化编程的负担。例如,System.IO.File类就提供了许多静态方法,允许我们直接以字节的方式对文件进行读写。
ReadAllBytes方法提供了直接读取文件的操作,方法的定义如下:
public static byte[] ReadAllBytes (
string path
)
这样的话,上面的例子就可以更加简单的如下完成了:
byte[] buffer= System.IO.File.ReadAllBytes("../../phone.txt");
Console.WriteLine(BitConverter.ToString(buffer));
Console.ReadLine();
字符编码
对于网页来说,我们通常并不需要处理字节那么底层,我们需要的是输出字符串组成的网页。通过建立人使用的字符与计算机处理的数字之间的对应关系,我们创建了字符编码表。通过字符编码表,我们可以查出字符对应的数字,也可以反向查出数字对应的字符。
现在在世界上,同时存在着许多字符编码表,常见的英文字符表为ASCII编码表,对于简体中文来说,GB2312字符表定义了常用汉字的编码,Unicode编码定义了一个包含目前人类使用的所以字符的巨大编码表。
在 .NET 环境下,使用定义在 System.Text 命名空间中的 Encoding类来表示不同的编码。
[SerializableAttribute]
[ComVisibleAttribute(true)]
public abstract class Encoding : ICloneable
这个抽象类提供了GetEncoding静态方法来获取特定的编码表对象。
public static Encoding GetEncoding(
int codepage
)
public static Encoding GetEncoding(
string name
)
using System;
using System.Text;
public class SamplesEncoding {
public static void Main() {
// Get a UTF-32 encoding by codepage.
Encoding e1 = Encoding.GetEncoding( 12000 );
// Get a UTF-32 encoding by name.
Encoding e2 = Encoding.GetEncoding( "utf-32" );
// Check their equality.
Console.WriteLine( "e1 equals e2? {0}", e1.Equals( e2 ) );
}
}
/*
This code produces the following output.
e1 equals e2? True
*/
其中name为编码方法的名称,codepage为编码方案的编号,也成为代码页(Code Page)。特殊的编号 0 用来特质当前系统的默认编码。对于简体中文来说,代码也为936,编码的名称为:GB2312
代码页容易被计算机处理,但是不容易被人记忆和使用。通常我们使用编码的名称,编码的名称来自IANA,通过Encoding对象的WebName属性可以取得这个名称。
public virtual string WebName { get; }
static void Main(string[] args)
{
Encoding encoding = System.Text.Encoding.GetEncoding(0);
Console.WriteLine(encoding.WebName);// gb2312
Console.ReadLine();
}
取得字符编码之后,通过字符编码,我们可以将字符编码为对应的数字,以自己方式进行输入和输出。通过编码对象的GetString方法可以将字节格式的数字重新解码为字符,相对应地,GetBytes方法允许我们将字符编码为字节格式的数字。通过这两个方法,我们就可以在程序中进行字符编码和转换操作。
byte[] buffer =System.IO.File.ReadAllBytes("../../phone.txt"); Encoding encoding = System.Text.Encoding.GetEncoding(0);
string s=encoding.GetString(buffer); Console.WriteLine(s); byte[] gbbytes = Encoding.UTF8.GetBytes(s); System.IO.File.WriteAllBytes("../../aa.txt", gbbytes); Console.ReadLine();
字符流
为了便于以流的方式操作字符,在 .NET中也提供了字符流。字符流对象内部直接包含了一个字符编码对象,使得我们可以直接使用字符的方式进行输入和输出,更加简便地完成字符与数字之间的转换工作。对于字符流来说,没有既支持输入又支持输出的字符流。基于字符的流被分为两大类:字符输入流和字符输出流。
字符输出流
需要注意的是,字节和字符是完全不同的两种数据类型,可以看到,字符输出流并不从字节流中派生,虽然它内部使用了字节流。
字符输出流提供了一个抽象基类,定义如下:
[SerializableAttribute] [ComVisibleAttribute(true)] public abstract class TextWriter : MarshalByRefObject, IDisposable
其中包含了一个字符编码的属性 Encoding。这个属性是只读的,只能在构造的时候进行设置。
其中常用的方法为输出一个字符。public abstract Encoding Encoding { get; }
WriteLine 已重载。 写入重载参数指定的某些数据,后跟行结束符。
WriteLine表示输出一行字符,format为格式化串,arg为格式化船中所使用的参数。在输出字符的过程中,字符输出流通过编码将字符编码为相应的数字,然后通过字节流进行输出。
它常用的两个派生类如下:
System.IO.StreamWriter //基于文件的字符输出
System.IO.StringWriter //基于内存的字符输出
字符输入流
字符输入流同样也有一个抽象基类,定义如下:
public abstract class TextReader : MarshalByRefObject , IDisposable
对于字符输入流来说,并没有一个可以直接访问到的Enconding属性,但是,在字符流输入流的内部也存在一个编码对象,这个编码对象只能在创建字符输入流对象实例的时候传递进去。
其中常用的方法如下:
public virtual string ReadLine()
表示从流中读取一个字符。
它的两个主要派生类如下:
System.IO.StreamReader //从文件中读取字符
System.IO.StringReader //从字符串中读取字符
使用字符流,我们可以更加简单地完成上面的字符编码转换操作。
控制台的流就是文本流,输入输出为TextReader类型,输出流为TextWriter类型。
string s = "aaaaaaaaaaaaaaaaaaaaaaaaa"; System.IO.TextWriter writer = Console.Out; writer.WriteLine(s);
回应对象中的流
所谓网站应用程序,就是指程序的输入和输出来自客户端的浏览器。如果我们能够取得来自于浏览器的输入流和指向浏览器的输出流,就可以将网站应用程序类似于普通的控制台程序进行处理,通过向字符串输出流中写入HTML的文本内容来使程序生成网页。
在网站中,HttpContext对象的Response属性指向表示输出的HttpResponse类型的对象实例,HttpResponse中的OutputStream就是指向浏览器的字节输出流。
多数是情况下,我们需要向浏览器输出字符,HttpResponse的Output属性表示指向浏览器的字符流,这个流所使用的字符编码由HttpResponse对象的ContentEncoding属性指定。这个属性的定义如下:
public encoding ContentEncoding {get; set;}
回应对象使用的文本输出流的实际类型定义在System.Web命名空间下,类型为HttpWriter,它使得通过HttpResponse输出网页变得更加方便。
public sealed class HttpWriter : TextWriter
所以,如果希望通过HttpResponse 对象数一段HTML,那么可以如下完成:
protected void Page_Load(object sender, EventArgs e) { string div = "<div><input type='button' value='input'/> </div>"; System.IO.TextWriter writer = Response.Output; writer.Write(div); }
专门输出HTML哦字符流
HTML是一种标记语言,这种语言定义了大量的标记,这些标记在 .NET 中以枚举的方式进行了定义,这个定义在命名空间System.Web.UI中。
public enum HtmlTextWriterTag
与之配合,为了能够更加方便地输出HTML字符,在System.Web.UI命名空间下,还定义了一系列专门输出HTML字符的字符流。这些字符流的基类为TextWriter,定义如下:
public clas HtmlTextWriter : TextWriter
它有三个派生类。
System.Web.UI.Html32TextWriter
System.Web.UI.MobileControls.Adapters.MultiPartWriter
System.Web.UI.XhtmlTextWriter
System.Web.UI.MobileControls 命名空间用于移动设备,对于MultiPartWriter来说,定义在命名空间System.Web.UI.MobileControls.Adapters 下的派生类 MobileTextWriter 用来生产移动设备使用的HTML 。
Html32TextWriter 用来生成 HTML 3 .2 标准的网页。
对于普通的网页来说,我们主要使用XhtmlTextWriter生成符合XHTML规范的网页。
注意一下XhtmlTextWriter的构造函数,可以看到它的内部使用字符输出流来完成实际的字符输出。
public XhtmlTextWriter( TextWriter writer )
对于上一节中的HTML,可以使用XhtmlTextWriter如下生成:
有一个地方比较有趣,XhtmlTextWriter的WriterFullBeginTag将生成一个包含 > 的开始标记,而WriterBeginTag则不会生产,这样有助于我们为这个开始标记继续生成一些属性,但是在属性生成之后,必须手工增加这个结束 > 符号,这个符号定义在HtmlTextWriter 的TagRightChar属性中。System.IO.TextWriter writer=Response.Output; System.Web.UI.XhtmlTextWriter inputtag = new XhtmlTextWriter(writer); inputtag.WriteFullBeginTag("div"); inputtag.WriteBeginTag("input"); inputtag.WriteAttribute("type", "button"); inputtag.WriteAttribute("value","input"); inputtag.Write(HtmlTextWriter.TagRightChar); inputtag.WriteEndTag("input"); inputtag.WriteEndTag("div");
配合HtmlTextWriterTag枚举,HtmlTextWriter还提供了RenderXXX方法,这样我们不可能由于拼写的问题写出不正确的标记名了。上面的代码还可以如下实现:
protected void Page_Load(object sender, EventArgs e) { System.IO.TextWriter writer=Response.Output; System.Web.UI.XhtmlTextWriter inputtag = new XhtmlTextWriter(writer); inputtag.RenderBeginTag(HtmlTextWriterTag.Div); inputtag.AddAttribute(HtmlTextWriterAttribute.Type, "button"); inputtag.AddAttribute(HtmlTextWriterAttribute.Value, "input"); inputtag.RenderBeginTag(HtmlTextWriterTag.Input); inputtag.RenderEndTag(); }
在使用RenderBeginTag的时候,需要先定义属性,然后再生成标记。