第一章 文件和流
6.1 用流读写文件
C#把所有文件都看成是顺序的字节流,用抽象类Stream代表一个流。Stream类有许多派生类,例如FileStream类,以字节为单位读写文件;BinaryRead类和BinaryWrite类,以基本数据类型为单位读写文件,可以从文件直接读写bool、String、int16、int等基本数据类型数据;StreamReader和StreamWriter类以字符或字符串为单位读写文件。本节首先介绍这些类的用法,然后介绍数据的序列化。使用流读写文件必须引入命名空间:System.IO。
6.1.1 用FileStream类读写字节
使用FileStream类可以建立文件流对象,用来打开和关闭文件,以字节为单位读写文件。也可对其它与文件相关的操作系统句柄进行操作,如管道、标准输入和标准输出。FileStream类对象能对输入输出进行缓冲,从而提高性能。该类常用属性、方法如下:
l 属性CanRead、CanSeek、CanWrite:检查流对象是否可以读、定位、写。只读属性。
l 属性Length:用字节表示的流对象长度,即文件的长度。只读属性。
l 属性Position:获取或设置流对象当前读写位置。
l 构造函数public FileStream(string path,FileMode mode,FileAccess access):参数path是文件的相对路径或绝对路径,构造函数将建立参数path指定文件的FileStream类对象。参数mode可以是如下模式:FileMode.Append,打开文件并将读写位置移到文件尾,文件不存在则创建新文件,只能同FileAccess.Write一起使用;FileMode.Create,创建新文件,如果文件已存在,文件内容将被删除;FileMode.CreateNew,创建新文件,如果文件已存在,则引发异常;FileMode.Open,打开现有文件,如果文件不存在,则引发异常;FileMode.OpenOrCreate,如果文件存在,打开文件,否则,创建新文件;FileMode.Truncate,打开现有文件,并将文件所有内容删除。参数access可以是:FileAccess.Read(只读方式打开文件)、FileAccess.Write(只写方式打开文件)、FileAccess.ReadWrite(读写方式打开文件)。也可以没有第三个参数,默认为FileAccess.ReadWrite。一共有8个构造函数,其它构造函数,请用帮助查看。
l 方法void Write(byte[] array,int offset,int count):将数组中多个字节写入流,参数1是要写入的数组,要写入流的第1个字节是array[offset],参数3为要写入的字节数。写字节数组数据到文件的程序如下,该程序将建立文件d:/g1.bin。
using System;
using System.IO;//使用文件必须引入的命名空间
class WriteFile
{ static void Main()
{ byte[] data=new byte[10];//建立字节数组
for(int i=0;i<10;i++)//为数组赋值
data[i]=(byte)i;
FileStream fs=new FileStream("d://g1.bin",FileMode.Create);//建立流对象
fs.Write(data,0,10);//写data字节数组中的所有数据到文件
fs.Close();//不再使用的流对象,必须关闭。垃圾收集器不能自动清除流对象
}
}
l 方法int Read(byte[] array,int offset,int count):从流中读数据写入字节数组array,读入的第1个字节写入array[offset],参数3为要读入的字节数。返回值为所读字节数,由于可能已读到文件尾部,其值可能小于count,甚至为0。读一个文件所有字节到数组并在屏幕显示的程序如下,请先用上例建一个文件,然后用此例读出。
using System;
using System.IO;//使用文件必须引入的命名空间
class ReadFile
{ static void Main()
{ FileStream fs=new FileStream("d://g1.bin",FileMode.Open);
byte[] data=new byte[fs.Length];
long n=fs.Read(data,0,(int)fs.Length);//n为所读字节数
fs.Close();
Console.WriteLine("文件的内容如下:");
foreach(byte m in data)
Console.Write("{0},",m);
}
}
l 方法long Seek(long offset,SeekOrigin origin):该方法移动文件读写位置到参数2指定位置加上参数1指定偏移量处,参数2可以是SeekOrigin.Begin、SeekOrigin.End、SeekOrigin.Current,分别为开始位置、结束位置、当前读写位置。例子如下:
using System;
using System.IO;//使用文件必须引入的命名空间
class FileStreamProperty
{ static void Main()
{ FileStream fs=new FileStream("d://g1.bin",FileMode.Open);//无第3个参数
fs.Seek(-4,SeekOrigin.End);//文件读写位置移到从文件尾部向前5个字节
Console.WriteLine("读写位置:{0},能定位:{1}",fs.Position,fs.CanSeek);
Console.WriteLine("能读:{0},能写:{1}",fs.CanRead,fs.CanWrite);
fs.Close();
}
}
注意建立流对象fs的构造函数无第3个参数,因此按读写方式打开。程序运行结果如下:
读写位置:6,能定位:true
能读:true, 能写:true
6.1.2 用BinaryReader、BinaryWriter类读写基本数据类型
使用BinaryReader和BinaryWriter类可以从文件直接读写bool、String、int16、int等基本数据类型数据。常用BinaryWriter类方法如下:
l 构造函数BinaryWriter(Stream input),参数为FileStream类对象。
l 方法viod Write(数据类型 Value):写入参数指定的数据类型的一个数据,数据类型可以是基本数据类型,例如,int、bool、float等。写int类型数据程序如下:
using System;
using System.IO;//使用文件必须引入的命名空间
class WriteFile
{ static void Main()
{ FileStream fs=new FileStream("d://g1.dat",FileMode.Create);
BinaryWriter w=new BinaryWriter(fs);
for(int i=0;i<10;i++)
w.Write(i);//写入整形数
w.Close();
}
}
常用BinaryReader类方法如下:
l 构造函数BinaryReader(Stream input),参数为FileStream类对象。
l 方法ReadBoolean、ReadByte、ReadChar等:返回指定类型一个数据。没有参数。
l 方法ReadBytes:用法如下byte[] array=r.ReadBytes(array.Length),其中r为BinaryReader类对象。
读int类型数据程序如下,请先用上例建一个文件,然后用此例读出。
using System;
using System.IO;//使用文件必须引入的命名空间
class ReadFile
{ static void Main()
{ int[] data=new int[10];
FileStream fs=new FileStream("d://g1.dat",FileMode.Open);
BinaryReader r=new BinaryReader(fs);
for(int i=0;i<10;i++)
data[i]=r.ReadInt32();
r.Close();
Console.WriteLine("文件的内容如下:");
foreach(int m in data)
Console.Write("{0},",m);
}
}
6.1.3 用StreamReader和StreamWriter类读写字符串
读写字符串可以用StreamReader和StreamWriter类。常用StreamWriter类方法如下:
l 构造函数StreamWriter(string path,bool append),path是要写文件的路径,如果该文件存在,并且append为false,则该文件被改写。如果该文件存在,并且append为 true,则数据被追加到该文件中。否则,将创建新文件。
l 方法void Writer(string value):将字符串写入流。
l 方法void Writer(char value):将字符写入流。
写字符串类型数据程序如下:
using System;
using System.IO;//使用文件必须引入的命名空间
class WriteFile
{ static void Main()
{ StreamWriter w=new StreamWriter("d://g.text",false);
w.Write(100);//100首先转换为字符串,再写入。
w.Write("100个");//字符串之间用换行符 ("\n"或"\r\n")分隔
w.Write("End of file");//一个字符串叫做文件中的一行
w.Close();
}
}
常用StreamReader类方法如下:
l 构造函数StreamReader(string path),参数是要读文件的路径。
l 方法Read():从流中读取一个字符,并使读字符位置移动到下一个字符。返回代表读出字符ASCII字符值的int类型整数,如果没有字符可以读出,返回-1。如果sr是StreamReader对象,读取一个字符的用法如下:char c=(char)sr.Read()。
l 方法ReadLine():从流中读取一行字符并将数据作为字符串返回。行的定义是:两个换行符 ("\n"或"\r\n")之间的字符序列。返回的字符串不包含回车或换行符。
读字符串程序如下,请先用上例建一个文件,然后用此例读出。
using System;
using System.IO;//使用文件必须引入的命名空间
using System.Collections;//使用ArrayList必须引入的命名空间
class ReadFile
{ static void Main()
{ string sLine="";
ArrayList arrText=new ArrayList();
using(StreamReader objReader=new StreamReader("d://g.text"))
{ do//使用using
语句以确保所涉及的文件在写入或读取操作后正确关闭
{ sLine=objReader.ReadLine();
if(sLine!=null)
arrText.Add(sLine);
}while(sLine!=null);
}
Console.WriteLine("文件的内容如下:");
foreach(string m in arrText)
Console.Write("{0}",m);
}
}
程序运行结果如下:
文件的内容如下:
100100个End of file
6.1.4 序列化
对于一个复杂的数据结构,例如数组,都用以上方法存入文件,就显得过于复杂了。为了简化这类问题,C#提出了序列化的概念,序列化包括序列化和反序列化,所谓序列化就是把类的对象作为一个整体存入文件,反序列化则是相反的过程。C#中的许多类都支持序列化,可以用如下方法判别一个类是否支持序列化:
Hashtable h=new Hashtable();//建立一个哈希表对象
Type myType=h.GetType();
bool myBool=myType.IsSerializable;//如为true,支持序列化,这里myBool=true
例子e6_1_4A:下边的例子完成了一个哈希表对象的序列化。
using System;
using System.IO;
using System.Collections;//使用哈希表引用的命名空间
using System.Runtime.Serialization.Formatters.Binary;//使用序列化引用的命名空间
using System.Runtime.Serialization;//使用序列化引用的命名空间
class SerialFile
{ static void Main()
{ Hashtable h=new Hashtable();//建立一个哈希表对象
h.Add("键1","值1");//哈希表的每一个元素是一对键值
h.Add("键2","值2");//例如商品编号和商品名称
h.Add("键3","值3");//通过键值,可以很容易找到键值对应的值
//序列化数据,@表示其后字符串不包括转义字符
FileStream fs=new FileStream(@"d:/d.dat",FileMode.Create);
BinaryFormatter formatter=new BinaryFormatter();
formatter.Serialize(fs,h);
fs.Close();
//反序列化数据
fs=new FileStream(@"d:/d.dat",FileMode.Open);
h.Clear();
h=(Hashtable)formatter.Deserialize(fs);
fs.Close();
//显示反序列化数据
foreach(DictionaryEntry de in h)
Console.WriteLine("{0}:{1};",de.Key,de.Value);//注意哈希表用法
}
运行结果如下:
键1:值1;
键2:值2;
键3:值3;
例子e6_1_4B:自己定义的类也可以序列化,只要在类定义前增加[Serializable]即可,下边例子首先定义了一个可序列化的类Person,建立若干Person类对象,存到ArrayList类对象中,并将ArrayList类对象序列化。程序如下:
using System;
using System.IO;
using System.Collections;//使用ArrayList类引用的命名空间
using System.Runtime.Serialization.Formatters.Binary;//使用序列化引用的命名空间
using System.Runtime.Serialization;//使用序列化引用的命名空间
namespace e6_1_4B
{ [Serializable] public class Person//[Serializable]表示该类可以序列化
{ private string P_name="张三";//P_name是私有字段
private int P_age=12;//P_age是私有字段
public string Name//定义属性Name
{ get
{ return P_name;}
set
{ P_name=value;}
}
public int Age//定义属性Age
{ get
{ return P_age;}
set
{ P_age=value;}
}
}
class Class1
{ [STAThread]
static void Main(string[] args)
{ ArrayList h=new ArrayList();
Person p=new Person();
h.Add(p);
p=new Person();
p.Name="李四";
p.Age=24;
h.Add(p);
//序列化数据
FileStream fs=new FileStream(@"d:/d.dat",FileMode.Create);
BinaryFormatter formatter=new BinaryFormatter();
formatter.Serialize(fs,h);
fs.Close();
//反序列化数据
fs=new FileStream(@"d:/d.dat",FileMode.Open);
h.Clear();
h=(ArrayList)formatter.Deserialize(fs);
fs.Close();
foreach(Person de in h)//显示反序列化数据
Console.WriteLine("Name={0},Age={1};",de.Name,de.Age);
}
}
}
运行结果如下:
Name=张三,Age=12;
Name=李四,Age=24;
6.1.5 Stream类的其它派生类
Stream类的其它派生类包括:MemoryStream、BuffereStream、NetworkStream(在System.Net.Sockets命名空间)。其中MemoryStream类把文件放到内存中,极大地提高了文件的读写速度。BuffereStream类为文件的读写建立一个缓冲区,写文件先把文件存到缓冲区中,缓冲区满了以后,才写入物理设备。先把文件中较多数据读到缓冲区中,读文件先从缓冲区读,缓冲区没有该数据,才再一次从物理设备读入缓冲区,用这样的方法改善文件读写的性能。NetworkStream类把网络传输的数据也看作流。请用帮助查看这些类的用法。
6.2 File类和FileInfo类
C#语言中通过File和FileInfo类来创建、复制、删除、移动和打开文件。在File类中提供了一些静态方法,使用这些方法可以完成上述功能,但File类不能建立对象。FileInfo类使用方法和File类基本相同,但FileInfo类能建立对象。在使用这两个类时需要引用System.IO命名空间。这里重点介绍File类的使用方法。
6.2.1 File类常用的方法
File类常用的方法如下,File类所有方法都是静态方法。
l AppendText:参数是文件路径,创建参数指定文件的StreamWriter类对象,可以向该文件添加字符或字符串数据。如文件不存在,就创建该文件。
l Copy:复制参数指定的文件到新文件夹。
l Create:按参数指定的路径建立新文件
l Delete:删除参数指定的文件。
l Exists:检查参数指定路径的文件是否存在,存在,返回true。
l GetAttributes:返回参数指定文件的属性。
l GetCreationTime:返回参数指定的文件或文件夹的创建日期和时间。
l GetLastAccessTime:返回参数指定的文件或文件夹的最后一次访问日期和时间。
l GetLastWriteTime:返回参数指定的文件或文件夹最后一次修改的日期和时间。
l Move:移动参数指定的文件到新文件夹。
l Open:参数是文件路径,创建参数指定文件的可以读写的FileStream类对象。
l OpenRead:参数是文件路径,创建参数指定文件的只读FileStream类对象。
l OpenWrite:参数是文件路径,返回参数指定文件的只写FileStream类对象。
l SetAttributes:设置参数指定文件的属性。
l SetCreationTime:设置参数指定文件的创建日期和时间。
l SetLastAccessTime:设置参数指定文件的最后一次访问日期和时间。
l SetLastWriteTime:设置参数指定文件最后一次修改的日期和时间。
下面通过程序实例来介绍其中主要方法的使用。
6.2.2 判断文件是否存在的方法File.Exists
方法声明如下:public static bool Exists(string path);该方法判断参数指定的文件是否存在,参数path指定文件路径。如果文件存在,返回true,如果文件不存在,或者访问者不具有访问此文件的权限,或者path描述一个目录,返回false。下面的代码段判断是否存在c:\Example\e1.txt文件:
if(File.Exists(@"c:\Example\e1.txt")) {…}//处理代码
6.2.3 文件删除方法File.Delete
方法声明如下:public static void Delete(string path);该方法删除参数指定的文件,参数path指定要删除的文件的相对或绝对路径。下面的程序删除用户指定文件。
using System;
using System.IO;//使用文件必须引入的命名空间
class DeleteFile
{ static void Main()
{ Console.WriteLine("请键入要删除的文件的路径:");
string path=Console.ReadLine();//从键盘读入路径,输入回车结束
if(File.Exists(@path))//@表示其后字符串不包括转义字符
File.Delete(@path);
else
Console.WriteLine("文件不存在!");
}
}
6.2.4 文件复制方法File.Copy
该方法声明如下:
public static void Copy(string sourceFileName,string destFileName,bool overwrite);
该方法将参数sourceFileName指定文件拷贝到参数destFileName指定的目录,修改文件名为参数destFileName指定的文件名,如果OverWrite为true,而且文件名为destFileName的文件已存在的话,将会被复制过去的文件所覆盖。例子如下:
using System;
using System.IO;//使用文件必须引入的命名空间
class CopyFile
{ static void Main()
{ Console.WriteLine("请键入要拷贝的源文件的路径:");
string path=Console.ReadLine();//从键盘读入路径,输入回车结束
Console.WriteLine("请键入要拷贝的目的文件的路径(包括文件名):");
string path1=Console.ReadLine();//从键盘读入路径,输入回车结束
if(File.Exists(@path))//@表示其后字符串不包括转义字符
{ if(!File.Exists(@path1))//如果不存在目的文件,拷贝
File.Copy(@path,@path1,true);
else
Console.WriteLine("目的文件存在或目的路径非法!");
}
else
Console.WriteLine("源文件不存在!");
}
}
6.2.5 文件移动方法File.Move和FileInfo.MoveTo
该方法声明如下:
public static void Move(string sourceFileName,string destFileName);
该方法将参数sourceFileName指定文件移动到参数destFileName指定的目录,修改文件名为参数destFileName指定的文件名,如果目标文件已经存在,或者路径格式不对,将引发异常。注意,只能在同一个逻辑盘下进行文件转移。如果试图将c盘下的文件转移到d盘,将发生错误。下面的代码可以将c:\Example下的e1.txt文件移动到c盘根目录下。File.Move(@"c:\Example\BackUp.txt",@"c:\BackUp.txt");
FileInfo类方法MoveTo可以将一个逻辑盘的文件移到另一个逻辑盘,例子如下:
using System;
using System.IO;//使用文件必须引入的命名空间
class MoveFile
{ static void Main()
{ Console.WriteLine("请键入要移动的源文件的路径:");
string path=Console.ReadLine();//从键盘读入路径,输入回车结束
Console.WriteLine("请键入要移动的目的文件的路径(包括文件名):");
string path1=Console.ReadLine();//从键盘读入路径,输入回车结束
if(File.Exists(@path))//@表示其后字符串不包括转义字符
{ if(!File.Exists(@path1))
{ FileInfo fi=new FileInfo(@path);//使用FileInfo必须建立对象
fi.MoveTo(@path1);//fi所代表的文件移到path1指定的位置
//File.Move(@path,@path1);//如在同一磁盘可使用此句替换前2句
}
else
Console.WriteLine("目的文件存在或路径非法!");
}
else
Console.WriteLine("源文件不存在!");
}
}
6.2.6 设置文件属性方法File.SetAttributes
方法声明如下:
public static void SetAttributes(string path,FileAttributes fileAttributes);
参数path指定要修改属性的文件的路径;参数fileAttributes指定要修改的文件属性,可以是如下数值:Archive(存档)、Compressed(压缩文件)、Directory(目录文件)、Encrypted(加密)、Hidden(隐藏)、Normal(普通文件)、ReadOnly(只读文件)、System(系统文件)、Temporary(临时文件)。下面的代码可以设置文件c:\e1.txt的属性为只读、隐藏。
File.SetAttributes(@"c:\e1.txt",FileAttributes.ReadOnly|FileAttributes.Hidden);
6.2.7 得到文件的属性方法File.GetAttributes
方法声明如下:public static FileAttributes GetAttributes(string path);方法返回参数指定的文件的FileAttributes,如果未找到路径或文件,返回-1,
例子如下:
using System;
using System.IO;//使用文件必须引入的命名空间
class GetFileAttributes
{ static void Main()
{ Console.WriteLine("请键入要得到属性的文件路径:");
string path=Console.ReadLine();//从键盘读入路径,输入回车结束
if(File.Exists(@path))//@表示其后字符串不包括转义字符
{ FileAttributes attributes=File.GetAttributes(path);
if((attributes&FileAttributes.Hidden)==FileAttributes.Hidden)
Console.WriteLine("隐藏文件");
else
Console.WriteLine("不是隐藏文件");
//得到文件其它信息
FileInfo fileInfo=new FileInfo(@path);
Console.WriteLine(fileInfo.FullName+"文件长度="+
fileInfo.Length+",建立时间="+fileInfo.CreationTime);
//也可用如下语句得到文件其它信息
Console.WriteLine("建立时间="+File.GetCreationTime(@path)
+"最后修改时间="+File.GetLastWriteTime(@path)+
"访问时间="+File.GetLastAccessTime(@path));
}
else
Console.WriteLine("文件不存在!");
}
}
6.3 Directory类和DirectoryInfo类
C#语言中通过Directory类来创建、复制、删除、移动文件夹。在Directory类中提供了一些静态方法,使用这些方法可以完成上述功能。但Directory类不能建立对象。DirectoryInfo类使用方法和Directory类基本相同,但DirectoryInfo类能建立对象。在使用这两个类时需要引用System.IO命名空间。这里重点介绍Directory类的使用方法。
6.3.1 Directory类常用的方法
Directory类常用的方法如下,这些方法都是静态方法。
l CreateDirectory:按参数指定路径创建所有目录(文件夹)和子目录。
l Delete:删除参数指定目录。
l Exists:检查参数指定路径的文件夹是否存在,存在,返回true。
l GetCreationTime:返回参数指定文件或文件夹的创建日期和时间。
l GetCurrentDirectory:获取应用程序的当前工作目录。
l GetDirectories:返回字符串数组,该数组记录参数指定文件夹中所有子文件夹的名称。
l GetDirectoryRoot:返回参数指定路径的卷信息、根信息或同时包括这两者的字符串。
l GetFiles:返回字符串数组,该数组记录参数指定文件夹中所有文件的名称。
l GetFileSystemEntries:返回字符串数组,记录参数指定目录中所有子目录和文件名称。
l GetLastAccessTime:返回参数指定的文件或文件夹的最后一次访问日期和时间。
l GetLastWriteTime:返回参数指定的文件或文件夹的最后修改日期和时间。
l GetLogicalDrives:返回字符串数组,数组记录计算机所有驱动器名称,如A:、C:等。
l GetParent:返回参数指定路径的父文件夹,包括绝对路径和相对路径。
l Move:将参数指定文件或文件夹及包含的文件、子文件夹移动到新位置。
l SetCreationTime:设置参数指定文件或文件夹的创建日期和时间。
l SetCurrentDirectory:将参数指定的目录设置为应用程序的当前工作目录。
l SetLastAccessTime:设置参数指定的文件或文件夹的最后一次访问日期和时间。
l SetLastWriteTime:设置参数指定的文件夹的最后一次修改日期和时间。
6.3.2 判断目录是否存在的方法Exists
方法声明如下:public static bool Exists(string path);该方法判断参数指定目录是否存在,参数path指定目录的路径。如果目录存在,返回true,如果目录不存在,或者访问者不具有访问此目录权限,返回false。下面代码判断是否存在c:\Dir1\Dir2目录。
if(Directory.Exists(@"c:\Dir1\Dir2")) {…}//处理代码
6.3.3 目录创建方法CreateDirectory
方法声明如下:public static DirectoryInfo CreateDirectory(string path);方法按参数path指定的路径创建所有目录及其子目录。如果由参数path指定的目录已存在,或者参数path指定的目录格式不正确,将引发异常。下面的程序创建按用户输入的目录名创建目录,具体程序如下:
using System;
using System.IO;//使用文件必须引入的命名空间
class CreateFileDirectory
{ static void Main()
{ Console.WriteLine("请键入要创建的目录路径:");
string path=Console.ReadLine();//从键盘读入路径,输入回车结束
if(!Directory.Exists(@path))//@表示其后字符串不包括转义字符
Directory.CreateDirectory(@path);
else
Console.WriteLine("目录已存在或目录非法!");
}
}
6.3.4 目录删除方法Delete
方法声明如下:public static void Delete(string path,bool recursive);该方法删除参数path指定的目录,可以是相对或绝对路径。方法的第二个参数为bool类型,它可以决定是否删除非空目录。如果该参数值为true,将删除整个目录,即使该目录下有文件或子目录;若为false,则仅当目录为空时才可删除。下面的程序删除用户指定目录。
using System;
using System.IO;//使用文件必须引入的命名空间
class DeleteFile
{ static void Main()
{ Console.WriteLine("请键入要删除的目录的路径:");
string path=Console.ReadLine();//从键盘读入路径,输入回车结束
if(Directory.Exists(@path))//@表示其后字符串不包括转义字符
Directory.Delete(@path);
else
Console.WriteLine("目录不存在或目录非法!");
}
}
6.3.5 目录移动方法Move
方法声明如下:public static void Move(string sourceDirName,string destDirName);该方法将将文件或目录及其子目录移到新位置,如果目标目录已经存在,或者路径格式不对,将引发异常。注意,只能在同一个逻辑盘下进行目录转移。如果试图将c盘下的目录转移到d盘,将发生错误。下面的代码可以将目录c:\Dir1\Dir2移动到c:\Dir3\Dir4。Directory.Move(@"c:\Dir1\Dir2",@"c:\Dir3\Dir4");}
Directory Info类方法MoveTo可以将一个逻辑盘的目录移到另一个逻辑盘,例子如下:
using System;
using System.IO;//使用文件必须引入的命名空间
class DeleteFile
{ static void Main()
{ Console.WriteLine("请键入要移动的源目录的路径:");
string path=Console.ReadLine();//从键盘读入路径,输入回车结束
Console.WriteLine("请键入要移动的目的目录的路径:");
string path1=Console.ReadLine();//从键盘读入路径,输入回车结束
if(Directory.Exists(@path))//@表示其后字符串不包括转义字符
{ if(!Directory.Exists(@path1))
{ DirectoryInfo dir=new DirectoryInfo(@path);//必须建立对象
dir.MoveTo(@path1);//dir所代表的目录移到path1指定的位置
//Directory.Move(@path,@path1);
}//如两个目录在同一磁盘中,可用被注解的语句替换前2句
else
Console.WriteLine("目的目录已存在!");
}
else
Console.WriteLine("源目录不存在!");
}
}
6.3.6 获取当前目录下所有子目录方法GetDirectories
该方法声明如下:public static string[] GetDirectories(string path);下面的程序读出用户指定目录下的所有子目录,并将其在屏幕显示。
using System;
using System.IO;//使用文件必须引入的命名空间
class DeleteFile
{ static void Main()
{ Console.WriteLine("请键入目录的路径:");//例如键入:d:/
string path=Console.ReadLine();//从键盘读入路径,输入回车结束
if(Directory.Exists(@path))//@表示其后字符串不包括转义字符
{ string[] Directorys;
Directorys=Directory.GetDirectories(@path);
foreach(string aDir in Directorys)
Console.WriteLine(aDir);
}
else
Console.WriteLine("目录不存在!");
}
}
获得所有逻辑盘符方法定义如下:
string[] AllDrivers=Directory.GetLogicalDrives();
6.3.7 获取当前目录下的所有文件方法GetFiles
该方法声明如下:public static string[] GetFiles(string path);下面的程序读出用户指定目录下的所有文件名,并将文件在屏幕上显示。
using System;
using System.IO;//使用文件必须引入的命名空间
class DeleteFile
{ static void Main()
{ Console.WriteLine("请键入目录的路径:");
string path=Console.ReadLine();//从键盘读入路径,输入回车结束
if(Directory.Exists(@path))//@表示其后字符串不包括转义字符
{ string[] files;
files=Directory.GetFiles(@path);
foreach(string aFile in files)
Console.WriteLine(aFile);
}
else
Console.WriteLine("目录不存在!");
}
}
6.3.8 目录属性设置方法Atttributes
与文件属性相同,目录属性也是使用FileAttributes来进行设置的。下面的代码设置c:\Dir1\Dir2目录为只读、隐藏。
DirectoryInfo DirInfo=new DirectoryInfo(@"c:\Dir1\Dir2");
DirInfo.Atttributes=FileAttributes.ReadOnly|FileAttributes.Hidden;
6.4 例子:查找文件
本节实现一个查找指定驱动器中指定文件的程序。
6.4.1 Panel和ListView控件
Panel是一个可以包含其它控件的控件,例如包含一组RadioButton控件。如果Panel控件的Enabled属性设置为false,则也会禁用包含在Panel中的所有控件。默认情况下,Panel控件在显示时没有任何边框。可以用BorderStyle属性提供标准或三维的边框,将窗体分为不同区域。因为Panel控件派生于ScrollableControl类,所以可以用AutoScroll属性来启用Panel控件中的滚动条。当AutoScroll属性设置为true时,使用所提供的滚动条可以滚动显示在Panel中,但不在其可视区域内的所有控件。
ListView控件可用4种不同视图(详细资料、大图标、列表、小图标)中的一种显示一些项的集合,ListView控件的外观与Windows 资源管理器的文件列表外观相似。其常用属性、方法和事件如下:
l 属性Columns:所有列标头的集合,可用"ColumnHeader集合编辑器"对话框修改。
l 属性MultiSelect:获取或设置一个值,该值指示是否可以选择多个项。
l 属性SelectedIndices:获取控件中选定项的索引集合。用下式得到这个集合:
ListView.SelectedIndexCollection I=ListView1.SelectedIndices;
其中I.Length表示选择了多少项,I[0]为选择的第1项的索引,I[1]为选择的第2项的索引等。ListView1.Items[I[0]].SubItems[0].Text表示第1个选项的第一个子项的显示内容。
l 属性SelectedItems:获取控件中选定项的集合。 用下式得到这个集合:
ListView.SelectedListViewItemCollection L=ListView1.SelectedItems;
其中L.Length表示选择了多少项,L[0]为选择的第1项,L[1]为选择的第2项等等。L[0].SubItems[0].Text表示第1个选项的第一个子项的显示内容。
l 属性View:获取或设置项在控件中的显示方式,可以是Detail(详细资料)、LargeIcon(大图标)、List(列表)、SmallIcon(小图标)。
l 方法Clear:从控件中移除所有项的内容。
l 方法DragDropEffects DoDragDrop(object data,DragDropEffects allowEffects):
开始拖放操作,参数data是要传送的数据,可以是String、Bitmap、IDataObject、Metafile类的对象,或者是实现序列化的数据等。参数2确定哪些拖动操作可以发生,可以是DragDropEffects.Copy、All、Link、Move、None、Scroll。
l 事件ItemDrag:鼠标开始拖动产生的事件。应用见例子e6_5。
l 事件DragEnter:鼠标进入拖动目的控件产生的事件。
l 事件DragDrop:鼠标进入拖动目的控件,并抬起鼠标产生的事件。
l 事件SelectedIndexChanged:当列表视图控件中选定的项的索引更改时发生。
6.4.2 在指定驱动器中查找文件
例子e6_4_2:Windows操作系统提供了一个查找文件的程序,可以查找指定文件夹中的指定文件,本例实现了在指定驱动器中查找指定文件。具体实现步骤如下:
(1) 新建项目。放Panel控件到窗体,属性Dock=Left。Panel控件可以把窗体分割为多个部分,这里将窗体分割为左右两部分。
(2) 放置Label控件到Panel控件中,属性Text为"指定搜索文件的名称"。在Panel控件中增加一个TextBox控件,位置在Label控件下方,属性Name=FileBox,属性Text为空,用来输入要搜索的文件名称。
(3) 放置Label控件到Panel控件中,属性Text为"指定在哪个驱动器中搜索"。在Panel控件中增加一个ComboBox控件,属性Name=DriverComBox,属性Text为空,属性DropDownStyle=DropDownList,用来输入要搜索的驱动器。
(4) 为Form1类的构造函数增加如下语句:
string[] AllDrivers=Directory.GetLogicalDrives();//获得所有逻辑盘符
for(int i=0;i<AllDrivers.Length;i++)//逻辑盘符增加到DriverComBox下拉列表中
DriverComBox.Items.Add(AllDrivers[i]);
(5) 在Panel控件中增加2个Button控件,属性Name分别为Start和Stop,属性Text分别为"开始搜索"和"停止搜索"。
(6) 放分割器控件Splitter到窗体,属性Dock=Left。
(7) 在分割器控件右侧放置视图控件ListView,属性Dock=fill,属性View=Detail。点击属性Column右侧标题为"…"的按钮,在弹出的ColumnHeader集合编辑器对话框(如下图)中添加4个列头,属性Name分别为:FileName、FileDirectory、FileSize和LastWriteTime,属性Text分别为:文件名称、所在文件夹、大小和文件修改时间。
(8) 为窗体增加一个方法:FindFiles(DirectoryInfo dir,string FileName),该方法是在第一个参数指定的文件夹中查找第二个参数指定的所有文件。在一个文夹中可能还有子文件夹,子文件夹中可能还有子文件夹,因此要在第一个参数指定的文件夹中和其子文件夹中查找第二个参数指定的所有文件。为了实现能够查找所有文件夹中的同名文件,采用递归调用方法,如果在一个文件夹中存在子文件夹,再一次调用函数自己,查找子文件夹中的文件。具体实现代码如下:
void FindFiles(DirectoryInfo dir,string FileName)
{ FileInfo[] files=dir.GetFiles(FileName);//查找指定文件并在ListView中显示
if(files.Length!=0)
{ foreach(FileInfo aFile in files)
{ ListViewItem lvi;
string[] s=new String[]{aFile.Name,aFile.Directory.FullName,
aFile.Length.ToString(),aFile.LastWriteTime.ToString()};
lvi=new ListViewItem(s);
listView1.Items.Add(lvi);//注意增加一行的方法
}
}
DirectoryInfo[] dirs=dir.GetDirectories();//查找指定目录中的所有子目录
if(dirs.Length!=0)
{ foreach(DirectoryInfo aDir in dirs)
FindFiles(aDir,FileName);//递归调用
}
}
(9) 为标题为"开始搜索"的按钮增加单击事件处理函数如下:
private void Start_Click(object sender, System.EventArgs e)
{ if((FileBox.Text=="")&&(DriverComBox.Text==""))
MessageBox.Show("文件名称和驱动器名称不能为空");
else
{ int n=FileBox.Text.IndexOf(".");//有无扩展名
if(n==-1)//如无扩展名,增加".*"为扩展名
FileBox.Text+=".*";
DirectoryInfo dir=new DirectoryInfo(DriverComBox.Text);
FindFiles(dir,@FileBox.Text);
}
}
(10) 按以上设计,无法在标题为"停止搜索"按钮的单击事件处理函数中用代码停止搜索,可行办法是,查找文件在另一个线程中进行,在单击"停止搜索"按钮后,停止搜索线程。
(11) 编译、运行,看是否能找到指定文件。查找D:盘所有*.doc文件的运行效果如下图。
图6.4.2
6.5 例子:用鼠标拖放打开一个文件
本例将图6.4.2中的图像文件拖到PictureBox控件中,打开并显示图像。进行拖放操作的组件的AllowDrop属性必须设定为True,因为此属性是决定组件是否可以进行拖放操作。要完成拖放操作,必须处理好三个事件:ItemDrag、DragEnter、DragDrop。其中第一个事件是在源组件中触发的,另外2个事件是在目标组件中触发的。当用户拖动组件触发ItemDrag事件;当拖动数据进入目标组件区域触发DragEnter事件;当用户在目标组件区域放置拖动的数据触发DragDrop事件。实现具体步骤如下:
(12) 设置ListView控件属性MultiSelect=false,不允许多选。
(13) 放PictureBox控件到窗体。属性Name=pictureBox1,在构造函数中设置属性AllowDrop=true。
(14) 为ListView的ItemDrag事件增加事件处理函数如下:
private void listView1_ItemDrag(object sender,
System.Windows.Forms.ItemDragEventArgs e)
{ if(e.Button==MouseButtons.Right)
return;
int n=listView1.SelectedIndices.Count;
if(n!=1)
return;//如果未选择任何项,退出
ListView.SelectedIndexCollection I=listView1.SelectedIndices;
string s=listView1.Items[I[0]].SubItems[1].Text+"\\";
s+=listView1.Items[I[0]].SubItems[0].Text;
if(s.Length==0)
return;
//下句将字符串s拷贝到目的地。也可用DragDropEffects.Move,表示移动。
listView1.DoDragDrop(s,DragDropEffects.Copy);
}
(15) 为pictureBox1的事件DragEnter增加事件处理函数如下:
private void pictureBox1_DragEnter(object sender,
System.Windows.Forms.DragEventArgs e)
{ if(e.Data.GetDataPresent(DataFormats.Text))
e.Effect=DragDropEffects.Copy;
else
e.Effect=DragDropEffects.None;
}
(16) 为pictureBox1的事件DragDrop增加事件处理函数如下:
private void pictureBox1_DragDrop(object sender,
System.Windows.Forms.DragEventArgs e)
{ if(e.Data.GetDataPresent(typeof(System.String))&&
e.Effect==DragDropEffects.Copy)
{ string s=(string)e.Data.GetData(typeof(System.String));
Bitmap bits=new Bitmap(s);//建立指定文件的新位图对象
pictureBox1.Image=bits;
}
}
(17) 编译、运行,查找D:盘所有*.bmp文件,拖动任一.bmp文件到pictureBox1中,看是否能打开这个图形文件显示图像。
6.6 例子:拆分和合并文件
在将一个文件作为电子邮件的附件传送时,由于附件的大小有限制,不能传送太大的文件。可以将较大的文件分割为多个较小的文件,传送后再合并为一个文件,下边两个方法实现文件的拆分和合并。首先是拆分方法,参数1是要拆分文件的路径(路径包括文件名及扩展名),参数2是拆分后的文件名(无扩展名),文件名后边由拆分方法自动增加序号和扩展名,参数3是被拆分后的文件大小,单位为字节。拆分方法定义如下:
void SplitFile(string f1,string f2,int f2Size)
{ FileStream inFile=new FileStream(f1,FileMode.Open,FileAccess.Read);//只读
bool mark=true;
int n,i=0;
byte[] buffer=new byte[f2Size];//读入文件数据存到数组buffer
FileStream OutFile=null;
while(mark)
{ if((n=inFile.Read(buffer,0,f2Size))>0)
{ OutFile=new FileStream//如有同名文件,将被删除
(f2+i.ToString()+".fsm",FileMode.Create,FileAccess.Write);
OutFile.Write(buffer,0,n);
i++;
OutFile.Close();
}
else
mark=false;
}
inFile.Close();
}
合并文件方法,参数1是要合并在一起的文件名(路径包括文件名及扩展名),参数2是拆分的文件名(无扩展名,无序号),文件名后边由合并方法自动增加序号和扩展名fsm,要将这些文件合并到参数1指定的文件,参数3是要合并的文件数。合并方法定义如下:
void MergeFile(string f1,string f2,int f2Num)
{ //如有同名文件,将被删除
FileStream OutFile=new FileStream(f1,FileMode.Create,FileAccess.Write);
long n,l;
byte[] buffer;
for(int i=0;i<f2Num;i++)
{ FileStream InFile=new FileStream//只读文件
(f2+i.ToString()+".fsm",FileMode.Open,FileAccess.Read);
l=InFile.Length;
buffer=new byte[l];
n=InFile.Read(buffer,0,(int)l);
OutFile.Write(buffer,0,(int)n);
InFile.Close();
}
OutFile.Close();
}
下边介绍两个方法的使用。首先在D:/建立文件夹g,在文件夹g中建立一个小于20k的Word文档g.doc。先调用方法SplitFile("d:/g/g.doc","d:/g/g",11000)拆分,然后调用方法MergeFile("d:/g/k.doc","d:/g/g",2) 合并,看是否可以用Word程序打开合并后的文档。