IO 其实是很友好的 Ps:感觉今年找工作有点难~ IO 大致可以分为文件 IO 和网络 IO,先从文件 IO 说起
文件IO
在讨论文件IO之前,先要理清几个问题,首先第一个问题是
文件种类
文件种类代表的是解析规则,也就是一种文件种类需要特定的解析规则解析它才有意义。最常见的是 以行为单位的 txt 文件, XML 文件特点是尖括号,Json 文件就非常灵活使用 [] 表示列表/数组,{} 表示类等等。因此将比特流转换为字符串之后就根据需要利用合适的规则解析它了。
大多数关于文件IO的博客到底写了什么
大多数关于文件 IO 的博客都是关于读取 txt 文件的,内容大概是使用一个 buffer 类包装原始 stream 进行读取,内容都是这些。原因在于 ① 其他的文件解析有大量库支持,可以方便到只需要提供文件路径+编码+过滤条件就能得到想要的结果。反过来说如果直接读取这些文件也不是不可以,读取之后还要解析,所以还要实现解析器,但这和IO就没什么直接联系(主要还是麻烦)② txt 文件解析比较容易,就是以行为单位没什么特殊的 ③ 引出一些底层原理
这些博客忽略了什么
首先这些博客作者的初衷是好的,他们主要想阐述三方面
- 常见 API 用法
-
编码与解码,也就是比特流、字符之间的转换
- 文件IO实现原理以及效率问题
但是因为这些作者表述过于隐晦导致初学者看见这些内容会产生类似 “???” 的感觉。因为在初学者眼里文件IO的效果就是我读了然后出现我想要的内容。
实际上IO是面向 byte[] 的,读取到的是 byte[],写要把内容转换为 byte[]。同时 byte[] 可以被多种”规则“解释,例如
0010 0010
从ACSII角度它是一个双引号,如果转换为十六进制它就是22。反过来同样的字符被不同规则会变成不同byte[]。
扯了这么多这以下才是重点
实际上很少将文件完全读取的,例如一个日志文件可能会很大(例如100MB以上)我们可能只需要读取关键的某一部分,例如最新添加的几条日志。或者以我遇到的一个问题举例,有一个门禁控制器如果被刷卡开门会将相关信息写入日志文件中,我们需要读取这些新添加的信息。这时问题来了,怎么才能读取到关键的某一部分,如果不能采用将文件全部读取并分析的方法,哪还有什么方法可以更加高效的解决这个问题。这时候有人会说我们可以使用kafka/大数据这些框架因为他们非常适合日志处理这种流式计算。当然你是对的,不过我们是在学习IO,先暂时不管他们(强行逻辑
答案就是使用偏移量
using (FileStream fs = File.Open(PATH, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (BufferedStream bs = new BufferedStream(fs))
{
bs.Seek(-OFFSET, SeekOrigin.End);
byte[] data = new byte[1024];
int length = bs.Read(data, 0, data.Length);
Console.WriteLine(Encoding.UTF8.GetString(data, 0, length));
}
Seek方法使用可以看 这篇文章
那这个偏移量如何获取
- 如果你的文件更新不是很频繁,则可以记录文件的
BaseStream.Length
的值。每当文件更新,获取到最新BaseStream.Length
的值与上一次获取到的值相减就可以获取到偏移量。
如果我想获取到文件后x行如何做到
-
可以将偏移量OFFSET设定成一个比较大的数值(例如8196)读取文件至末尾之后再利用以下代码
String[] lines = Encoding.UTF8.GetString(data, 0, dataLength).Split(Environment.NewLine.ToCharArray());
这样每一行为一个String,