C#学习笔记——文件系统数据

来源:互联网 发布:苏州矩阵光电 编辑:程序博客网 时间:2024/04/30 20:53

一、前言

      C#经常要对数据进行读写操作,而进行这些操作的基础是在流上进行的,即C#通过流来实现对数据的读取。C#中常用的流有两种:

      1、输出流:当向某些外部目标写入数据时,就要用到输入流。

       2、输入流:用于将数据读入程序可以访问的内存或变量中。

      System.IO名称空间包含了用于在文件中读写数据的类,这里仅介绍用于文件输入输出的主要类,如下:

            类                     说明                                          File静态实用类,提供许多静态方法,用于移动、复制和删除文件。Directory静态实用类,提供许多静态方法,用于移动、复制和删除目录。Path实用类,用于处理路径名称。FileInfo表示磁盘上的物理文件,该类包含处理此文件的方法。要完成对文件的读写工作,就必须创建Stream对象。DirectoryInfo表示磁盘上的物理目录,该类包含处理此目录的方法。FileSystemInfo用作FileInfo和DirectoryInfo的基类,可以使用多态性同时处理文件和目录。FileStream表示可写或可读,或二者均可的文件。可以同步或异步地读写此文件。StreamReader从流中读取字符数据,可以使用FileStream作为基类创建。StreamWriter向流写入字符数据,可以使用FileStream作为基类创建。FileSystemWatcher FileSystemWatcher是本章要介绍的最复杂类。它用于监控文件和目录,提供了这些文件和目录发生变化时
应用程序可以捕获的事件。表1-1 用于访问文件系统的类注意:System.IO.Compression名称空间,它允许读写压缩文件。主要看一下两个流类:

DeflateStream——表示在写入时自动压缩数据或读取时自动解压缩的流,使用Deflate算法来实现压缩。

GZipStream——表示在写入时自动压缩数据或者在读取时自动解压缩的流,使用GZIP算法来实现压缩。

二、FiIe类和Directory类

File和Directory实用类提供了许多的静态方法,用于处理文件和目录,这些方法可以移动文件、查询和更新特性,还可以创建FileStream对象。如下为File类最常用的静态类方法:

  表1-2 File类的静态方法     方 法                   说明                                 Copy()将文件从源位置复制到目标位置。Create()在指定的路径上创建文件。Delete()删除文件。Open()返回指定路径上的FileStream对象。Move()将指定的文件移到新位置。可在新位置为文件指定不同的名称。

Directory类最常用的静态类方法:
表1-3 Directory类的静态方法                          方法                                                  说明                                                          CreateDirectory()创建具有指定路径的目录。Delete()删除指定的目录及其中的所有文件。GetDirectories()返回表示指定目录下的目录名的string对象数组。EnumerateDirectories() 与GetDirectories()类似,但返回目录名的IEnumerable<string>集合。GetFiles()返回在指定目录中的文件名的string对象数组。EnumerateFiles()与GetFiles()类似,但返回文件名的IEnumerable<string>集合。GetFileSystemEntries()返回指定目录中的文件和目录名的string对象数组。EnumerateFileSystemEntries() 与GetFilesSystemEntries()类似,但返回文件和目录名的IEnumerable<string>集合。Move()将指定目录移到新位置,可在新位置为文件夹指定一个新名称。 注意:其中3个EnumerateXxx()方法是在.NET4中引入的,在存在大量文件或目录时,其性能比对应的GetXxx()方法好。

FileInfo类:它不是静态类,没有静态方法,只有在实例化后才能使用,FileInfo对象表示磁盘或网络位置上的文件,提供文件路径就可以创建一个FileInfo对象:

FileInfo aFile = new FileInfo(@“C:\Log.txt”);注:@表示该字符串按照字面意思解释,而不解释为转义字符

FileInfo类提供了许多方法类似于File类,但由于File是静态方法,它需要一个字符串参数为每个方法调用指定的文件位置,下面代码完成相同工作:

                   FileInfo aFile = new FileInfo(“Data.txt”);                                        if(File.Exists(“Data.txt”)) Console.WriteLine("File Exists");

                   if(aFile.Exists) Console,WriteLine("File Exists");

FileInfo和File的使用场景:

1、如果仅进行单一方法调用,则可以使用静态File类,因为不用实例化,所以调用要快些。

2、如果应用程序在文件上执行几种操作,则实例化FileInfo对象并使用其方法就更好一些。

下面介绍FileSystem的属性

表1-4 FileSystem的属性                           属性                                                 说明                    Attributes 使用FileAttributes枚举,获取或者设置当前文件或目录的特性。CreationTime,
CreationTimeUtc获取当前文件的创建日期和时间,可以使用UTC和非UTC版本。Extension提取文件的扩展名。这个属性是只读的。Exists确定文件是否存在,这是一个只读的抽象属性,在FileInfo和DirectoryInfo中进行了重写。FullName检索文件的完整路径,这个属性是只读的LastAccessTime,
LastAccessTimeUtc获取或设置上次访问当前文件的日期和时间,可以使用UTC和非UTC版本LastWriteTime,
LastWriteTimeUtc获取或设置上次写入当前文件的日期和时间,可以使用UTC和非UTC版本Name检索文件的完整路径,这是一个只读抽象属性,在FileInfo和DirectoryInfo中进行了重写。下面是FileInfo的专用属性:
表1-5 FileInfo的属性       属性                                     说明                                   Directory检索一个DirectoryInfo对象,表示包含当前文件的目录。这个属性是只读的 DirectoryName返回文件目录的路径。这个属性是只读的IsReadOnly文件只读特性的快捷方式。也可以通过Attributes来访问这个属性Length获取文件的大小(以字节为单位),返回long值。这个属性是只读的 注意:FileInfo对象本身不表示流,要读写文件,必须创建Stream对象,不过FileInfo对象提供了几个返回实例化的Stream对象的方法来帮助做到这一点。

下面介绍DirectoryInfo:

Directory和DirectoryInfo的区别及用法跟File和FileInfo的区别及用法类似。以下是DirectoryInfo的专用属性:
表1-6 DirectoryInfo类的专用属性      属性                      说明                                                                         Parent    检索一个DirectoryInfo对象,表示包含当前目录的目录。这个属性是只读的Root检索一个DirectoryInfo对象,表示包含当前目录的根目录,例如 C:\目录。这个属性是只读的 路径名和相对路径:

路径名:也成绝对路径名。就是显示的指定文件或目录来自于哪一个已知的位置,通俗讲就是完整路径。

相对路径:以当前工作目录为起点,这是相对路径名的默认设置。例如应用程序运行在C:\Development\FileDemo目录上,并使用相对路径LogFile.txt,该文件就是:C:\Development\FileDemo\LogFile.txt。若想上移目录,要使用..字符串,那么路径..\Log.txt表示C:\Development\Log.txt文件。如果上移两个目录,则是..\..\。如果有必要可使用Directory.GetCurrentDirectory()找出工作目录的当前位置,也可以使用Directory.SetCurrentDirectory()设置新路径。

FileStream对象:

FileStream对象表示指向磁盘或网络路径上的文件流。这个类提供了在文件中读写字节的方法。但经常使用StreamReader或StreamWrite执行这些功能,这是因为FileStream类操作的是字节和字节数组,而Stream类操作的是字符数据。字符数据易于使用,但是有些操作,如随机文件访问(访问文件中间某点的数据),就必须由FileStream对象执行。创建FileStream对象最简单的构造函数仅有两个参数,即文件名和FileMode枚举值。如下:

FileStream aFile = new FileStream(filename,FileMode.<Member>);          FileMode枚举包含几个成员,指定了如何打开或创建文件。如表1-7:

表1-7 FileMode枚举成员         成员              文件存在                             文件不存在                  Append打开文件,流指向文件的末尾处,只能与
枚举FileAccess.Write结合使用创建一个新文件。只能与枚举
FileAccess.Write结合使用Create删除该文件,然后创建新文件创建新文件CreateNew抛出异常创建新文件Open打开文件,流指向文件开头处抛出异常OpenOrCreate打开文件,流指向文件开头处创建新文件Truncate打开文件,清除其内容,流指向文件
开头处,保留文件的初始创建日期抛出异常

另外一个常用的构造函数如下:

