zoukankan      html  css  js  c++  java
  • 文件和流(使用流读写文件)

           .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 控件)。两者都代表 <input type='file'> HTML 标签。唯一真正的差别是 FileUpload 控件自动设置表单的编码,把它设置为 multipart/form 数据。如果你使用 HtmlInputFile 控件就必须手动设置 <form> 标签的这个特性,如果未设置,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 文件中 <httpRuntime> 设置的 maxRequestLength 特性。这个设置以字节为单位:<httpRuntime maxRequestLength="8192" > 即 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 + "<br />");
                    }
                } 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;
        }
    }
    image

    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);
  • 相关阅读:
    MyBatis 缓存机制
    MyBatis 动态SQL
    SpringMVC的简介与使用
    捕获组和前后查找
    正则表达式:( ) 小括号、[ ] 中括号、{ } 大括号的区别
    343.整数拆分
    74. 搜索二维矩阵
    数的划分
    213.打家劫舍||
    整数划分为k份
  • 原文地址:https://www.cnblogs.com/SkySoot/p/2647930.html
Copyright © 2011-2022 走看看