C#文件读写及相关操作

首先介绍一下对文件读写的一些相关操作

文件读写相关类介绍

文件读写操作涉及的类主要是:

  • MarshalByRefObject 类:允许在支持远程处理的应用程序中跨应用程序域边界访问对象;
  • BinaryReader 类:用特定的编码将基元数据类型读作二进制值。
  • BinaryWriter 类: 以二进制形式将基元类型写入流,并支持用特定的编码写入字符串。
  • Stream 类: 提供字节序列的一般视图。
  • FileStream类:公开以文件为主的 Stream,既支持同步读写操作,也支持异步读写操作。
  • MemoryStream 类:创建其支持存储区为内存的流。
  • BufferedStream 类:给另一流上的读写操作添加一个缓冲层。
  • TextReader 类:表示可读取连续字符系列的阅读器。
  • TextWriter 类:表示可以编写一个有序字符系列的编写器。
  • StreamReader 类:实现一个 TextReader,使其以一种特定的编码从字节流中读取字符。
  • StreamWriter 类:实现一个 TextWriter,使其以一种特定的编码向流中写入字符。
  • StringReader 类:实现从字符串进行读取的 TextReader。
  • StringWriter 类:实现一个用于将信息写入字符串的 TextWriter。该信息存储在基础 StringBuilder 中。

在使用它们之前最好能了解它们的继承关系,有助于作出最合适的选择。

另外还要注意一下 FileInfo 和 File 类的一些方法,如 Create,CreateText,Open 等,有时也会带来方便。这些类的内容比较繁多,更多内容还请参考MSDN。

一些常见的问题及其解决方案

问题 1:如何读写文本文件(并考虑不同的编码类型)

解决方案:

创建一个 FileStream 对象用以引用该文件。要写入文件,将 FileStream 对象封装在 StreamWriter 对象中,使用其重载了的 Write 方法;要读取文件,将 FileStream 对象封装在 StreamReader 对象中,使用其 Read 或 ReadLine 方法;

.NET Framework 允许通过 StreamWriter 和 StreamReader 类操作任何流来读写文本文件。当使用 StreamWriter 类写入数据时,调用它的 Write 方法,该方法在重载后可以支持所有常见的 C# 数据类型,包括字符串、字符、整数、浮点数以及十进制数等。但 Write 方法总会将的得到的数据转换为文本,如果希望将这些文本转换回原来的数据类型,应使用 WriteLine 方法,以确保每个值都处于单独的一行上。

字符串的表现形式取决于你使用的编码,最常见的编码类型包括下面几种:ASCII,UTF-16,UTF-7,UTF-8。

.NET Framework 在 System.Text 命名空间中为每种编码类型提供了一个类。在使用 StreamWriter 和 StreamReader 类时,可以指定需要的编码类型,或者使用默认的 UTF-8。

而在读取文本文件时,则要使用 StreamReader 类的 Read 或 ReadLine 方法。Read 方法读取单个字符或者指定个数的字符,返回类型为字符或字符数组;ReadLine 方法则返回包含整行内容的字符串;ReadToEnd 方法从当前位置读取至流的结尾。

写入文本文件的示例:

using (FileStream fs = new FileStream(fileName, FileMode.Create))
{
    // 创建一个 StreamWriter 对象,使用 UTF-8 编码格式
    using (StreamWriter writer = new StreamWriter(fs, Encoding.UTF8))
    {
        // 分别写入十进制数,字符串和字符类型的数据
        writer.WriteLine(123.45M);
        writer.WriteLine("String Data");
        writer.WriteLine('A');
    }
}

读取文本文件的示例:

// 以只读模式打开一个文本文件
using (FileStream fs = new FileStream(fileName, FileMode.Open))
{
    using (StreamReader reader = new StreamReader(fs, Encoding.UTF8))
    {
        string text = string.Empty;
        
        while(!reader.EndOfStream)
        {
            text = reader.ReadLine();
            txtMessage.Text += text + Environment.NewLine;
        }
    }
}

