C#OOP CH09 文件操作
本章目标
- 掌握文本文件的读写
- 会进行文件和文件夹操作
1. 文件操作
1.1 如何读写文本文件
通常来讲,用C#程序读写一个文件需要以下五个基本步骤:
- 创建文件流
- 创建阅读器或者写入器
- 执行读写操作
- 关闭阅读器或者写入器
- 关闭文件流
代码演示如下:
写:
string path = this.txtPath.Text.Trim();
string context = this.txtContent.Text.Trim();
//创建文件流
FileStream fs = File.Create(path);
//创建写入流
StreamWriter sw = new StreamWriter(fs);
//执行写入操作
sw.Write(content);
//关闭写入流
sw.Close();
//关闭文件流
fs.Close();
读:
string path = this.txtPath.Text.Trim();
//创建文件流
FileStream fs = File.OpenRead(path);
//创建读取流
StreamReader sr = new StreamReader(fs);
//执行读取操作
string content = sr.ReadToEnd();
this.txtContent.Text = content;
//关闭读取流
sr.Close();
//关闭文件流
fs.Close();
简化步骤如下:
- 创建文件流
- 调用方法执行读写操作
- 关闭文件流
简化代码演示如下:
写:
using (FileStream fs = File.Create(path))
{
byte[] info = new UTF8Encoding(true).GetBytes(content);
fs.Write(info, 0, info.Length);
}
读:
using (FileStream fs = File.OpenRead(path))
{
byte[] b = new byte[1024];
UTF8Encoding temp = new UTF8Encoding(true);
StringBuilder sb = new StringBuilder();
while (fs.Read(b, 0, b.Length) > 0)
{
sb.Append(temp.GetString(b));
}
this.txtContent.Text = sb.ToString();
}
1.2 创建文件流
-
创建文件流时,需要在它的构造函数中指定参数,参数说明如下:
-
关闭文件流:
写入结束后一定要关闭文件流 myfs.Close()。
1.3 创建写入器和读取器
-
文本文件的写入器和读取器方法如图所示:
-
解决乱码问题:
使用StreamReader读取文件中的中文文本,有时会产生乱码问题。这并不是因为C#语言有问题,而是因为不同文件编码格式可能不同。如果在编码时给文件读取器对象指定对应的编码格式,问题就迎刃而解了。
以下代码展示了如何使用指定的编码格式:
StreamReader sr = new StreamReader("文件名",Encoding.Default);
-
可以通过Encoding类的静态成员指定编码格式。例如:
Encoding.UTF8:获取UTF-8格式的编码
Encoding.Default:获取操作系统的当前编码
-
也可以通过Encoding类的静态方法GetEncoding(string name) 指定字符编码,参数name必须是C#支持的编码名。例如:
StreamReader sr = new StreamReader("文件名",Encoding.GetEncoding("GB2312"))
常见的编码可以参考官网:https://docs.microsoft.com/zh-cn/dotnet/standard/base-types/character-encoding
-
-
使用写入器和读取器简化读写操作如下:
写:
using (StreamWriter sw = new StreamWriter(path,false,Encoding.UTF8)) { sw.Write(this.txtContent.Text); }
读:
using (StreamReader sr = new StreamReader(path,Encoding.UTF8)) { StringBuilder sb = new StringBuilder(); while(sr.Peek() > -1) { sb.AppendLine(sr.ReadLine()); } this.txtContent.Text = sb.ToString(); }
1.4 网络电视精灵读写案例演示
需求:
将定制的频道信息写入文本文件
实现思路:
- 编写SaveAsText()方法,实现将定制的频道信息存入.txt文件
- 编写主窗体的FormClosed的事件,调用SaveAsText()方法
修改ChannelBase.cs,增加SaveAsText()方法,代码如下:
/// <summary>
/// 频道管理类
/// </summary>
public class ChannelManager
{
//省略其他代码
private string file = @"files/save.txt";
private List<ChannelBase> list = new List<ChannelBase>();
public List<ChannelBase> ChannelBaseList => list;
public void SaveAsText()
{
try
{
using (StreamWriter writer = new StreamWriter(file,false,Encoding.UTF8))
{
list.ForEach(c =>
writer.WriteLine(c.ChannelType + "|"
+ c.ChannelName + "|" + c.Path));
}
}
catch(Exception e)
{
Console.WriteLine("写入文件异常,异常是:" + e.ToString());
}
}
}
编写主窗体的FormClosed的事件,调用SaveAsText()方法,关键代码如下:
private void FrmTV_FormClosed(object sender, FormClosedEventArgs e)
{
//先清空频道集合中的数据
channelManager.ChannelBaseList.Clear();
//判断我电视台中是否有子节点
if(this.tvChannel.Nodes[0].Nodes.Count > 0)
{
foreach (TreeNode node in tvChannel.Nodes[0].Nodes)
{
//将频道加入到集合中
ChannelBase channel = node.Tag as ChannelBase;
channelManager.ChannelBaseList.Add(channel);
}
}
//写入文件
channelManager.SaveText();
//MessageBox.Show("写入成功");
Application.Exit();
}
需求:
读取定制频道信息
实现思路:
- 从文本文件save.txt 读取定制的频道信息,并在主窗体运行时加载,最终实现定制频道信息的读取
修改ChannelManager.cs,增加ReadText方法,代码如下:
//读取文件
public void ReadText()
{
try
{
if (!File.Exists(fileName)) return;//若文件不存在 则不执行读取操作
using (StreamReader sr = new StreamReader(fileName,Encoding.UTF8))
{
while (sr.Peek() > -1)
{
string[] paramValue = sr.ReadLine().Split('|');
ChannelBase channel = CreateChannelBase(paramValue[0],paramValue[1],paramValue[2]);
ChannelBaseList.Add(channel);
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
修改主窗体的窗体加载事件,代码如下:
public partial class FrmTV : Form
{
ChannelManager channelManager = new ChannelManager();
private void FrmTV_Load(object sender, EventArgs e)
{
//读取文件
channelManager.ReadText();
TreeNode rootNode = new TreeNode("我的电视台",0,1);
this.tvChannels.Nodes.Add(rootNode);
rootNode = new TreeNode("所有电视台",0,1);
foreach (ChannelBase item in channelManager.FullChannels.Values)
{
TreeNode node = new TreeNode(item.ChannelName,0,1);
node.Tag = item;
node.Name = item.ChannleName;
rootNode.Nodes.Add(node);
}
this.tvChannels.Nodes.Add(rootNode);
//我的电视台
if(channelManager.ChannelBaseList.Count > 0)
{
foreach (ChannelBase item in channelManager.ChannelBaseList)
{
TreeNode node = new TreeNode(item.ChannelName, 0, 1);
node.Tag = item;
node.Name = item.ChannleName;
this.tvChannels.Nodes[0].Nodes.Add(node);
}
}
this.tvChannels.ExpandAll();
}
//省略其他代码
}
2. 文件和目录操作
2.1 File 类
2.2 Directory 类
2.3 静态类与非静态类
2.4 静态成员与实例成员
2.4 FileInfo 类
2.5 DirectoryInfo 类
3. 文件的综合运用
开发小型资源管理器,可以浏览文件信息
3.1 读取目录下的子目录信息
先来完成第一个功能。操作目可以Directory类和DirectoryInfo类。由于资源管理器中的目录需要多次使用,所以优先选择DirectoryInfo类。如下示例所示:使用DirectoryInfo类获取D盘下的所有目录,并将目录信息显示在TreeView树状菜单中。
窗体类中,编写代码如下:
//窗体加载
private void FrmFile_Load(object sender, EventArgs e)
{
//加载盘符
LoadDrivers();
}
public void LoadDrivers()
{
string path = @"D:\";
TreeNode node = new TreeNode(path);
node.Tag = path;
this.tvDic.Nodes.Add(node);
}
//节点选中改变事件
private void tvDic_AfterSelect(object sender, TreeViewEventArgs e)
{
//获取当前选中的节点
TreeNode node = this.tvDic.SelectedNode;
//填充treeview
BindInfo(node);
}
//将目录绑定到TreeView中
public void BindInfo(TreeNode node)
{
//创建目录对象
DirectoryInfo directoryInfo = new DirectoryInfo(node.Tag.ToString());
//加载子目录
DirectoryInfo[] dirs = directoryInfo.GetDirectories();
dirs.ForEach(dir =>
{
TreeNode temp = new TreeNode(dir.Name);
temp.Tag = dir.FullName;
node.Nodes.Add(temp);
});
//foreach (DirectoryInfo dir in dirs)
//{
// TreeNode temp = new TreeNode(dir.Name);
// temp.Tag = dir.FullName;
// node.Nodes.Add(temp);
//}
}
创建MyExMethod类,扩展ForEach方法,代码如下:
public static class MyExMethods
{
public static void ForEach<T>(this IEnumerable<T> source,Action<T> action)
{
foreach (var item in source)
{
action(item);
}
}
}
3.2 读取目录下的文件信息
读取文件信息的方式和读取目录信息的方式大同小异。为了表示方便,我们定义一个MyFile类来存储文件信息。MyFile类的属性如下表所示:
属性 | 类型 | 说明 |
---|---|---|
FileLength | float | 文件长度,以KB为单位 |
FileName | string | 文件名 |
FilePath | string | 文件路径 |
FileType | string | 文件类型 |
创建MyFile类,封装文件的各种信息,代码如下:
public class MyFile
{
public string FileName { get; set; }//文件名
private float fileLength;
public float FileLength//文件大小
{
get
{
return fileLength;
}
set
{
fileLength = (float)Math.Round(value / 1024, 2);
}
}
public string FileType { get; set; }//文件类型
public string FilePath { get; set; }//文件路径
public MyFile(string fileName, float fileLength, string fileType, string filePath)
{
this.FileName = fileName;
this.FileLength = fileLength;
this.FileType = fileType;
this.FilePath = filePath;
}
}
单击树状菜单上的目录节点,绑定该目录下文件的基本信息。代码如下:
//将目录绑定到TreeView中 并且加载目录下的子文件
public void BindInfo(TreeNode node)
{
//创建目录对象
DirectoryInfo directoryInfo = new DirectoryInfo(node.Tag.ToString());
//加载子目录
DirectoryInfo[] dirs = directoryInfo.GetDirectories();
dirs.ForEach(dir =>
{
TreeNode temp = new TreeNode(dir.Name);
temp.Tag = dir.FullName;
node.Nodes.Add(temp);
});
//foreach (DirectoryInfo dir in dirs)
//{
// TreeNode temp = new TreeNode(dir.Name);
// temp.Tag = dir.FullName;
// node.Nodes.Add(temp);
//}
//加载子文件
FileInfo[] fileInfo = directoryInfo.GetFiles();
List<MyFile> files = new List<MyFile>();
fileInfo.ForEach(file =>files.Add(
new MyFile(file.Name, file.Length, file.Extension, file.FullName)));
//foreach (FileInfo file in fileInfo)
//{
// MyFile myFile = new MyFile();
// myFile.FileName = file.Name;
// myFile.FileLength = file.Length;
// myFile.FileType = file.Extension;
// myFile.FilePath = file.FullName;
// files.Add(myFile);
//}
//绑定ListView
BindListView(files);
}
//绑定ListView
public void BindListView(List<MyFile> files)
{
this.lvFile.Items.Clear();
files.ForEach(item =>
{
ListViewItem listViewItem = new ListViewItem(item.FileName);
listViewItem.SubItems.Add(item.FileLength.ToString());
listViewItem.SubItems.Add(item.FileType);
listViewItem.SubItems.Add(item.FilePath);
this.lvFile.Items.Add(listViewItem);
});
//foreach (MyFile item in files)
//{
// ListViewItem listViewItem = new ListViewItem(item.FileName);
// listViewItem.SubItems.Add(item.FileLength.ToString());
// listViewItem.SubItems.Add(item.FileType);
// listViewItem.SubItems.Add(item.FilePath);
// this.lvFile.Items.Add(listViewItem);
//}
}
3.3 实现文件复制
需求说明
- 完善小型资源管理器,实现文件复制
- 支持用户从“浏览文件夹”对话框选定目标位置
实现思路
- 打开“浏览文件夹”,并获得选择的存储路径,实现代码如下
//复制
private void tsmiCopy_Click(object sender, EventArgs e)
{
if (this.lvFile.SelectedItems.Count == 0) return;
FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog();
DialogResult result = folderBrowserDialog.ShowDialog();
if (result == DialogResult.No) return;
File.Copy(this.lvFile.SelectedItems[0].SubItems[3].Text,
folderBrowserDialog.SelectedPath + "\\" + this.lvFile.SelectedItems[0].Text);
MessageBox.Show("复制成功");
}
3.4 实现文件删除
需求说明
-
完善小型资源管理器,实现文件删除,并刷新ListView显示
实现思路 -
可以使用File类的Delete(string filePath)方法,实现代码如下:
//删除
private void tsmiDel_Click(object sender, EventArgs e)
{
if (this.lvFile.SelectedItems.Count == 0) return;
DialogResult result = MessageBox.Show("确定要删除该文件吗?", "提示", MessageBoxButtons.YesNo);
if (result == DialogResult.No) return;
File.Delete(this.lvFile.SelectedItems[0].SubItems[3].Text);
MessageBox.Show("删除成功");
//刷新
this.lvFile.SelectedItems[0].Remove();
}