textfilestream_文件和流(使用流读写文件)

.NET Framework 在框架的多个领域里使用了流模型。流是允许你用相似的方式(作为顺序字节流)对待不同数据源的一种抽象。所有 .NET 流类从 System.IO.Stream 类继承。

流可以代表内存缓冲器中的数据、从网络连接获得的数据、从文件获得的或要写入文件的数据。

下面这段代码演示了如何创建一个新文件并用 FileStream 写入一个字节数组:

FileStream fileStream = null;

try

{

fileStream = new FileStream(filename, FileMode.Create);

fileStream.Write(bytes, 0, bytes.Length - 1);

}

finally

{

if (fileStream!=null)

{

fileStream.Close();

}

}

这段代码演示了如何打开一个 FileStream 并把它的内容读入字节数组:

FileStream fileStream = null;

try

{

fileStream = new FileStream(filename, FileMode.Open);

byte[] dataArray = new byte[fileStream.Length];

for (int i = 0; i < fileStream.Length; i++)

{

dataArray[i] = (byte)fileStream.ReadByte();

}

}

finally

{

if (fileStream!=null)

{

fileStream.Close();

}

}

就其本身而言,流不太有用,因为它们完全以单个字节或字节数组的形式工作。

一定要记得关闭流,它会释放文件句柄并允许其他人访问文件。此外,因为 FileStream 类使可释放的,所以建议在 using 语句块中使用,这就保证了块结束时 FileStream 立即被关闭。

FileMode 枚举值:

Append

如果文件存在,就打开文件并找到文件尾,否则创建一个新文件

Create

指定由操作系统创建一个新文件,如果文件存在,就覆盖它

CreateNew

指定由操作系统创建一个新文件,如果文件存在,就抛出一个 IOException 异常

Open

指定由操作系统打开一个现有的文件

OpenOrCreate

如果文件已存在,就由操作系统打开它,否则,创建一个新文件

Truncate

指定由操作系统打开一个现有的文件,打开后,文件被截断至 0 字节

文本文件

你可以用 System.IO 命名空间中的 StreamWriter 和 StreamReader 类读写文件的内容。创建这些类时,只需要把底层的流作为构造函数的参数传入:

FileStream fileStream = new FileStream(@"c:\myfile.txt", FileMode.Create);

StreamWriter w = new StreamWriter(fileStream);

你还可以使用 File 类和 FileInfo 类的静态方法,如 CreateText()或 OpenText()得到一个 StreamWriter 或 StreamReader 对象:

StreamWriter w = File.CreateText(@"c:\myfile.txt");

这段代码和前面的示例等效。

.NET 在 System.Text 命名空间里为每种编码方式提供了一个类。使用 StreamWriter 和 StreamReader 时,可以在构造函数参数中指定要使用的编码,或者直接使用默认的 UTF-8 编码:

FileStream fileStream = new FileStream(@"c:\myfile.txt", FileMode.Create);

StreamWriter w = new StreamWriter(fileStream, System.Text.Encoding.ASCII);

结束文件处理时,必须保证把它关闭。否则,更新可能不会正确写到磁盘上,文件锁定不能被打开。在任意时刻都可以调用 Flush()确保所有的数据都写到了磁盘上,因为 StreamWriter 为了优化性能会在内存中缓存你的数据。

提示:

还可以用 ReadToEnd()方法读取整个文件的内容,它返回一个字符串。File 类还有一些快捷方法,如静态方法 ReadAllText()和 ReadAllBytes(),但它们只适用于小型文件。大型文件不该一次读入内存,而是应该使用 FileStream 一次读取一部分内容来减轻内存负载。

二进制文件

二进制数据更有效的利用了空间,但创建的文件不可读(基本读不懂)。要打开用二进制写的文件,需要创建一个新的 BinaryWriter :

// BinaryWriter 的构造函数接受一个流作为参数

// 可以手工创建,也可以用 File 类的静态方法获得

BinaryWriter w = new BinaryWriter(File.OpenWrite(@"c:\binaryfile.bin"));

.NET 关注流对象,而不是数据源或数据目标。也就是说,你可以用相同的代码把二进制数据写入任意类型的流,无论他是一个文件还是其他存储介质。

遗憾的是,二进制流在读取数据时,必须知道要获取的数据类型:

BinaryReader r = new BinaryReader(File.OpenRead(@"c:\binaryfile.bin"));

string str = r.ReadString();

int integer = r.ReadInt32();

上传文件