问题 2:如何读写二进制文件(使用强数据类型)

解决方案:

创建一个 FileStream 对象用以引用该文件。要写入文件,将 FileStream 对象封装在 BinaryWriter 对象中,使用其重载了的 Write 方法;要读取文件,将 FileStream 对象封装在 BinaryReader 对象中,使用相应数据类型的 Read 方法。

.NET Framework 允许通过 BinaryWriter 和 BinaryReader 类操作任何流来读写二进制数据。当使用 BinaryWriter 类写入数据时,调用它的 Write 方法,该方法在重载后可以支持所有常见的 C# 数据类型,包括字符串、字符、整数、浮点数以及十进制数等,然后数据会被编码为一系列字节写入文件,也可以配置该过程中的编码类型。

在使用二进制文件时,一定要特别注意其中的数据类型。当你读取数据时,一定要使用 BinaryReader 类的某种强类型的 Read 方法。例如,要读取字符串,要使用 ReadString 方法。(BinaryWriter 在写入二进制文件时总会记录字符串的长度以避免任何可能的错误)

写入文件的示例:

using (FileStream fs = new FileStream(fileName, FileMode.Create))
{
    using (BinaryWriter writer = new BinaryWriter(fs))
    {
        // 写入十进制数,字符串和字符
        writer.Write(234.56M);
        writer.Write("String");
        writer.Write('!');
    }
}

读取文件的示例:

// 以只读模式打开一个二进制文件
using (FileStream fs = new FileStream(fileName, FileMode.Open))
{
    using (StreamReader sr = new StreamReader(fs))
    {
        MessageBox.Show("全部数据:" + sr.ReadToEnd());
        
        fs.Position = 0;
        using (BinaryReader reader = new BinaryReader(fs))
        {
            // 选用合适的数据类型读取数据
            string message = reader.ReadDecimal().ToString() + Environment.NewLine;
            message += reader.ReadString() + Environment.NewLine;
            message += reader.ReadChar().ToString();
            MessageBox.Show(message);
        }
    }
}

问题 3:如何异步读取文件

解决方案:

有时你需要读取一个文件但又不希望影响程序的执行。常见的情况是读取一个存储在网络驱动器上的文件。

FileStream 提供了对异步操作的基本支持,即它的 BeginRead 和 EndRead 方法。使用这些方法,可以在 .NET Framework 线程池提供的线程中读取一个数据块,而无须直接与 System.Threading 命名空间中的线程类打交道。

采用异步方式读取文件时,可以选择每次读取数据的大小。根据情况的不同,你可能会每次读取很小的数据(比如,你要将数据逐块拷贝至另一个文件),也可能是一个相对较大的数据(比如,在程序逻辑开始之前需要一定数量的数据)。在调用 BeginRead 时指定要读取数据块的大小,同时传入一个缓冲区(buffer)以存放数据。因为 BeginRead 和 EndRead 需要访问很多相同的信息,如 FileStream,buffer,数据块大小等,因此将这些内容封装一个单独的类当中是一个好主意。

下面这个类就是一个简单的示例。AsyncProcessor 类提供了 StartProcess 方法,调用它开始读取,每次读取操作结束,OnCompletedRead 回调函数会被触发,此时可以处理数据,如果还有剩余数据,则开始一个新的读取操作。默认情况下,AsyncProcessor 类每次读取 2KB 数据。

写入文件的示例:

using (FileStream fs = new FileStream(fileName, FileMode.Create))
{
    using (BinaryWriter writer = new BinaryWriter(fs))
    {
        // 写入十进制数,字符串和字符
        writer.Write(234.56M);
        writer.Write("String");
        writer.Write('!');
    }
}
    
class AsyncProcessor
{
    private Stream inputStream;
    
    // 每次读取块的大小
    private int bufferSize = 2048;
    
