Java NIO简介
- Java NIO(New IO / Non Blocking IO),从java1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API.NIO与原来的IO有同样的作用和目的,但是使用方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作
NIO 与 IO的主要区别
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向缓冲区(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
无 | 选择器(Selectors) |
通道和缓冲区
- Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到IO设备(例如:文件、套接字)的连接。若需要使用NIO系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理
- 简而言之,Channel负责传输,Buffer负责存储
缓冲区的数据读取
直接缓冲区与非直接缓冲区
package com.atguigu.nio;
import org.junit.Test;
import java.nio.ByteBuffer;
/**
* 一、缓冲区(Buffer):在Java NIO中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据
*
* 根据数据类型不同(boolean除外),提供了相应类型的缓冲区:
* ByteBuffer
* CharBuffer
* ShortBuffer
* IntBuffer
* LongBuffer
* FloatBuffer
* DoubleBuffer
* 上述缓冲区的管理方式几乎一致,通过 allocate() 获取缓冲区
*
* 二、缓冲区存取数据的两个核心方法
* put(): 存入数据到缓冲区中
* get(): 获取缓冲区中的数据
*
* 四、缓冲区中的四个核心属性:
* capacity : 容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变
* limit : 界限,表示缓冲区中可以操作数据的大小。(limit 后的数据不能进行读写)
* position : 位置,表示缓冲区中正在操作数据的位置
*
* mark : 标记,表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置
*
* 0 <= mark <= position <= limit <= capacity
*
* 五、直接缓冲区与非直接缓冲区:
* 非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在JVM内存中
* 直接缓冲区: 通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率
*
*/
public class TestBuffer {
@Test
public void test3() {
//创建直接缓冲区
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
//判断是否直接缓冲区
System.out.println(buf.isDirect());
}
@Test
public void test2() {
String str = "abcde";
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.put(str.getBytes());
buf.flip();
byte[] dst = new byte[buf.limit()];
buf.get(dst, 0, 2);
System.out.println(new String(dst, 0, 2));
System.out.println(buf.position());
//mark() : 标记
buf.mark();
buf.get(dst, 2, 2);
System.out.println(new String(dst, 2, 2));
System.out.println(buf.position());
//reset() : 恢复 position 到 mark位置
buf.reset();
System.out.println(buf.position());
//判断缓冲区中是否还有剩余的数据
if (buf.hasRemaining()) {
//获取缓冲区中可以操作的数量
System.out.println(buf.remaining());
}
}
@Test
public void test1() {
String str = "abcde";
//1.分配一个指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//2.使用 put() 存入数据到缓冲区中
buf.put(str.getBytes());
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//3.切换到读取数据模式
buf.flip();
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//4.读取缓冲区的数据
byte[] bytes = new byte[buf.limit()];
buf.get(bytes);
System.out.println(new String(bytes, 0, bytes.length));
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//5.rewind() 可重复读数据
buf.rewind();
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//6.clear() 清空缓冲区。但是缓冲区中的数据依然存在,但是出于“被遗忘”状态
buf.clear();
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
}
}
通道(Channel)
- 通道(Channel):Channel表示IO源于目标打开的连接。Channel类似于传统的"流"。只不过Channel本身不能访问数据,Channel只能与Buffer进行交互
package com.atguigu.nio;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
/**
* 一、通道(Channel): 用于源节点与目标节点的连接,在 Java NIO 中负责缓冲区中数据的传输。Channel 本身不存储数据,因此需要配合缓冲区进行传输
*
* 二、通道的主要实现类
* java.nio.channels.Channel 接口:
* |--FileChannel
* |--SocketChannel
* |--ServerSocketChannel
* |--DatagramChannel
*
*
* 三、获取通道
* 1. Java 针对支持通道的类提供了 getChannel() 方法
* 本地IO:
* FileInputStream/FileOutputStream
* RandomAccessFile
* 网络IO:
* Socket
* ServerSocket
* DatagramSocket
*
* 2.在 JDK 1.7 中的 NIO.2 针对各个通道提供了一个静态方法 open()
* 3.在 JDK 1.7 中的 NIO.2 的 Files 功具类的 newByteChannel()
*
* 四、通道之间的数据传输
* transferFrom()
* transferTo()
*
* 五、分散(Scatter)与聚集(Gather)
* 分散读取(Scattering Reads): 将通道中的数据分散到多个缓冲区中
* 聚集写入(Gathering Writes): 将多个缓冲区中的数据聚集到通道中
*
* 六、字符集: Charset
* 编码:字符串->字节数组
* 解码:字节数组->字符串
*/
public class TestChannel {
@Test
public void test6() throws CharacterCodingException {
Charset cs1 = Charset.forName("GBK");
//获取编码器
CharsetEncoder ce = cs1.newEncoder();
//获取解码器
CharsetDecoder cd = cs1.newDecoder();
CharBuffer cBuf = CharBuffer.allocate(1024);
cBuf.put("哈哈哈哈哈哈");
cBuf.flip();
//编码
ByteBuffer bf = ce.encode(cBuf);
for (int i = 0; i < 12; i++) {
System.out.println(bf.get(i));
}
//解码
CharBuffer cBuf1 = cd.decode(bf);
System.out.println(cBuf1.toString());
}
//字符集
@Test
public void test5() {
SortedMap<String, Charset> map = Charset.availableCharsets();
Set<Map.Entry<String, Charset>> set =map.entrySet();
for (Map.Entry<String, Charset> entry : set) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
@Test
public void test4() throws IOException {
RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");
//1.获取通道
FileChannel channel1 = raf1.getChannel();
//2.分配指定大小的缓冲区
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
//3.分散读取
ByteBuffer[] bufs = {buf1, buf2};
channel1.read(bufs);
for (ByteBuffer buffer : bufs) {
buffer.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()));
RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
FileChannel channel2 = raf2.getChannel();
channel2.write(bufs);
channel2.close();
channel1.close();
}
//通道之间的数据传输(直接缓冲区)
@Test
public void test3() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("7.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
// inChannel.transferTo(0, inChannel.size(), outChannel);
outChannel.transferFrom(inChannel, 0, inChannel.size());
inChannel.close();
outChannel.close();
}
//使用直接缓冲区完成文件的复制(内存映射文件)
@Test
public void test2() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("5.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
//内存映射文件
MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
//直接对缓冲区进行数据的读写操作
byte[] dst = new byte[inMappedBuf.limit()];
inMappedBuf.get(dst);
outMappedBuf.put(dst);
inChannel.close();
outChannel.close();
}
//利用通道完成文件的复制(非直接缓冲区)
@Test
public void test1() {
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("1.jpg");
fos = new FileOutputStream("2.jpg");
//1.获取通道
inChannel = fis.getChannel();
outChannel = fos.getChannel();
//2.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//3.将通道中的数据存入缓冲区中
while (inChannel.read(buf) != -1) {
//切换成读数据模式
buf.flip();
//4.将缓冲区中的数据写入通道
outChannel.write(buf);
//清空缓冲区
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outChannel != null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inChannel != null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
NIO的非阻塞式网络通信
非阻塞式TCP通信
package com.atguigu.nio;
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
/**
* 一、使用NIO完成网络通信的三个核心
*
* 1.通道(Channel):负责连接
*
* java.nio.channels.Channel接口
* |--SelectableChannel
* |--SocketChannel
* |--ServerSocketChannel
* |--DatagramChannel
*
* |--Pipe.SinkChannel
* |--Pipe.SourceChannel
*
* 2.缓冲区(Buffer): 负责数据的存取
*
* 3.选择器(Selector): 是SelectableChannel 的多路复用器。用于监控 SelectableChannel的IO状况
*
*/
public class TestNonBlackingNIO {
//客户端
public static void main(String[] args) throws IOException {
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//2.获取指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//3.切换通道为非阻塞模式
sChannel.configureBlocking(false);
//4.准备要写出的数据
Scanner scan = new Scanner(System.in);
while (scan.hasNext()) {
String str = scan.next();
buf.put((LocalDateTime.now() + ":" + str).getBytes());
//5.切换buf为写出模式
buf.flip();
//6.将数据通过sChannel写出
sChannel.write(buf);
//7.清理buf
buf.clear();
}
sChannel.close();
}
//服务端
@Test
public void server() throws IOException {
//1.获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2.切换非阻塞模式
ssChannel.configureBlocking(false);
//3.绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//4.获取选择器
Selector selector = Selector.open();
//5.将通道注册到原则器上,并且制定"监听"事件
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//6.轮训式获取选择器上已经“就绪”的事件
while (selector.select() > 0) {
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
//7.遍历已经就绪的key
while (it.hasNext()) {
SelectionKey sk = it.next();
//8.判断具体是什么事件准备就绪
if (sk.isAcceptable()) {
//9.若"接收就绪",获取客户连接
SocketChannel sChannel = ssChannel.accept();
//10.切换非阻塞模式
sChannel.configureBlocking(false);
//11.将该通道注册到选择器上
sChannel.register(selector, SelectionKey.OP_READ);
} else if (sk.isReadable()) {
//12.如果是“读就绪”,获取当前选择器上的"读就绪"状态的通道
SocketChannel sChannel = (SocketChannel) sk.channel();
//13.读取数据
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();
}
}
//取消选择器
it.remove();
}
}
}
}
非阻塞式UDP通信
package com.atguigu.nio;
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Scanner;
public class TestNonBlockingNIO2 {
public static void main(String[] args) throws IOException {
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
Scanner scan = new Scanner(System.in);
while (scan.hasNext()) {
String str = scan.next();
buf.put(str.getBytes());
buf.flip();
dc.send(buf, new InetSocketAddress("127.0.0.1", 8989));
buf.clear();
}
dc.close();
}
@Test
public void receive() throws IOException {
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
dc.bind(new InetSocketAddress(8989));
Selector selector = Selector.open();
dc.register(selector, SelectionKey.OP_READ);
while (selector.select() > 0) {
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey sk = it.next();
if (sk.isReadable()) {
ByteBuffer buf = ByteBuffer.allocate(1024);
dc.receive(buf);
buf.flip();
System.out.println(new String(buf.array(), 0, buf.limit()));
buf.clear();
}
it.remove();
}
}
}
}
管道(Pipe)
- Java NIO管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
package com.atguigu.nio;
import org.junit.Test;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
public class TestPipe {
@Test
public void test1() throws IOException {
//1.获取管道
Pipe pipe = Pipe.open();
//2.将缓冲区的数据写入管道
ByteBuffer buf = ByteBuffer.allocate(1024);
Pipe.SinkChannel sinkChannel = pipe.sink();
buf.put("通过单向管道写数据".getBytes());
buf.flip();
sinkChannel.write(buf);
//3.读取缓冲区中的数据
buf.flip();
Pipe.SourceChannel sourceChannel = pipe.source();
sourceChannel.read(buf);
System.out.println(new String(buf.array(), 0, buf.limit()));
sourceChannel.close();
sinkChannel.close();
}
}