所谓NIO,就是New IO的缩写。是从JDK 1.4开始引入的全新的IO API。NIO将以更高效的方式进行文件的读写操作,可完全代替传统的IO API使用。而且JDK 1.7对NIO又进行了更新,可以称作NIO 2.0。
区别主要如下:
IO | NIO |
---|---|
面向流 | 面向缓冲区 |
阻塞IO | 非阻塞IO |
无选择器 | 有选择器 |
上面说了NIO是利用通道和缓冲区进行数据传输的,那下面就来了解一下。
通道和缓冲区
1、缓冲区(Buffer): 一个用于特定基本数据类型的容器。由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类。简单的说,缓冲区就是用于存储数据的。八种基本类型中,除了boolean没有对应的缓冲区,其他的都有,分别是:ByteBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer 、CharBuffer。
(1). 缓冲区的四个核心属性:
属性关系图
(2). 缓冲区的核心方法:
看例子:
@Test
public void test1(){
String str = "abcde";
//1.分配一个指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.out.println("==================allocate================");
System.out.println(buffer.position());//0
System.out.println(buffer.limit());//1024
System.out.println(buffer.capacity());//1024
//2.利用put()把数据存入缓冲区
buffer.put(str.getBytes());
System.out.println("===================put====================");
System.out.println(buffer.position());//5
System.out.println(buffer.limit());//1024
System.out.println(buffer.capacity());//1024
//3.切换到读数据模式
buffer.flip();
System.out.println("===================flip====================");
System.out.println(buffer.position());//0
System.out.println(buffer.limit());//5
System.out.println(buffer.capacity());//1024
//4.读取缓冲区的数据
byte[] dst = new byte[buffer.limit()];
buffer.get(dst);
System.out.println(new String(dst,0,dst.length));//abcde
System.out.println("===================get====================");
System.out.println(buffer.position());//5
System.out.println(buffer.limit());//5
System.out.println(buffer.capacity());//1024
//6.清空缓冲区(但是缓冲区中的数据依然存在,只不过里面的数据处于被遗忘状态)
buffer.clear();
System.out.println("===================clear====================");
System.out.println(buffer.position());//0
System.out.println(buffer.limit());//1024
System.out.println(buffer.capacity());//1024
System.out.println((char)buffer.get());//a
}
(3). 直接缓冲区和非直接缓冲区:
@Test
public void test2(){
//分配直接缓冲区
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
System.out.println(buffer.isDirect());//判断是否是直接缓冲区
}
2、通道(Channel): Channel 表示 IO 源与目标打开的连接。 上面说了,通道就是铁路,缓冲区就是火车,所以这两个要搭配使用。
(1). Channel接口主要实现类:
(2). 获取通道:
(3). 通道之间的数据传输:
(4). 通道与缓冲区之间的数据传输:
看例子:利用通道完成文件的复制。 方式一:非直接缓冲区
@Test
public void test() throws IOException {
FileInputStream fis = new FileInputStream("1.jpg");//读取项目中的1.jpg图片
FileOutputStream fos = new FileOutputStream("2.jpg");//复制1.jpg,重命名为2.jpg
//1.获取通道
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
//2.分配缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//3.将通道中的数据存入缓冲区
while (inChannel.read(buffer) != -1){
buffer.flip();//切换成读数据模式
//4.将缓冲区中的数据写入通道中
outChannel.write(buffer);
buffer.clear();
}
outChannel.close();
inChannel.close();
fos.close();;
fis.close();
}
上面代码使用通道和缓冲区进行了图片的复制,将1.jpg复制并重命名为2.jpg。
方式二:直接缓冲区
@Test
public void test3() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"),
StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("3.jpg"),
StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
//inChannel.transferTo(0,inChannel.size(),outChannel);
outChannel.transferFrom(inChannel,0,inChannel.size());
inChannel.close();
outChannel.close();
}
3、分散读取与聚集写入:
看例子:
//分散读取与聚集写入
@Test
public void test4() throws IOException {
RandomAccessFile raf = new RandomAccessFile("1.jpg","rw");
//1.获取通道
FileChannel channel = raf.getChannel();
//2.分配缓冲区
ByteBuffer buffer1 = ByteBuffer.allocate(128);//缓冲区1
ByteBuffer buffer2 = ByteBuffer.allocate(1024);//缓冲区2
//3.分散读取
ByteBuffer[] bufs = {buffer1,buffer2};//将两个缓冲区加到数组中
channel.read(bufs);//读取缓冲区数据
for (ByteBuffer byteBuffer : bufs){
byteBuffer.flip();//修改成读模式
}
System.out.println(new String(bufs[0].array(),0,bufs[0].limit()));
System.out.println("============================");
System.out.println(new String(bufs[1].array(),0,bufs[1].limit()));
//4.聚集写入
RandomAccessFile raf1 = new RandomAccessFile("5.jpg","rw");
FileChannel channel1 = raf1.getChannel();
channel1.write(bufs);
}
4、编码与解码:
看例子:
//字符集(GBK编GBK解,不会乱码,GBK编UTF8解就会乱码)
@Test
public void test5() throws IOException {
Charset cs1 = Charset.forName("GBK");//指定编码
CharsetEncoder ce = cs1.newEncoder();//获取编码器
CharsetDecoder cd = cs1.newDecoder();//获取解码器
CharBuffer cBuf = CharBuffer.allocate(1024);
cBuf.put("花自飘零水自流");
cBuf.flip();
//编码
ByteBuffer bBuf = ce.encode(cBuf);
for (int i=0; i<14;i++){
System.out.println(bBuf.get());
}
System.out.println("===========================");
//解码
bBuf.flip();
CharBuffer cBuf2 = cd.decode(bBuf);
System.out.println(cBuf2.toString());
}
1、阻塞和非阻塞:
2、NIO进行网络通信的三个核心:
3、使用NIO进行网络通信案例(阻塞式):
//客户端
@Test
public void client() throws IOException {
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
//2.分配缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//3.读取本地文件,并发送到服务端
while(inChannel.read(buf) != -1){
buf.flip();
sChannel.write(buf);
buf.clear();
}
sChannel.shutdownOutput();
//4.接收服务端的反馈
int len = 0;
while((len = sChannel.read(buf)) != -1){
buf.flip();
System.out.println(new String(buf.array(), 0, len));
buf.clear();
}
//5.关闭通道
inChannel.close();
sChannel.close();
}
//服务端
@Test
public void server() throws IOException{
//1.获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
FileChannel outChannel = FileChannel.open(Paths.get("6.jpg"),
StandardOpenOption.WRITE,
StandardOpenOption.CREATE);
//2.绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//3.获取客户端连接通道
SocketChannel sChannel = ssChannel.accept();
//4.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//5.接收客户端数据并保存到本地
while(sChannel.read(buf) != -1){
buf.flip();
outChannel.write(buf);
buf.clear();
}
//6.发送反馈给客户端
buf.put("服务端接收数据成功".getBytes());
buf.flip();
sChannel.write(buf);
//7.关闭通道
sChannel.close();
outChannel.close();
ssChannel.close();
}
4、使用NIO进行网络通信案例(非阻塞式):
//客户端
@Test
public void client() throws IOException {
//1. 获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9998));
//2. 切换非阻塞模式
sChannel.configureBlocking(false);
//3. 分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//4. 发送数据给服务端
Scanner scan = new Scanner(System.in);
while (scan.hasNext()) {
String str = scan.next();
buf.put((new Date().toString() + "\n" + str).getBytes());
buf.flip();
sChannel.write(buf);
buf.clear();
}
//5. 关闭通道
sChannel.close();
}
//服务端
@Test
public void server() throws IOException {
//1. 获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2. 切换非阻塞模式
ssChannel.configureBlocking(false);
//3. 绑定连接
ssChannel.bind(new InetSocketAddress(9998));
//4. 获取选择器
Selector selector = Selector.open();
//5. 将通道注册到选择器上, 并且指定“监听接收事件”
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//6. 轮询式的获取选择器上已经“准备就绪”的事件
while (selector.select() > 0) {
//7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
//8. 获取准备“就绪”的是事件
SelectionKey sk = it.next();
//9. 判断具体是什么事件准备就绪
if (sk.isAcceptable()) {
//10. 若“接收就绪”,获取客户端连接
SocketChannel sChannel = ssChannel.accept();
//11. 切换非阻塞模式
sChannel.configureBlocking(false);
//12. 将该通道注册到选择器上
sChannel.register(selector, SelectionKey.OP_READ);
} else if (sk.isReadable()) {
//13. 获取当前选择器上“读就绪”状态的通道
SocketChannel sChannel = (SocketChannel) sk.channel();
//14. 读取数据
ByteBuffer buf = ByteBuffer.allocate(1024);
int len = 0;
while ((len = sChannel.read(buf)) > 0) {
buf.flip();
System.out.println(new String(buf.array(), 0, len));
buf.clear();
}
}
//15. 取消选择键 SelectionKey
it.remove();
}
}
}
非阻塞模式的服务端写法更麻烦一点,都有详细注释。
总的来说,NIO性能比传统IO要好很多,主要就是理解通道和缓冲区这两个概念。而阻塞和非阻塞主要是用于网络编程。