    public int BufferSize
    {
        get { return bufferSize; }
        set { bufferSize = value; }
    }
    
    // 容纳接收数据的缓存
    private byte[] buffer;
    
    public AsyncProcessor(string fileName)
    {
        buffer = new byte[bufferSize];
        
        // 打开文件,指定参数为 true 以提供对异步操作的支持
        inputStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, true);
    }
    
    public void StartProcess()
    {
        // 开始异步读取文件,填充缓存区
        inputStream.BeginRead(buffer, 0, buffer.Length, OnCompletedRead, null);
    }
    
    private void OnCompletedRead(IAsyncResult asyncResult)
    {
        // 已经异步读取一个 块 ,接收数据
        int bytesRead = inputStream.EndRead(asyncResult);
        
        // 如果没有读取任何字节,则流已达文件结尾
        if (bytesRead > 0)
        {
            // 暂停以模拟对数据块的处理
            Debug.WriteLine("   异步线程:已读取一块");
            Thread.Sleep(TimeSpan.FromMilliseconds(20));
            
            // 开始读取下一块
            inputStream.BeginRead(buffer, 0, buffer.Length, OnCompletedRead, null);
        }
        else
        {
            // 结束操作
            Debug.WriteLine("   异步线程:读取文件结束");
            inputStream.Close();
        }
    }
}

使用该类时可以这么写:

// 开始在另一线程中异步读取文件
AsyncProcessor asyncIO = new AsyncProcessor("test.txt");
asyncIO.StartProcess();

// 在主程序中,做其它事情,这里简单地循环 10 秒
DateTime startTime = DateTime.Now;
while (DateTime.Now.Subtract(startTime).TotalSeconds < 10)
{
    Debug.WriteLine("主程序:正在进行");
    // 暂停线程以模拟耗时的操作
    Thread.Sleep(TimeSpan.FromMilliseconds(100));
}

Debug.WriteLine("主程序:已完成");

问题 4:如何创建临时文件

解决方案:

有时需要在特定用户的临时目录下创建一个临时文件,这要求该文件具有唯一的名称,避免与其它程序生成的临时文件相冲突。我们会有多种选择。最简单的是,在程序所在目录内使用 GUID 或时间戳加上随机值作为文件名称。但 Path 类提供的方法还是可以为你节省工作量,这就是它的静态 GetTempFileName 方法,它在当前用户的临时目录下创建一个临时文件(文件名称一定是唯一的),临时目录通常类似于这样:C:/Documents and Settings/[username]/Local Settings/Temp。

string tempFile = Path.GetTempFileName();

using (FileStream fs = new FileStream(tempFile, FileMode.Open))
{
    using (BinaryWriter writer = new BinaryWriter(fs))
    {
        // 写入数据
        writer.Write("临时文件信息");
    }
}
   
// 最后删除临时文件
File.Delete(tempFile);

问题 5:如何获得随机文件名

解决方案:

使用 Path.GetRandomFileName 方法,它与 GetTempFileName 方法的不同之处在于它仅仅返回一个字符串但不会创建文件。

问题 6:监视文件系统的变化

解决方案:

如果指定路径内的文件发生改变(比如文件被修改或创建),你希望能对此作出反应。

如果程序与其它多个程序或业务处理相关,常常需要创建一个程序,并且只有文件系统发生变化时它才处于活动状态。你可以创建一个这样的程序,让它定期区检测指定目录,此时会发现有件事情让你苦恼:检测得越频繁,就会浪费越多的系统资源;而检测得越少,那么检测到变化的时间就会越长。

这时可以使用 FileSystemWatcher 组件,指定要进行监视的目录或文件,并处理其 Created,Deleted,Renamed,Changed 事件。