ASP.NET 有两个控件可以让用户把文件上传到 Web 服务器。服务器接收到上传文件的数据后,你的应用程序就可以确定是查看、忽略还是保存到后端数据库或者 Web 服务器的文件系统中。

允许上传的控件是 HtmlInputFile(HMTL 服务器控件)和 FileUpload(ASP.NET Web 控件)。两者都代表 HTML 标签。唯一真正的差别是 FileUpload 控件自动设置表单的编码,把它设置为 multipart/form 数据。如果你使用 HtmlInputFile 控件就必须手动设置

标签的这个特性,如果未设置,HtmlInputFile 控件就不能工作。

通常会在页面上添加一个 Button 控件来回送页面,看下面的示例:

protected void btnUpload_Click(object sender, EventArgs e)

{

if (Uploader.PostedFile.ContentLength != 0)

{

try

{

if (Uploader.PostedFile.ContentLength > 1048576)

{

lblStatus.Text = "Too large. This file is not allowed.";

}

else

{

string destDir = Server.MapPath("~/Upload");

string fileName = Path.GetFileName(Uploader.PostedFile.FileName);

string destPath = Path.Combine(destDir, fileName);

Uploader.PostedFile.SaveAs(destPath);

lblStatus.Text = "Thank you for submitting your file.";

}

}

catch (Exception err)

{

lblStatus.Text = err.Message;

}

}

}

除了把直接上传的文件保存到磁盘外,还可以通过流模型与其交互。需要借助 FileUpload.PostedFile.InputStream 属性获得对数据的访问:

// 假设这个文件是基于文本的

StreamReader r = new StreamReader(Uploader.PostedFile.InputStream);

lblStatus.Text = r.ReadToEnd();

r.Close();

提示:

默认情况下,允许上传的最大文件是 4MB。如果试图上传一个更大的文件,会得到一个运行时错误。可以修改 web.config 文件中 设置的 maxRequestLength 特性。这个设置以字节为单位: 即 8MB。

使文件对多用户安全

虽然很容易就可以创建一个唯一的文件名,但如果不得不在多个不同的请求间访问同一个文件,会发生什么呢?

一个办法是用共享方式打开文件,这样将会允许多个进程同时访问同一个文件。要使用这一技术,你必须使用一个接收 4 个参数的 FileStream 构造函数,它允许你选择 FileMode:

FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);

这条语句允许多个用户同时打开文件来读。不过,没有人能更新该文件。可以指定不同的 FileAccess 值让多个用户以读-写模式打开文件。此时,当你写文件时,Windows 会动态锁定文件的一小部分(或者你可以用 FileStream.Lock()方法锁定文件某一字节范围内的部分),如果两个用户试图同时写锁定的部分,会产生一个异常。Web 应用程序有高度并发性的需求,所有不推荐使用这项技术,而且它的实现非常困难,它还迫使你使用低层次的字节偏移计算,这很容易产生细小而扰人的错误。

提示:

另一项技术在多用户需要访问同一数据时非常有效,尤其是数据被频繁使用且不是特别大的时候,就是把数据加载到缓存。这样,多个用户可以毫无顾忌的同时访问数据,如果另一个进程负责创建或定期更新文件,在文件变更的时候可以使用文件依赖来使缓存失效。

那么多个用户必须同时更新文件,解决方案是什么呢?

办法一:为每个请求创建一个单独的用户特定的文件

办法二:把文件绑定到另一个对象并使用锁定。

1. 创建唯一的文件名

为避免冲突,可以为每个用户创建一个目录或者给文件名添加一些信息,如时间戳、GUID(全球唯一标识符)或者随机数。

private string GetFileName()

{

string fileName = "user." + Guid.NewGuid().ToString();

// 获取当前正在执行的服务器应用程序的根目录的物理文件系统路径。

return Path.Combine(Request.PhysicalApplicationPath, fileName);

}

注解:

GUID 是一个 128 位整数。GUID 对程序非常有用,因为它们从统计学的角度来说是唯一的,因此广泛运用于唯一标识的队列任务、用户会话及其他动态信息。相对数字序列,它们还有不易猜测的优点。GUID 通常用一组小写的十六进制数字字符串表示。

使用 GetFileName()就可以创建一个更为安全的日志程序,在本示例中,所有日志通过调用 Log()方法来记录:

private void Log(string message)

