文件与数据
1. 文件与目录
(1) 文件/目录操作的相关类型
与文件/目录操作有关的类型主要分布在Windows.Storage命名空间及该命名空间下的子命名空间中。下面列出了与文件/目录操作有关的几个重要类型。
- StorageFile 表示一个文件类型,通过该类,可以对某个文件进行一些常用操作,例如重命名、删除、获取基本属性等
- StorageFolder 表示一个目录实例,可以对某个目录进行重命名、复制、删除等操作
- KnownFolders 一个静态类型,公开一系列静态属性,通过这些属性可以直接获取特殊目录的引用,如图片库、音乐库、视频库等
- FileIO 公开一系列静态方法,以方便对文件进行读写操作,简化了对文件进行操作的步骤,该类针对StorageFile类实例而设计
- PathIO 同上,公开一系列便捷的方法来读写文件,与FileIO不同的是,PathIO是面向路径的,即在读写文件时不需要创建StorageFile实例,只需要通过字符串指定文件的路径就可以了
(2) 读写本地文件
当应用程序安装完成后,操作系统会为每个应用分配独立的文件夹,每个应用程序只能访问自己的文件夹。这个文件夹也叫本地目录,可以通过ApplicationData类的LocalFolder属性获得与本地目录相关的StorageFolder实例,随后应用程序就可以使用该StorageFolder对象来读写文件了。
下面示例将演示如何读写本地文件。示例首先将一个随机生成的字节序列写入本地文件,然后再从已保存的文件中读出这些字节序列。
- MainPage页面的布局如下XAML所示:
<StackPanel Spacing="10">
<TextBlock Text="写入文件" FontSize="36"/>
<Button x:Name="writeBtn" Content="将字节序列写入文件" Tapped="writeBtn_Tapped"/>
<TextBlock x:Name="tbBytes" TextWrapping="Wrap" FontSize="24"/>
<Line X1="0" Y1="0" X2="100" Y2="0" StrokeThickness="6" Stretch="Fill" Stroke="LightGray"/>
<TextBlock Text="从文件读入" FontSize="36"/>
<Button x:Name="readBt" Content="读取字节序列" Tapped="readBt_Tapped"/>
<TextBlock x:Name="tbReadBytes" FontSize="24" TextWrapping="Wrap"/>
</StackPanel>
- 分别处理两个Button控件的Tapped事件。
private async void writeBtn_Tapped(object sender, TappedRoutedEventArgs e)
{
writeBtn.IsEnabled = false;
//产生字节数组
Random rand = new Random();
byte[] data = new byte[8];
rand.NextBytes(data);
//从字节数组产生缓冲区对象
IBuffer buffer = data.AsBuffer();
//在本例目录中创建新文件
StorageFile newFile = await local.CreateFileAsync("my.data", CreationCollisionOption.ReplaceExisting);
//打开文件流
using (IRandomAccessStream outputStream = await newFile.OpenAsync(FileAccessMode.ReadWrite))
{
//将缓冲区的内容写入流
await outputStream.WriteAsync(buffer);
}
//显示已写入的字节序列
tbBytes.Text = $"已向文件写入:{BitConverter.ToString(data)}";
writeBtn.IsEnabled = true;
}
private async void readBt_Tapped(object sender, TappedRoutedEventArgs e)
{
readBt.IsEnabled = false;
//获取文件
var file = await local.GetFileAsync("my.data");
if (file != null)
{
//用于存放读到的数据的缓冲区
IBuffer buffer = null;
//打开流
using (var inputStream = await file.OpenReadAsync())
{
//实例化缓冲区对象
buffer = WindowsRuntimeBuffer.Create((int)inputStream.Size);
//从流中读入数据,存放到缓冲区中
await inputStream.ReadAsync(buffer,buffer.Capacity,InputStreamOptions.None);
}
//显示读到的字节序列
tbReadBytes.Text = $"读到的字节序列:{BitConverter.ToString(buffer.ToArray())}";
}
readBt.IsEnabled = true;
}
在Running App中读写数据通常用到缓冲区对象,即IBuffer接口及实现了该接口的Buffer类。在声明变量时,一般使用IBuffer来作为类型标识。
IBuffer buffer = data.AsBuffer();
有过.NET开发经历的开发者会明白,在托管代码中处理字节序列时,用得最多的是通过字节数组(byte[])作为临时缓冲区,而由于UWP中要用到IBuffer缓冲区对象,这就涉及到两种类型之间的转换。为了实现它们之间的互相转化,API库为它们定义了相关的扩展方法:
- 调用byte[]实例的AsBuffer方法,可以返回IBuffer实例。
- 调用IBuffer实例的ToArray方法,可以得到byte数组。
StorageFile类有两种方法打开文件流:OpenReadAsync方法打开的流只能用于读取,不能进行写操作,在读取文件时使用该方法较合适;OpenAsync方法既可以打开只读的流,也可以打开可读可写的流,这决定于传递给方法参数的FileAccessMode枚举的值。
(3) FileIO与PathIO
FileIO类与PathIO都公开了一系列方法,以便简捷地读写文件。如下所示,可以将这些方法按照读写的方向分为两组。
写入:
-
AppendLinesAsync 在文件原有内容的基础上追加一行或多行文本,每次写入的文本的结尾自动加上换行符。
-
AppendTextAsync 向文件的现有内容追加文本。
-
WriteBufferAsync 将缓冲区中的数据写入文件。
-
WriteBytesAsync 将字节数组写入文件。
-
WriteTextAsync 向文件写入文本,每次写入的内容会替换文件的现有内容。
-
WriteLinesAsync 向文件写入一行或多行文本,写入的文本末尾会自动加上换行符,文件的现有内容会被替换。
读取: -
ReadTextAsync 获取文件中所有文本内容。
-
ReadLinesAsync 从文件中读取一行或多行文本。
-
ReadBufferAsync 将从文件读取的数据存入到缓冲区对象中。
接下来用一个示例来演示FileIO和PathIO两个类的使用方法。
页面的布局XAML如下:
<StackPanel Spacing="10">
<TextBox x:Name="txtInput" Header="请输入内容:" TextWrapping="Wrap"/>
<Button Tag="write" Content="写入文件" Tapped="Button_Tapped"/>
<Button Tag="read" Content="读取文件" Tapped="Button_Tapped"/>
<TextBox x:Name="txtOutput" IsReadOnly="True" Header="读出的内容:" TextWrapping="Wrap"/>
</StackPanel>
在本示例中,将使用FileIO类将文本写进本地文件中,随后使用PathIO类从本地文件中读出文本内容。Button控件的Tapped事件处理程序如下:
private async void Button_Tapped(object sender, TappedRoutedEventArgs e)
{
Button btn = sender as Button;
btn.IsEnabled = false;
if ((sender as Button).Tag.ToString() is "write")
{
if (txtInput.Text.Length < 1)
{
return;
}
//获取本地目录的引用
var local = ApplicationData.Current.LocalFolder;
//创建新文件
var newFile = await local.CreateFileAsync("data.txt",CreationCollisionOption.ReplaceExisting);
//写入文件
await FileIO.WriteTextAsync(newFile,txtInput.Text);
}
else
{
try
{
//直接读取文件内容
txtOutput.Text = await PathIO.ReadTextAsync("ms-appdata:///local/data.txt");
}
catch (Exception ex)
{
txtOutput.Text = ex.Message;
}
}
btn.IsEnabled = true;
}
从上面代码中可以看出,使用FileIO类和PathIO类来读写文件非常简便。要注意的是,在使用PathIO类读取文件时使用的是文件路径,本地文件夹的路径为:
ms-appdata:///local/
示例中的data.txt文件是放在本地目录下的,因此文件的路径为:
ms-appdata:///local/data.txt
还要注意:PahtIO类只能对已经存在的文件进行操作,如果文件不存在,则会发生异常。
(4) DataWriter与DataReader
这两个类都是以流对象作为载体而进行的读写操作。通过这两个类的封装,使流操作变得更加方便,功能上类似于FileIO类和PathIO类。但是,FileIO类和PathIO类所操作的目标是文件,而DataWriter类和DataReader类是面向流的,既可以用来读写文件,也可以用于读写内存数据,在网络通信中还可以读写网络数据。可见,DataWriter与DataReader适用的范围更广。
在写入数据时,可以使用DataWriter类公开的如WriteByte、WriteDateTime、WriteInt32、WriteString等方法;同理,在读取数据时,可以调用DataReader类的ReadByte、ReadDateTine、ReadString等方法。这些方法的优点在于封装了基础数据类型的处理,可以很方便地进行读写。不过要注意的是,读出数据的顺序应当与数据写入时的顺序相同。例如先写入一个byte类型的值,后写入一个int类型的值,在读出数据的时候,应该先读出byte类型的值,在读出int类型的值。
下面示例先用DataWriter类向本地文件写入数据,然后使用DataReader类将数据读出来。应用程序主页面的XAML代码如下:
<StackPanel Spacing="10">
<RichTextBlock FontSize="20" TextWrapping="Wrap">
<Paragraph>
写入的内容及顺序如下:
</Paragraph>
<Paragraph>
<Span>1、bool值:true</Span>
<LineBreak/>
<Span>2、DateTime类型值:2020-12-20</Span>
<LineBreak/>
<Span>3、字符串:测试文本</Span>
</Paragraph>
</RichTextBlock>
<Button Tag="write" Content="写入文件" Tapped="Button_Tapped"/>
<Line X1="0" X2="20" Stretch="Fill" StrokeThickness="5" Stroke="LightYellow"/>
<Button Tag="read" Content="读取内容" Tapped="Button_Tapped"/>
<TextBlock x:Name="tbResult" FontSize="20" TextWrapping="Wrap"/>
</StackPanel>
示例要向本地文件写入三段数据:第一段为bool类型的值ture;第二段数据为当前日期值;第三段数据是字符串"测试文本"。
Button控件的事件处理程序如下:
private async void Button_Tapped(object sender, TappedRoutedEventArgs e)
{
if ((sender as Button).Tag.ToString() is "write")
{
//创建新文件
var file = await localFolder.CreateFileAsync("demo.dat",CreationCollisionOption.ReplaceExisting);
//打开文件流
using (var outputStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
//实例化DataWriter
DataWriter dw = new DataWriter(outputStream);
//设置默认编码格式
dw.UnicodeEncoding = UnicodeEncoding.Utf8;
//写入bool值
dw.WriteBoolean(true);
//写入日期时间值
DateTime dt = DateTime.Now;
dw.WriteDateTime(dt);
//写入字符串
string str = "测试文本";
//计算字符串的长度
uint len = dw.MeasureString(str);
//先写入字符串的长度
dw.WriteInt32((int)len);
//再写入字符串
dw.WriteString(str);
//以下方式必须调用
await dw.StoreAsync();
//解除DataWriter与流的关联
//dw.DetachStream();
dw.Dispose();
}
await new MessageDialog