java nio主要需要理解缓冲区、通道、选择器三个核心概念,作为对java i/o的补充, 以提升大批量数据传输的效率。
(推荐教程:java课程)
学习nio之前最好能有基础的网络编程知识
java i/o流
java 网络编程
java nio:缓冲区
通道(channel)作为nio的三大核心概念之一(缓冲区、通道、选择器),用于在字节缓冲区与位于通道另一侧的实体(文件或者套接字)之间有效的传输数据(核心是传输数据)
nio编程的一般模式是:把数据填充到发送字节缓冲区 --> 通过通道发送到通道对端文件或者套接字
通道基础
使用channel的目的是进行数据传输,使用前需要打开通道、使用后需要关闭通道
打开通道
我们知道i/o有两大类:file io和 stream i/o,其对应到通道也就有文件通道(filechannel)和套接字通道(socketchannel、serversocketchannel、datagramchannel)两种
对于套接字通道,使用静态工厂方法打开
socketchannel sc = socketchannel.open();serversocketchannel sc = serversocketchannel.open();datagramchannel sc = datagramchannel.open();
对于文件通道只能通过对一个randomaccessfile、fileinputstream、fileoutputstream对象调用getchannel()方法获取
fileinputstream in = new fileinputstream("/tmp/a.txt");filechannel fc = in.getchannel();
使用通道进行数据传输
下段代码首先将要写入的数据放到bytebuffer中, 然后打开文件通道,把缓冲区中的数据放到文件通道。
//准备数据并放入字节缓冲区bytebuffer bf = bytebuffer.allocate(1024);bf.put("i am cool".getbytes());bf.flip();//打开文件通道fileoutputstream out = new fileoutputstream("/tmp/a.txt");filechannel fc = out.getchannel();//数据传输fc.write(bf);//关闭通道fc.close();
关闭通道
如同socket、fileinputstream等对象使用完毕之后需要关闭一样, 通道使用之后也需要关闭。一个打开的通道代表与一个特定i/o服务的特定连接并封装该连接的状态,通道关闭时连接丢失,不再连接任何东西。
阻塞 & 非阻塞模式
通道有阻塞和非阻塞两种运行模式,非阻塞模式的通道永远不会休眠,请求的操作要么立即完成,要么返回一个结果表明未进行任何操作(具体看socket通道处的描述)。只有面向流的通道可使用非阻塞模式
文件通道
文件通道用于对文件进行访问, 通过对一个randomaccessfile、fileinputstream、fileoutputstream对象调用getchannel()方法获取。调用getchannel方法返回一个连接到相同文件的filechannel对象,该filechannel对象具有与file对象相同的访问权限。
文件访问
使用文件通道的目的还是对文件进行读写操作,通道的读写api如下:
public abstract int read(bytebuffer dst) throws ioexception;public abstract int write(bytebuffer src) throws ioexception;
下面是一段读取文件的demo
//打开文件channelrandomaccessfile f = new randomaccessfile("/tmp/a.txt", "r");filechannel fc = f.getchannel();//从channel中读取数据,直到文件尾bytebuffer bb = bytebuffer.allocate(1024);while (fc.read(bb) != -1) {;}//翻转(读之前需要先进行翻转)bb.flip();stringbuilder builder = new stringbuilder();//把每一个字节转为字符(ascii编码)while (bb.hasremaining()) {builder.append((char) bb.get());}system.out.println(builder.tostring());
上面这个demo有个问题:我们只能读取字节, 然后由应用程序去解码,这个问题我们可以通过工具类channels将通道包装成reader和writer来解决;当然我们也可以直接使用java i/o流模式的reader和writer操作字符
文件通道位置与文件空洞
文件通道位置(position)就是普通文件的位置, position的值决定了文件中哪个位置的数据接下来将被读或者写
读取超出文件尾部位置的数据会返回-1(文件eof)
往一个超出文件尾部的位置写入数据会造成文件空洞:比如一个文件现在有10个字节, 但是此时往position=20 处写入数据就会造成10~20之间的位置是没有数据的,这就是文件空洞
force操作
force操作强制通道将全部修改立即应用到磁盘文件(防止系统宕机导致修改丢失)
public abstract void force(boolean metadata) throws ioexception;
内存文件映射
filechannel提供了一个map()方法,该方法可以在一个打开的文件和特殊类型的bytebuffer(mappedbytebuffer)之间建立一个虚拟内存映射。
因为map方法返回的mappedbytebuffer对象是直接缓冲区,所以通过mappedbytebuffer来操作文件非常高效(尤其是大量数据传输的情况)
mappedbytebuffer的使用
通过mappedbytebuffer读取文件
fileinputstream in = new fileinputstream("/tmp/a.txt");filechannel fc = in.getchannel();mappedbytebuffer mbb = fc.map(mapmode.read_only, 0, fc.size());stringbuilder builder = new stringbuilder();while (mbb.hasremaining()) { builder.append((char) mbb.get());}system.out.println(builder.tostring());
mappedbytebuffer的三种模式
read_only
read_write
private
只读和读写模式都好理解,private模式下写操作写的是一个临时缓冲区,不会真正去写文件。(写时拷贝思想)
socket通道
socket 通道可以运行在非阻塞模式且是可选择的,这两点使得对于网络编程我们不再需要为每个socket连接创建一个线程,而是使用一个线程即可管理成百上千的socket连接。
所有的socket通道在实例化的时候都会创建一个对象的socket对象, socket通道并不负责协议相关的操作, 协议相关的操作都委派给对等socket对象(如socketchannel对象委派给socket对象)
非阻塞模式
相较于传统java socket的阻塞模式,socketchannel提供了非阻塞模式,以构建高性能的网络应用程序
非阻塞模式下,几乎所有的操作都是立刻返回的。比如下面的socketchannel运行在非阻塞模式下,connect操作会立即返回,如果success为true代表连接已经建立成功了, 如果success为false, 代表连接还在建立中(tcp连接需要一些时间)。
//打开socket通道 socketchannel ch = socketchannel.open(); //非阻塞模式 ch.configureblocking(false); //连接服务器 boolean success = ch.connect(inetsocketaddress.createunresolved("127.0.0.1", 7001)); //轮训连接状态, 如果连接还未建立就可以做一些别的工作 while (!ch.finishconnect()){ //dosomething else } //连接建立, 做正事 //do something;
serversocketchannel
serversocketchannel与serversocket类似,只是可以运行在非阻塞模式下
下为一个通过serversocketchannel构建服务器的简单例子,主要体现了非阻塞模式,核心思想与serversocket类似
serversocketchannel ssc = serversocketchannel.open();ssc.configureblocking(false);ssc.bind(new inetsocketaddress(7001));while (true){ socketchannel sc = ssc.accept(); if(sc != null){ handle(sc); }else { thread.sleep(1000); }}
socketchannel 与 datagramchannel
socketchannel 对应 socket, 模拟tcp协议;datagramchannel对应datagramsocket, 模拟udp协议
二者的使用与seversocketchannel大同小异,看api即可
工具类
文体通道那里我们提到过, 通过只能操作字节缓冲区, 编解码需要应用程序自己实现。如果我们想在通道上直接操作字符,我们就需要使用工具类channels,工具类channels提供了通道与流互相转换、通道转换为阅读器书写器的能力,具体api入下
//通道 --> 输入输出流public static outputstream newoutputstream(final writablebytechannel ch);public static inputstream newinputstream(final asynchronousbytechannel ch);//输入输出流 --> 通道public static readablebytechannel newchannel(final inputstream in);public static writablebytechannel newchannel(final outputstream out);//通道 --> 阅读器书写器public static reader newreader(readablebytechannel ch, string csname);public static writer newwriter(writablebytechannel ch, string csname);
通过将通道转换为阅读器、书写器我们就可以直接在通道上操作字符。
randomaccessfile f = new randomaccessfile("/tmp/a.txt", "r"); filechannel fc = f.getchannel(); //通道转换为阅读器,utf-8编码 reader reader = channels.newreader(fc, "utf-8"); int i = 0, s = 0; char[] buff = new char[1024]; while ((i = reader.read(buff, s, 1024 - s)) != -1) { s += i; } for (i = 0; i < s; i++) { system.out.print(buff[i]); }
总结
通道主要分为文件通道和套接字通道。
对于文件操作:如果是大文件使用通道的文件内存映射特性(mappedbytebuffer)来有利于提升传输性能, 否则我更倾向传统的i/o流模式(字符api);对于套接字操作, 使用通道可以运行在非阻塞模式并且是可选择的,利于构建高性能网络应用程序。
相关推荐:java入门
以上就是详细介绍java nio的详细内容。