{

FileMode mode;

if (ViewState["LogFile"] == null)

{

ViewState["LogFile"] = GetFileName();

mode = FileMode.Create;

}

else

{

mode = FileMode.Append;

}

string fileName = ViewState["LogFile"].ToString();

using (FileStream fs = new FileStream(fileName, mode))

{

StreamWriter w = new StreamWriter(fs);

w.WriteLine(DateTime.Now);

w.WriteLine(message);

w.WriteLine();

w.Close();

}

}

每次加载页面时都会记录一条日志信息:

protected void Page_Load(object sender, EventArgs e)

{

if (Page.IsPostBack)

{

Log("Page posted back.");

}

else

{

Log("Page loaded for the first time.");

}

}

最后是两个按钮事件,允许删除日志文件或者显示它的内容:

protected void btnRead_Click(object sender, EventArgs e)

{

if (ViewState["LogFile"] != null)

{

StringBuilder log = new StringBuilder();

string fileName = ViewState["LogFile"].ToString();

using (FileStream fs = new FileStream(fileName, FileMode.Open))

{

StreamReader r = new StreamReader(fs);

string line;

do

{

line = r.ReadLine();

if (line != null)

{

log.Append(line + "
");

}

} while (line != null);

r.Close();

}

lblInfo.Text = log.ToString();

}

else

{

lblInfo.Text = "There is no log file";

}

}

protected void btnDelete_Click(object sender, EventArgs e)

{

if (ViewState["LogFile"] != null)

{

File.Delete(ViewState["LogFile"].ToString());

ViewState["LogFile"] = null;

}

}

58449691_1.png

2. 锁定文件访问对象

有些情况你却是需要响应多个用户活动而更新同一个文件。一个办法是使用锁。基本的技术就是为所有获取数据的任务创建一个单独的类。一旦定义了这个类,就可以为该类创建一个全局的实例并把它加入到 Application 集合。现在,可以用 C# 的 lock 语句来确保每次只有一个线程可以访问这个对象。

例如,假设你设了如下的 Logger 类:

public class Logger

{

public void LogMessage()

{

lock (this)

{

// Open file and update it.

}

}

}

Logger 对象在访问日志文件,创建临界区之前将自身锁定,这就保证了每次只能有一个线程可以执行 LogMessage()代码,从而消除了文件的冲突。

不过,要让这一方式起效,你必须保证所有的类都使用 Logger 对象的同一个实例。有好几个选择:

响应 global.asax 的 HttpApplication.Start 事件创建一个 Logger 类实例并保存到 Application 集合中。

在 global.asax 中添加下述代码来通过一个静态变量公开一个 Logger 实例。

private static Logger log = new Logger();

public Logger Log

{

get { return log; }

}

现在,任何使用 Logger 调用 LogMessage()的页面都会得到一个排它的访问:

Application.Log.LogMessage(myMessage);

要记住的是,这种方式只是对文件系统先天局限性的一种拙劣补偿,它不会允许你管理更加复杂的任务。如让每个用户同时读写同一文件的片段,此外文件被某个客户端锁住时,其他请求不得不等待。这肯定会降低应用程序的性能。这项技术仅适用于小型 Web 应用程序。也正是基于这样的原因,ASP.NET 应用程序几乎从不使用基于文件的日志,相反,它们把日志写在 Windows 事件日志或数据库里。

压缩

.NET 支持在任何流中压缩数据,这一技巧允许你压缩写入任意文件的数据。这一支持来自 System.IO.Compression 命名空间的 GZipStream 和 DeflateStream 类。这两个类都提供相似的高效无损压缩算法。

要使用压缩,必须把真实的流包装到某个压缩流中。例如可以包装一个 FileStream(写入磁盘时将其压缩)或 MemoryStream(为了压缩内存中的数据)。使用 MemoryStream 时,可以在数据存入数据库的某个二进制字段前或者在把数据传送给 Web 服务前对其进行压缩。

假设你希望压缩保存到文件的数据:

FileStream fs = new FileStream(fileName, FileMode.Create);

// CompressionMode.Compress 枚举指定是压缩还是解压

GZipStream compressStream = new GZipStream(fs, CompressionMode.Compress);

// 写入真是的数据时,要使用压缩流的 Write(),而不是 FileStream 的 Write()

// 如果要使用更高层次的写入器,可以提供一个压缩流代替 FileStream

StreamWriter w = new StreamWriter(compressStream);

w.WriteLine();

w.Flush();

fs.Close();

读文件很简单。差别在于枚举值的选择:

FileStream fs = new FileStream(fileName, FileMode.Open);

GZipStream decompressStream = new GZipStream(fs, CompressionMode.Decompress);

StreamReader r = new StreamReader(decompressStream);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值