要使用 FileSystemWatcher 组件,首先要创建它的一个实例,然后设置下列属性:

  • Path:指定要监视的目录;
  • Filter:指定要监视的文件类型,如“*.txt”;
  • NotifyFilter:指定要监视的变化类型;
  • FileSystemWatcher会引发四个关键的事件:Created,Deleted,Renamed,Changed。这些事件都在其 FileSystemEventArgs 参数中提供了相关文件的信息:如文件名,路径,改变类型,Renamed 事件中还可以了解到改变前的文件名和路径。如果要禁用这些事件,则将它的 EnableRaisingEvents 属性设置为 false。Created,Deleted,Renamed 三个事件比较容易处理,但 Changed 事件就得当心了,你需要设置它的 NotifyFilter 属性以指示监视那些类型的变化。否则,程序会在文件被修改时淹没在不断发生的事件中(缓存区溢出)。
// 设置相关属性
watcher.Path = appPath;
watcher.Filter = "*.txt";
watcher.IncludeSubdirectories = true;
   
// 添加事件处理函数
watcher.Created += new FileSystemEventHandler(OnChanged);
watcher.Deleted += new FileSystemEventHandler(OnChanged);
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
    
    
void OnRenamed(object sender, RenamedEventArgs e)
{
    string renamedFormat = "File: {0} 被重命名为 :{1}";
    txtChangedInfo.Text = string.Format(renamedFormat, e.OldFullPath, e.FullPath);
}

void OnChanged(object sender, FileSystemEventArgs e)
{
    // 显示通知信息
    txtChangedInfo.Text = "文件: " + e.FullPath + "发生改变" + Environment.NewLine;
    txtChangedInfo.Text += "改变类型: " + e.ChangeType.ToString();
}

问题 7:如何使用独立存储文件

解决方案:

有时你需要将数据存储在文件中,但对本地硬盘驱动器却没有必要的权限(FileIOPermission)。这时要用到 System.IO.IsolatedStorage 命名空间中的类,这些类允许你的程序在特定用户的目录下将数据写入文件而不需要直接访问硬盘驱动器的权限。

// 创建当前用户的独立存储
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForAssembly())
{
    // 创建一个文件夹
    store.CreateDirectory("MyFolder");
    
    // 创建一个独立存储文件
    using (Stream fs = new IsolatedStorageFileStream("MyFile.txt", FileMode.Create, store))
    {
        StreamWriter writer = new StreamWriter(fs);
        writer.WriteLine("Test Line!");
        writer.Flush();
    }
    
    Debug.WriteLine("当前大小:" + store.CurrentSize.ToString() + Environment.NewLine);
    Debug.WriteLine("范围:" + store.Scope.ToString() + Environment.NewLine);
    string[] files = store.GetFileNames("*.*");
    if (files.Length > 0)
    {
        Debug.WriteLine("当前文件:" + Environment.NewLine);
        foreach (string file in files)
        {
            Debug.WriteLine(file + Environment.NewLine);
        }
    }
}
下面介绍一下对文件的一些相关操作
       System.IO.File类和System.IO.FileInfo类主要提供有关文件的各种操作,在使用时需要引用System.IO命名空间。下面通过程序实例来介绍其主要属性和方法。
    (1) 文件打开方法:File.Open ()
  该方法的声明如下:
public static FileStream Open(string path,FileMode mode)
  下面的代码打开存放在c:/tempuploads目录下名称为newFile.txt文件,并在该文件中写入hello。
private void OpenFile()
{
 FileStream.TextFile=File.Open(@"c:/tempuploads/newFile.txt",FileMode.Append);
 byte [] Info = {(byte)'h',(byte)'e',(byte)'l',(byte)'l',(byte)'o'};
 TextFile.Write(Info,0,Info.Length);
 TextFile.Close();
}
   (2) 文件创建方法:File.Create()
  该方法的声明如下:
public static FileStream Create(string path;)
  下面的代码演示如何在c:/tempuploads下创建名为newFile.txt的文件。
  由于File.Create方法默认向所有用户授予对新文件的完全读/写访问权限,所以文件是用读/写访问权限打开的,必须关闭后才能由其他应用程序打开。为此,所以需要使用FileStream类的Close方法将所创建的文件关闭。
