Java开发笔记(九十二)文件通道的基本用法
前面介绍的各色流式io在功能方面着实强大,处理文件的时候该具备的操作应有尽有,可流式io在性能方面不尽如人意,它的设计原理使得实际运行效率偏低,为此从java4开始增加了nio技术,通过全新的架构体系带来了可观的性能提升。
nio是“non-blocking io”的缩写,意思是非阻塞的io,与之相对应,传统的流式io又被称作bio(“blocking io”的缩写),意即阻塞的io。所谓阻塞与非阻塞,说起来挺拗口,令人不知所云,这都是设计师脑袋短路惹的祸,发明了这么难懂的词汇,害得初学者一脸懵逼。其实阻塞与非阻塞的区别,犹如私家车与出租车的区别,私家车买回来以后只供车主一家开,没开的时候要么停在小区地库,要么停在公共停车场,其他人是不能随便坐上这部私家车的,如此一来私家车便处于阻塞模式,车门塞住了外人打不开。而出租车整日在街上穿行,有客人招手就停下来载客,开到目的地乘客下车,然后恢复空车状态重新揽客,这样出租车便处于非阻塞模式,车门没塞住乘客打得开。显然阻塞模式存在资源的极大浪费,一个资源分配给某人之后,即使无事可做也只能空在一边闲得发慌;而非阻塞模式充分发挥了物尽其用的原则,一个资源用完之后马上释放,随时允许下一个人接着使用。
非阻塞的nio机制画了一个高效的大饼,谁知对于文件来说却是画饼充饥,原来非阻塞模式只适用于网络请求交互,而文件处理总是处于阻塞模式,想想看,某个文件被a用户打开之后,b用户还能往该文件写入数据吗?很明显即使a用户打开文件后啥事都不做,b用户也不能写入该文件,缘于文件已经被a用户霸占了。之所以文件没有非阻塞模式,是因为文件仅仅为磁盘上的某个存储片段,它既不智能也不主动,更无法进行任务调度,只能被动的打开和关闭。既然文件处理不支持非阻塞机制,难道nio技术对文件来说形同虚设?当然事实并非如此,要知道nio技术不光光包括非阻塞机制,还包括文件通道、虚拟内存等等手段,可谓博大精深、不一而足。
先看文件通道,众所周知,传统的流式io分为输入流与输出流,输入与输出拥有各自的stream工具,输入流工具inputstream只能用来读文件,输出流工具outputstream只能用来写文件,二者井水不犯河水。那如果打开文件之后,想要一会儿读一会儿写,输入流和输出流可得忙坏了,读的时候招呼inputstream来个全套操作,写的时候再招呼outputstream来个全套操作,实在是劳民伤财。文件通道就不一样,通道中的数据允许双向流动,流进来意味着读操作,流出去意味着写操作,这样文件的读写操作集中在文件通道里进行,大大节省了系统的资源开销。在操作系统层面,通道是一种专职i/o操作的简单处理器,它专门负责输入输出控制,使得cpu从繁琐的i/o处理中解放出来,从而有效地提高整个系统的资源利用率。
文件通道对应的java类型名叫filechannel,它的创建方式主要有两种,第一种要通过输入输出流,即调用输入输出流的getchannel方法获取通道对象。比如下面代码根据文件输入流得到了可读的文件通道:
// 第一种方式:根据文件输入流获得可读的文件通道
filechannel channel1 = new fileinputstream(mfilename).getchannel();
又如下面代码根据文件输出流得到了可写的文件通道:
// 第一种方式:根据文件输出流获得可写的文件通道
filechannel channel2 = new fileoutputstream(mfilename).getchannel();
第二种方式则要通过随机文件工具,仍旧调用随机文件工具的getchannel方法获取通道对象。此时文件通道对象的构建代码示例如下:
// 第二种方式:根据随机访问文件获得可读的文件通道
filechannel channel1 = new randomaccessfile(mfilename, "r").getchannel();
// 第二种方式:根据随机访问文件获得可写的文件通道
filechannel channel2 = new randomaccessfile(mfilename,"rw").getchannel();
得到文件通道对象之后,接着便能调用下列方法完成相应的文件处理动作:
isopen:判断文件通道是否打开。
size:获取文件通道的大小(即文件长度)。
truncate:截断文件大小到指定长度。
read:把文件通道中的数据读到字节缓存。
write:往文件通道写入字节缓存中的数据。
force:强制写入磁盘,相当于缓存输出流的flush方法。
close:关闭文件通道。
从以上方法列表可知,filechannel相当于集成了fileinputstream和fileoutputstream,用起来更加方便。下面来个利用文件通道写文件的代码例子,一样的简洁明了:
// 通过文件通道写入文件
private static void writechannel() {
string str = "春眠不觉晓,处处闻啼鸟。\n夜来风雨声,花落知多少。";
// 根据文件输出流获得可写的文件通道。注意文件通道支持try(...)的自动关闭操作
try (filechannel channel = new fileoutputstream(mfilename).getchannel()) {
// 生成字符串对应的字节缓存对象
bytebuffer buffer = bytebuffer.wrap(str.getbytes());
channel.write(buffer); // 往文件通道写入字节缓存
//channel.force(true); // 强制写入磁盘,相当于输出流的flush方法
} catch (exception e) {
e.printstacktrace();
}
}
再来利用文件通道读文件的代码例子,具体如下所示:
// 通过文件通道读取文件
private static void readchannel() {
// 根据文件输入流获得可读的文件通道。注意文件通道支持try(...)的自动关闭操作
try (filechannel channel = new fileinputstream(mfilename).getchannel()) {
int size = (int) channel.size(); // 获取文件通道的大小(即文件长度)
// 分配指定大小的字节缓存
bytebuffer buffer = bytebuffer.allocatedirect(size);
channel.read(buffer); // 把文件通道中的数据读到字节缓存
buffer.flip(); // 把缓冲区从写模式切换到读模式。从缓冲区读取数据之前,必须先调用flip方法
byte[] bytes = new byte[size]; // 创建与文件大小相同长度的字节数组
buffer.get(bytes); // 把字节缓存中的数据取到字节数组
string content = new string(bytes); // 把字节数组转换为字符串
system.out.println("content="+content);
} catch (exception e) {
e.printstacktrace();
}
}
看来文件通道在读文件过程中也使用了缓存,整体的代码流程同缓存输入流bufferedinputstream类似,只不过与filechannel搭配的字节缓存bytebuffer用着不太顺手,别着急,后面的文章将细细道来bytebuffer的详细用法。
更多java技术文章参见《java开发笔记(序)章节目录》
上一篇: 雷军:小米9骁龙855全球真首发 联想手机:给您拜个真晚年
下一篇: 微信赚钱的主要公式