C#文件读取与多线程

Apr 19,2017   4006 words   15 min

Tags: Others

针对近期写的一个C#程序对一些不太了解的知识进行简单总结。

1.打开多个文件

与打开单个文件使用OpenFileDialog类似,对于打开多个文件,只需要设置OpenFileDialog的Multiselect属性为true即可。 VS的描述信息如下: 这是一个bool类型的属性,当为true时表示支持文件多选,false表示不支持多选。此外,还需要接收多个文件路径。 在之前打开单个文件时,一般通过直接获取FileName属性来得到路径。在多选的时候则需要通过FileNames来获取。 获取到的是个string类型的数组。因此我们需要事先建立一个string数组用于接收文件路径。部分代码如下:

String[] inputFiles;
OpenFileDialog open = new OpenFileDialog();
open.Multiselect = true;
open.Title = "请选择文件";
open.Filter = "JPG(*.jpg)|*.jpg|PNG(*.png)|*.png";
if (open.ShowDialog() == DialogResult.OK)
{
    inputFiles = open.FileNames;
}
else
{
    return;
}

2.以字节方式读取文件

在C#中可以通过Stream和字节数组来读取文件的字节,具体代码如下:

Stream stream = File.OpenRead(inputfiles[i]);
int length = (int)stream.Length;
byte[] buffer = new byte[length];
stream.Read(buffer, 0, length);

在使用这些之前别忘了添加引用using System.IO;。 这几行代码表示的意思是,首先,通过File中的OpenRead函数打开inputfiles[i]指向的文件, 并将结果返回给流。然后获取流的长度并将其转换为int型。基于流的长度新建一个byte数组。 最后通过Stream的Read()函数将文件内容以字节形式读取到buffer数组中来。这样便完成了文件的读取。

3.字节数组读取

(1)单字节解析

在上面我们已经读取了字节数组,现在如需要对某个字节进行输出或转换,可以直接利用ToString()函数完成。 如对buffer[0]进行输出,只需要如下代码即可:

buffer[0].ToString();

则会以十进制来输出该字节,这里读取的结果为137。当然也可以以十六进制输出,则可以在ToString()函数中 增加格式字符串来格式化输出,如“X”表示十六进制输出,“X2”表示将输出对齐为2位。若x为小写,则输出的字母也为小写。 假设有两个数10和26,正常情况十六进制显示0xA、0x1A,这样看起来不整齐,为了好看,可以指定”X2”,这样显示出来就是:0x0A、0x1A。

(2)多字节解析

对于一个占两个字节的数据,我们可以通过将两个字节转换成十六进制再拼成字符串,便能得到对应16进制表示。

String str = buffer[0].ToString("X2") + buffer[1].ToString("X2");

如读取某文件,校验码为文件的前两位字节,因此我们可以依次读取字节数组的前两个元素,再拼成字符串便能得到校验码。 在这里第一个字节对应的十六进制表示为“EB”,第二个是“90”。因此校验码对应的字符串即为“EB90”。

(3)多字节转int

在获取了多字节对应的十六进制string后,我们可以利用int类型的parse()函数实现转换。

String time = UInt32.Parse(buffer[i].ToString("X2") + buffer[i+1].ToString("X2") + buffer[i+2].ToString("X2") + buffer[i+3].ToString("X2"), System.Globalization.NumberStyles.HexNumber).ToString();

上述代码解析了一个四个字节的整型变量,然后再将其变成字符串赋给time。对于Parse()函数,说明如下:

(4)多字节转float

对于多个字节转float与之前有些不太一样。需要用到BitConverter这个类,用于将基础数据类型与字节数组相互转换。

byte[] bytetemp = new byte[4];
bytetemp[0] = buffer[i];
bytetemp[1] = buffer[i+1];
bytetemp[2] = buffer[i+2];
bytetemp[3] = buffer[i+3];
float ftemp = BitConverter.ToSingle(bytetemp, 0);

首先建立了一个临时的数组用于存放4个字节。然后调用ToSingle()函数将4个字节转换成对应的float类型。 第一个参数为要转换的数组,第二个参数为起始偏移量。

4.多线程

在WinForm中与Android类似,如果在UI线程中进行了大量计算,那么这些大量的计算就会阻塞UI线程。直观的反映就是造成界面假死, 待计算完成后又重新恢复。解决这个问题可以效仿Android编程思路,再开一个子线程专门用于计算,形成“UI线程+子线程”的模式。 UI线程只负责对计算进度的更新和结果的显示。同时与Android类似的是,在子线程中操作UI组件会被认为是不安全的而被阻止。 在Android中是利用Handler解决。但在C#中并没有Handler。在C#中采用托管的方式处理。
首先,我们在需要启动子线程的按钮的点击事件中这样写:

progressBar1.Maximum = inputfiles.Length;
Thread t1 = new Thread(new ThreadStart(SaveFunc));
t1.IsBackground = true;
t1.Start();

progressBar1用于显示完成进度。这里设置最大值为输入文件的个数。然后新建一个线程,使用之前需要using System.Threading;。 其中SaveFunc()是我们自定义的一个保存文件的函数。代码如下:

void SaveFunc() 
{
    Action<String> task = delegate(string n)
    {
        progressBar1.Value = Int16.Parse(n);
    };
    for (int j = 0; j < inputfiles.Length; j++)
    {
		//your code...
        progressBar1.Invoke(task, new object[] { (j + 1).ToString() });
    }
}

在循环中,调用了progressBar1的Invoke()函数,每次执行后,便会将progressBar1的进度加一,直到100%。当然task内容还可以更加丰富:

Action<String> task = delegate(string n)
{
    groupBox2.Text="处理进度 "+((float.Parse(n) / inputfiles.Length) * 100).ToString("f2") + "%";
    label1.Text = "当前处理第 " + n + " 个, 共 " + inputfiles.Length + " 个";
    progressBar1.Value = Int16.Parse(n);
};

这里给出了当前处理的文件序号、百分比以及进度条,效果如图所示。 这样便能简单实现不阻塞UI线程的计算了。

C#文件路径

(1)判断文件夹是否存在

在保存文件时,有时需要新建一个文件夹。这个时候可以使用Directory类判断(包含在System.IO)中。

if (!System.IO.Directory.Exists(savepath))
{
    System.IO.Directory.CreateDirectory(savepath);
}

上述代码利用Exist()函数判断savepath路径是否存在,如果不存在,就调用CreateDirectory()函数创建一个。

(2)路径拼接、截取

例如我们打开的文件路径为”E:\test.png”。现在我们读取了1.png并对其进行了一系列处理得到了一幅新的影像。想将其 存放到指定路径如”E:\output\test.jpg”下。首先需要判断E盘中是否有output文件夹,如果没有需要新建一个。然后需要对 输入路径进行截取,截取最后一个“\”到“.”之间的字符串,也就是文件名。然后再拼接上”.bmp”。代码如下:

string savepath_img;
String savepath = inputfiles[0].Substring(0, inputfiles[0].LastIndexOf('\\'));
savepath += "\\output";
if (!System.IO.Directory.Exists(savepath))
{
    System.IO.Directory.CreateDirectory(savepath);
}
savepath_img = savepath + inputfiles[j].Substring(inputfiles[j].LastIndexOf('\\'), inputfiles[j].LastIndexOf('.') - inputfiles[j].LastIndexOf('\\')) + ".bmp";

需要注意的是Substring()中的参数。第一个参数为起始索引,第二个参数是截取长度,而不是结束索引,这点要注意。

本文作者原创,未经许可不得转载,谢谢配合

返回顶部