FileStream aFile = new FileStream(filename,FileMode.<Member>,FileAccess.<Member>);第三个参数是FileAcess枚举的一个成员,它指定了流的作用。如表1-8:

表1-8 FileAccess枚举成员      成员                          说明                                Read     打开文件,用于只读Write      打开文件,用于只写ReadWrite     打开文件,用于读写 注意:对文件进行非FileAcess枚举成员指定的操作会导致抛出异常,此属性的作用是基于用户的权限级别改变用户对文件的访问权限。在FileStream构造函数不使用FileAcess枚举的版本中,使用默认值FileAcess.ReadWrite。

File和FileInfo类都提供了OpenRead()和OpenWrite()方法,更易于创建FileStream对象,前者打开了只读访问的文件,后者只允许写入文件。如下打开了用于只读访问的Data.tex文件:

FileStream aFile = File.OpenRead("Data.txt");

       下面代码执行同样的功能:FileInfo aFileInfo = new aFileInfo("Data.txt");  FileStream aFile = aFileInfo.OpenRead();

1、文件位置

FileStream类维护内部文件指针,该指针指向文件中进行下一次读写操作的位置。大多情况下打开文件时,它就指向文件的开始位置,但可以通过Seek()方法修改位置。该方法有两个参数:第一个指定文件指针移动距离(以字节为单位)。第二个指定开始计算的起始位置,用SeekOrigin枚举的一个值表示。SeekOrigin枚举包含:Begin、Current和End。例如:aFile.Seek(8,SeekOrigin.Begin);将指针从文件开始位置移动8个字节。

2、读取数据

FileStream类只能处理原始字节(byte),这使得FileStream类可以用于任何数据文件,而不仅仅是文本文件,诸如图像声音等都可读取,但是FileStream对象不能直接将数据读入字符串,StreamReader类却可以。

FileStream.Read()方法是从FileStream对象所指向的文件中访问数据的主要手段,这个方法从文件中读取数据,再把数据写入一个字节数组,它有三个参数:第一个是传入的字节数组,用来接受FileStream对象中的数据;第二个是字节数组中开始写入数据的位置,它通常是0;第三个指定从文件中读取多少字节。

例子:

        static void Main(string[] args)        {            byte[] byteData = new byte[200];            char[] charaData = new char[200];            FileStream aFile = new FileStream("../../Program.cs",FileMode.OpenOrCreate);            aFile.Seek(144,SeekOrigin.Begin);            aFile.Read(byteData,0,200);            Decoder d = Encoding.UTF8.GetDecoder();            d.GetChars(byteData,0,byteData.Length,charaData,0);            Console.WriteLine(charaData);            Console.ReadLine();        }
3、写入数据

写入过程与读取过程类似。如下例子:

        static void Main(string[] args)        {            byte[] byteData;            char[] charaData;            FileStream aFile = new FileStream("Program.txt",FileMode.Create);            charaData = "My pink half of the drainpipe.".ToCharArray();            byteData = new byte[charaData.Length];            Encoder d = Encoding.UTF8.GetEncoder();            d.GetBytes(charaData, 0, charaData.Length, byteData, 0,true);            aFile.Seek(0,SeekOrigin.Begin);            aFile.Write(byteData,0,byteData.Length);        }
StreamWriter对象:

操作字节数组比较麻烦,所以通常使用StreamReader或StreamWriter来处理文件,如果不需要改变文件指针位置,这些类就很容易操作文件。StreamWriter类允许将字符和字符串写入到文件中,它处理底层的转换,向FileStream对象写入数据。创建StreamWriter对象的方法很多,如果已经有了FileStream对象,则可以使用此对象来创建:

FileStream aFile = new FileStream("Log.txt",FileMode.CreateNew); 

StreamWriter sw = new StreamWriter(aFile);

也可以直接从文件中创建StreamWriter对象: 

StreamWriter sw = new StreamWriter("Log.txt",true);这个构造函数的参数是文件名和一个Boolean值,这个Boolean值指定是追加文件,还是创建新文件:

a、如果是false,则创建一个新文件或者截取现有文件并打开它

b、如果是true,则打开文件,保留原来的数据,如果找不到,则创建一个新文件