private void MakeFile()
{  
    FileStream NewText=File.Create(@"c:/tempuploads/newFile.txt");
 NewText.Close();
} 
        (3) 文件删除方法:File.Delete()
  该方法声明如下:
public static void Delete(string path);
  下面的代码演示如何删除c:/tempuploads目录下的newFile.txt文件。
private void DeleteFile()
{
 File.Delete(@"c:/tempuploads/newFile.txt");
}
   (4) 文件复制方法:File.Copy ()
  该方法声明如下:
public static void Copy(string sourceFileName,string destFileName,bool overwrite);
  下面的代码将c:/tempuploads/newFile.txt复制到c:/tempuploads/BackUp.txt。
  由于Cope方法的OverWrite参数设为true,所以如果BackUp.txt文件已存在的话,将会被复制过去的文件所覆盖。
private void CopyFile()
{
 File.Copy(@"c:/tempuploads/newFile.txt",@"c:/tempuploads/BackUp.txt",true);
}
   (5) 文件移动方法:File.Move ()
  该方法声明如下:
public static void Move(string sourceFileName,string destFileName);
  下面的代码可以将c:/tempuploads下的BackUp.txt文件移动到c盘根目录下。
  注意:
  只能在同一个逻辑盘下进行文件转移。如果试图将c盘下的文件转移到d盘,将发生错误。
private void MoveFile()
{
 File.Move(@"c:/tempuploads/BackUp.txt",@"c:/BackUp.txt");
}
    (6) 设置文件属性方法:File.SetAttributes()
  该方法声明如下:
public static void SetAttributes(string path,FileAttributes fileAttributes);
  下面的代码可以设置文件c:/tempuploads/newFile.txt的属性为只读、隐藏。
private void SetFile()
{
 File.SetAttributes(@"c:/tempuploads/newFile.txt",
 FileAttributes.ReadOnly|FileAttributes.Hidden);
}
  文件除了常用的只读和隐藏属性外,还有Archive(文件存档状态),System(系统文件),Temporary(临时文件)等。关于文件属性的详细情况请参看MSDN中FileAttributes的描述。
   (7) 判断文件是否存在的方法:File.Exist ()
  该方法声明如下:
public static bool Exists(string path);
  下面的代码判断是否存在c:/tempuploads/newFile.txt文件。若存在,先复制该文件,然后其删除,最后将复制的文件移动;若不存在,则先创建该文件,然后打开该文件并进行写入操作,最后将文件属性设为只读、隐藏。
if(File.Exists(@"c:/tempuploads/newFile.txt")) //判断文件是否存在
{
 CopyFile(); //复制文件
 DeleteFile(); //删除文件
 MoveFile(); //移动文件
}
else
{
 MakeFile(); //生成文件
 OpenFile(); //打开文件
 SetFile(); //设置文件属性
}
  此外,File类对于Text文本提供了更多的支持。
  · AppendText:将文本追加到现有文件
  · CreateText:为写入文本创建或打开新文件
  · OpenText:打开现有文本文件以进行读取
  但上述方法主要对UTF-8的编码文本进行操作,从而显得不够灵活。在这里推荐读者使用下面的代码对txt文件进行操作。
  · 对txt文件进行“读”操作,示例代码如下:
StreamReader TxtReader = new StreamReader(@"c:/tempuploads/newFile.txt",System.Text.Encoding.Default);
string FileContent;
FileContent = TxtReader.ReadEnd();
TxtReader.Close();
  · 对txt文件进行“写”操作,示例代码如下:
StreamWriter = new StreamWrite(@"c:/tempuploads/newFile.txt",System.Text.Encoding.Default);
string FileContent;
TxtWriter.Write(FileContent);
TxtWriter.Close();
   System.IO.Directory类和System.DirectoryInfo类
  主要提供关于目录的各种操作,使用时需要引用System.IO命名空间。下面通过程序实例来介绍其主要属性和方法。
   (1) 目录创建方法:Directory.CreateDirectory ()
  该方法声明如下:
public static DirectoryInfo CreateDirectory(string path);
  下面的代码演示在c:/tempuploads文件夹下创建名为NewDirectory的目录。
private void MakeDirectory()
{
 Directory.CreateDirectory(@"c:/tempuploads/NewDirectoty");
}
   (2) 目录属性设置方法:DirectoryInfo.Atttributes ()
  下面的代码设置c:/tempuploads/NewDirectory目录为只读、隐藏。与文件属性相同,目录属性也是使用FileAttributes来进行设置的。
private void SetDirectory()
{
 DirectoryInfo NewDirInfo = new DirectoryInfo(@"c:/tempuploads/NewDirectoty");
 NewDirInfo.Atttributes = FileAttributes.ReadOnly|FileAttributes.Hidden;
}
   (3) 目录删除方法:Directory.Delete ()
  该方法声明如下:
public static void Delete(string path,bool recursive);
  下面的代码可以将c:/tempuploads/BackUp目录删除。Delete方法的第二个参数为bool类型,它可以决定是否删除非空目录。如果该参数值为true,将删除整个目录,即使该目录下有文件或子目录;若为false,则仅当目录为空时才可删除。
private void DeleteDirectory()
{
 Directory.Delete(@"c:/tempuploads/BackUp",true);
}
   (4) 目录移动方法:Directory.Move ()
  该方法声明如下:
public static void Move(string sourceDirName,string destDirName);
  下面的代码将目录c:/tempuploads/NewDirectory移动到c:/tempuploads/BackUp。
private void MoveDirectory()
{
 File.Move(@"c:/tempuploads/NewDirectory",@"c:/tempuploads/BackUp");
}
   (5) 获取当前目录下的所有子目录方法:Directory.GetDirectories()
  该方法声明如下:
public static string[] GetDirectories(string path;);
  下面的代码读出c:/tempuploads/目录下的所有子目录,并将其存储到字符串数组中。
private void GetDirectory()
{
 string [] Directorys;
 Directorys = Directory. GetDirectories (@"c:/tempuploads");
}
   (6) 获取当前目录下的所有文件方法:Directory.GetFiles()
  该方法声明如下:
public static string[] GetFiles(string path;);
  下面的代码读出c:/tempuploads/目录下的所有文件,并将其存储到字符串数组中。
private void GetFile()
{
 string [] Files;
 Files = Directory. GetFiles (@"c:/tempuploads",);
}
   (7) 判断目录是否存在方法:Directory.Exist()
  该方法声明如下:
public static bool Exists(
 string path;
);
  下面的代码判断是否存在c:/tempuploads/NewDirectory目录。若存在,先获取该目录下的子目录和文件,然后其移动,最后将移动后的目录删除。若不存在,则先创建该目录,然后将目录属性设为只读、隐藏
if(File.Exists(@"c:/tempuploads/NewDirectory")) //判断目录是否存在
{
 GetDirectory(); //获取子目录
 GetFile(); //获取文件
 MoveDirectory(); //移动目录
 DeleteDirectory(); //删除目录
}
else
{
 MakeDirectory(); //生成目录
 SetDirectory(); //设置目录属性
}
  注意:
  路径有3种方式,当前目录下的相对路径、当前工作盘的相对路径、绝对路径。以C:/Tmp/Book为例(假定当前工作目录为C:/Tmp)。“Book”,“/Tmp/Book”,“C:/Tmp/Book”都表示C:/Tmp/Book。
  另外,在C#中 “/”是特殊字符,要表示它的话需要使用“//”。由于这种写法不方便,C#语言提供了@对其简化。只要在字符串前加上@即可直接使用“/”。所以上面的路径在C#中应该表示为“Book”,@“/Tmp/Book”,@“C:/Tmp/Book”。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值