StreamWriter对象不会提供像FileStream中FileMode、FileAccess类似的选项,只有使用Boolean来追加文件或创建新文件,因此总是对文件拥有读写权。若想使用高级参数,必须首先在FileStream构造函数中指定,然后在FileStream对象中创建StreamWriter。示例如下:

        static void Main(string[] args)        {            FileStream aFile = new FileStream("Log.txt",FileMode.OpenOrCreate);            StreamWriter sw = new StreamWriter(aFile);            bool truth = true;            sw.WriteLine("Hell to you");            sw.WriteLine("It is now {0} and things are looking good.",DateTime.Now.ToLongDateString());            sw.Write("More than that");            sw.Write("it's {0} that C# is fun.",truth);            sw.Close();        }
注意:WriteLine()自动换行,Write()则每次会在上次的结尾处写入。

StreamReader对象:

用于读取数据,它的创建方式跟StreamWriter类似,最简单的创建方式如下:

FileStream aFile = new FileStream(“Log.txt”,FileMode.Open);StreamReader sr = new StreamReader(aFile);

也可用具体文件路径创建:StreamReader sr = new StreamReader(“Log.txt”);具体示例如下:

        static void Main(string[] args)        {            string line;            FileStream aFile = new FileStream("Log.txt",FileMode.OpenOrCreate);            StreamReader sr = new StreamReader(aFile);            line = sr.ReadLine();            while(line != null)            {                Console.WriteLine(line);                line = sr.ReadLine();            }            sr.Close();            Console.ReadKey();        }
1、读取数据

ReadLine()方法不是在文件中访问数据的唯一方法。StreamReader类还包含许多读取数据的方法,其中最简单的是Read(),此方法将流的下一个字符作为正整数值返回,如果到达了流的结尾处,则返回-1,使用Convert类可以把这个值转换为字符,用该方法重新编写上面:

            StreamReader sr = new StreamReader(aFile);            int charCode;            charCode = sr.Read();            while(charCode !=-1)            {                Console.WriteLine(Convert.ToChar(charCode));                charCode = sr.Read();            }            sr.Close();
对于小型文件,可使用一个非常简单的方法ReadToEnd(),此方法读取整个文件,并将其作为字符串返回。如:

            StreamReader sr = new StreamReader(aFile);            line = sr.ReadToEnd();            Console.WriteLine(line);            sr.Close();
处理大型文件的另一个方法是.NET4中新增的静态方法File.ReadLines(),它返回IEnumerable<string>集合。可迭代这个集合中的字符串,一次读取文件中的一行。使用如下

            foreach(string alternativeLine in File.ReadLines("Log.txt"))            {                Console.WriteLine(alternativeLine);            }
2、用分隔符分隔的文件

用分隔符分割的数据格式,常用String类的Split()方法将字符串转换为一个数组。具体事例如下:

        private static List<Dictionary<string, string>> GetData(out List<string> columns)        {            string line;            string[] stringArray;            char[] charArray = new char[] { ',' };            List<Dictionary<string, string>> data =                new List<Dictionary<string, string>>();            columns = new List<string>();            FileStream aFile = new FileStream(@"..\..\SomeData.txt",FileMode.Open);            StreamReader sr = new StreamReader(aFile);            line = sr.ReadLine();            stringArray = line.Split(charArray);            for(int x = 0;x<=stringArray.GetUpperBound(0);x++)            {                columns.Add(stringArray[x]);            }            line = sr.ReadLine();            while(line!=null)            {                stringArray = line.Split(charArray);                Dictionary<string, string> dataRow = new Dictionary<string, string>();                for(int x = 0;x<=stringArray.GetUpperBound(0);x++)                {                    dataRow.Add(columns[x],stringArray[x]);                }                data.Add(dataRow);                line = sr.ReadLine();            }            sr.Close();            return data;        }        static void Main(string[] args)        {            List<string> columns;            List<Dictionary<string, string>> myData = GetData(out columns);            foreach(string column in columns)            {                Console.Write("{0,-20}",column);            }            Console.WriteLine();            foreach(Dictionary<string,string> row in myData)            {                foreach(string column in columns)                {                    Console.Write("{0,-20}", row[column]);                }                Console.WriteLine();            }            Console.ReadKey();                  }
异步文件访问:

当一次性执行大量文件访问操作或者要处理非常大的文件,读写文件系统数据是很缓慢的,此时想在等待这些操作完成的同时去执行其他操作,这时就需要一步操作了,这种异步适用于FileStream、StreamWriter和StreamReader类,通常是带有Async后缀的,例如StreamReader类的ReaderLineAsync()方法。

读写压缩文件:

在处理文件时,使用压缩文件会节约大量的硬盘空间。System.IO.Compression名称空间就包含能在代码中压缩文件的类,这些类使用GZIP或者Deflate算法。但压缩文件并不只是把他们压缩一下就完事了,商业应用程序允许把多个文件放在一个压缩文件(通常称为存档文件)中。本节介绍的内容简单得多:只是把文本数据保存在压缩文件中。不能在外部实用程序中访问这个文件,但这个文件比未压缩版本要小得多。

System.IO.Compression名称空间中有两个压缩流类DeflateStream和GZipStream,它们的工作方式非常类似,对于这两个类,都要用已有的流初始化它们,对于文件,流就是FileStream对象。此后就可以把他们用于StreamReader和StreamWriter了。此外只需指定流是用于压缩(保存文件)还是解压缩(加载文件),类就知道要对传送给它的数据执行什么操作。示例如下:

        static void SaveCompressedFile(string filename,string data)        {            FileStream fileStream = new FileStream(filename,FileMode.Create,FileAccess.Write);            GZipStream compressionStream = new GZipStream(fileStream,CompressionMode.Compress);            StreamWriter writer = new StreamWriter(compressionStream);            writer.Write(data);            writer.Close();        }        static string LoadCompressedFile(string filename)        {            FileStream fileStream = new FileStream(filename,FileMode.Open,FileAccess.Read);            GZipStream compressionStream = new GZipStream(fileStream,CompressionMode.Decompress);            StreamReader reader = new StreamReader(compressionStream);            string data = reader.ReadToEnd();            reader.Close();            return data;        }        static void Main(string[] args)        {            string filename = "compressedFile.txt";            string sourceString = Console.ReadLine();            StringBuilder sourceStringMultiplier = new StringBuilder(sourceString.Length*100);            for(int i = 0; i<100; i++)            {                sourceStringMultiplier.Append(sourceString);            }            sourceString = sourceStringMultiplier.ToString();            Console.WriteLine("Source data is {0} bytes long.",sourceString.Length);            SaveCompressedFile(filename, sourceString);            Console.WriteLine("\nData saved to {0}.",filename);            FileInfo compressedFileData = new FileInfo(filename);            Console.WriteLine("Compressed file is {0} bytes long.",compressedFileData.Length);            string recoveredString = LoadCompressedFile(filename);            recoveredString = recoveredString.Substring(0,recoveredString.Length/100);            Console.WriteLine("\nRecovered data: {0}",recoveredString);            Console.ReadKey();        }
序列化对象:

应用程序经常需要在硬盘上存储数据。前面介绍了逐段构建文本和数据文件,但这通常并非是最简单的方式。有时最好以对象的形式存储数据。

1、.NETFramework在System.Runtime.Serialization和System.Runtime.Serialization.Formatter名称空间中提供了序列化对象的基础架构,后者包含的名称空间中有一些具体的类实现了这个基础架构。在框架中,有一个重要的实现可用:System.Runtime.Serialization,Formatter.Binary。这个名称空间包含的BinaryFormatter类能够将对象序列化为二进制数据,也可以将二进制数据序列化为对象。

表1-9 IFormatter接口的方法          方法                                                       说明                                    void Serialize(Stream stream,object source)把source序列化为streamobject Deserialize(Stream stream)反序列化stream中的数据,返回得到的对象这些方法都处理流,所以可与前面的内容结合起来,使用BinaryFormatter进行序列化就简单了:

IFormatter serializer = new BinaryFormatter();      serializer.Serialize(myStream,myObject);

反序列化同样简单:

IFormatter serializer = new BinaryFormatter();    MyObjectType myNewObject = serializer.Deserialize(myStream) as MyObjectType;

下面将实际应用这些代码:

新添加一个Product类:

    public class Product    {        public long Id;        public string Name;        public double Price;        [NonSerialized]        string Notes;        public Product(long id, string name, double price, string notes)        {            Id = id;            Name = name;            Price = price;            Notes = notes;        }        public override string ToString()        {            return string.Format("{0}: {1} (${2:F2}) {3}", Id, Name, Price, Notes);        }    }
然后在Progress类的Main方法中添加:

        static void Main(string[] args)        {            List<Product> products = new List<Product>();            products.Add(new Product(1,"Spily Pung",1000.0,"Goods stuff."));            products.Add(new Product(2, "Gloop Galloop Soup", 25.0, "Tasty."));            products.Add(new Product(4, "Hat Sauce", 12.0, "One for the kids."));            Console.WriteLine("Products to save:");            foreach(Product product in products)            {                Console.WriteLine(product);            }            Console.WriteLine();            IFormatter serializer = new BinaryFormatter();            FileStream saveFile = new FileStream("Products.bin", FileMode.Create, FileAccess.Write);            serializer.Serialize(saveFile,products);            saveFile.Close();            FileStream loadFile = new FileStream("Products.bin",FileMode.Open,FileAccess.Read);            List<Product> saveProducts = serializer.Deserialize(loadFile) as List<Product>;            loadFile.Close();            Console.WriteLine("Products loaded:");            foreach(Product product in saveProducts)            {                Console.WriteLine(product);            }        }
本示例创建一个Product对象集合,把集合保存到磁盘上,然后重新加载它。但第一次运行抛出异常,因为Product对象没有标记为“可序列化”。.NET Framework要求把对象标记为可序列化,才能序列化它们。这有许多原因,包括:

1、一些对象序列化的效果不佳。例如,他们需要引用只有他们本身位于内存中时才存在的本地数据。

2、一些对象包含敏感的数据,这些数据不应该以不安全的方式保存或传输到另一个进程中。

Serializable这个特性并没有由派生类继承,他必须应用于要进行序列化的每个类。NoSerialized这个属性任何成员都可使用,使用之后将不被序列化。

监控文件系统:

.NET Freamwork使用FileSystemWatcher来对文件进行监控,其过程非常简单:首先必须设置一些属性,指定监控的位置、内容以及引发应用程序要处理的事件的时间。然后给FileSystemWatcher提供定制事件处理程序的地址,当发生重要事件时,FileSystemWatcher就可以调用这些事件处理程序,最后打开FileSystemWatcher,等待事件。在启用FileSystemWatcher之前必须设置的属性如下:

表1-10 FileSystemWatcher的属性               属性                                          说明                                                                                  Path设置要监控的文件位置或目录NotifyFilter这是NotifyFilters枚举值的组合,NotifyFilters枚举值指定了在被监控的文件内要监控哪些内容。
这些表示要监控的文件或文件夹的属性。如果指定的属性发生了变化,就引发事件。可能的枚
举值是Attributes、CreationTime、DirectoryName、FileName、LastAccess、LastWrite、
Security和Size。注意,可通过二元OR运算符来合并这些枚举值Filter指定要监控哪些文件的过滤器。例如,*.txt 设置之后,就必须为4个事件Changed、Created、Deleted和Renamed编写事件处理程序。在设置了属性和事件之后,将EnableRaisingEvents属性设置为true,就可以开始监控工作。如下示例:

实例化FileSystemWatcher对象并添加监听

            FileSystemWatcher watcher = new FileSystemWatcher();            watcher.Deleted += (s, e) => AddMessage("File: {0} Deleted",e.FullPath);            watcher.Renamed += (s, e) => AddMessage("File renamed from {0} to {1}",e.OldName,e.FullPath);            watcher.Changed += (s, e) => AddMessage("File: {0} {1}",e.FullPath,e.ChangeType.ToString());            watcher.Created += (s, e) => AddMessage("File: {0} Created",e.FullPath);
设置监听位置及其他监听条件:

            watcher.Path = System.IO.Path.GetDirectoryName("Progress.txt");            watcher.Filter = System.IO.Path.GetFileName("Progress.txt");            watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Size;            watcher.EnableRaisingEvents = true;
System.IO.Path用来处理和提取文件位置字符串中的信息,这里首先使用它通过GetDirectoryName()方法提取用户在文本框中输入的目录名称,NotifyFilter是过滤器。
0 0
原创粉丝点击