文章目录
前面的记录: java基础——IO流——20200616
简介
Java NIO(New IO/ Non-Blocking IO)是从java1.4开始引入的一套新的IO API。可以替代标准的java IO API。 NIO与原本的IO有相同的作用和目的,但使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
IO | NIO |
---|---|
面向流 | 面向缓冲区 |
阻塞IO | 非阻塞IO |
(无) | 选择器 |
缓冲区 Buffer
- 在NIO中负责数据的存储
- 缓冲区用数组存储不同类型的数据,根据数据类型不同(boolean除外),提供了相应的Buffer子类
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
- Buffer主要用于与NIO通道进行交互,数据是从通道读入缓冲区,从缓冲区读入通道中的 。
public abstract class Buffer {
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
- capacity: 容量,表示缓冲区中最大存储数据的容量,一旦声明不能改变。
- limit:表示缓冲区中可以操作数据的大小,即超出limit的数据不能读写
- position :表示缓冲区中正在操作数据的位置
- mark 表示记录当前position的位置,可以通过reset恢复到mark的位置。
常用API
@Test
public void test1(){
//1. 分配一个指定大小的huanchongqu
ByteBuffer buf = ByteBuffer.allocate(1024);
// Invariants: mark <= position <= limit <= capacity
System.out.println(buf.position()); //0
System.out.println(buf.limit()); // 1024
System.out.println(buf.capacity()); // 1024
System.out.println(buf.mark()); // java.nio.HeapByteBuffer[pos=0 lim=1024 cap=1024]
System.out.println("----------put----------");
buf.put("sadfdv".getBytes());
System.out.println(buf.position()); //6
System.out.println(buf.limit()); // 1024
System.out.println(buf.capacity()); // 1024
// for (int i = 0; i < buf.position(); i++) {
// System.out.print((char)buf.get(i));
// }//sadfdv
System.out.println("---------切换模式---------");
/**
* postion x(6)-->0
* limit cap(1024)-->x(6)
* capacity 1024-->1024
*/
buf.flip();
System.out.println(buf.position()); //0
System.out.println(buf.limit()); // 6
System.out.println(buf.capacity()); // 1024
System.out.println("------------get-----------");
byte[] read = new byte[buf.limit()];
buf.get(read);
System.out.println(new String(read,0,buf.limit()));//sadfdv
System.out.println(buf.position()); //6
System.out.println(buf.limit()); // 6
System.out.println(buf.capacity()); // 1024
/**
* 又切换回读模式
*/
System.out.println("------------rewind-----------");
buf.rewind();
System.out.println(buf.position()); //0
System.out.println(buf.limit()); // 6
System.out.println(buf.capacity()); // 1024
System.out.println("------------clear-----------");
buf.clear();
System.out.println(buf.position()); //0
System.out.println(buf.limit()); // 1024
System.out.println(buf.capacity()); // 1024
for (int i = 0; i < 6; i++) {
System.out.print((char)buf.get(i));
}//sadfdv (其实数据还在)
}
@Test
public void test2(){
//1. 分配一个指定大小的huanchongqu
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("----------put----------");
buf.put("ABCDEFG".getBytes());
System.out.println(buf.position()); //6
System.out.println(buf.limit()); // 1024
System.out.println(buf.capacity()); // 1024
System.out.println("------------get-----------");
buf.flip();
byte[] read = new byte[buf.limit()];
buf.get(read,0,2); //read=[65,66,0,0,0,0,0]
System.out.println(new String(read,0,2));//AB
System.out.println(buf.position()); //2
buf.mark();
System.out.println("------------mark-----------");
buf.get(read,2,2);//read=[65,66,67,68,0,0,0]
System.out.println(new String(read,2,2));//CD
System.out.println(buf.position()); //4
System.out.println("------------reset-----------");
buf.reset();
System.out.println(buf.position()); //2
//如果缓冲区中还有剩余数据 return position < limit;
if (buf.hasRemaining()){
//获取缓冲区还可操作的空间 [limit - position;]
System.out.println(buf.remaining());//5
}
}
- 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM内存中
- 直接缓冲区:通过allocateDirect()方法分配缓冲区,将缓冲区建立在物理内存中;可以提高效率,但是不安全
- 进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留于常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给哪些易受基础系统的本机IO操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
- 直接字节缓冲区还可以通过FileChannel的map()方法将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer。java平台的实现有助于通过JNI从本地代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区 的内容,并且会在访问期间或稍后的某个时间抛出不确定的异常。
- 字节缓冲区域是直接的还是非直接的可以用isDirect()方法来确定。提供该方法是为了能够在性能关键型代码中执行显式缓冲区管理。
通道 Channel
- package java.nio.channels;
- 表示IO源与目标打开的连接,类似传统的”流"。只不过Channel本身不能直接访问数据,只能与Buffer进行交互。
DMA:直接存储器存储
- 主要实现类
- FileChannel;
- SocketChannel;
- ServerSocketChannel;
- DatagramChannel
- 三种方式获取通道
- 1.支持通道的类有getChannel()方法
- 本地IO:FileInputStream/ FileOutputStream/ RandomAccessStream
- 网络IO:Socket/ServerSocket/DatagramSocket
- 2.NIO2针对各个通道的类提供了静态方法open()
- 3.NIO2的Files工具类 的newByteChannel()方法
- 1.支持通道的类有getChannel()方法
复制图片
String path = "static/img/";
@Test
public void test1(){
try(FileInputStream fis = new FileInputStream(path+"save.png");
FileOutputStream fos = new FileOutputStream(path+"save-copy.png");
//1. channel
FileChannel inChannel = fis.getChannel();
FileChannel fosChannel = fos.getChannel();
){
//2. buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
//3. 将通道中的数据存入缓冲区
while (inChannel.read(buffer)!=-1){
//4. 将缓冲区的数据写入通道
buffer.flip();
fosChannel.write(buffer);
buffer.clear();
buffer.rewind();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 非直接缓冲区复制文件(内存映射文件)
*/
@Test
public void test2(){
try(
//1. channel
FileChannel inChannel = FileChannel.open(Paths.get(path,"save.png"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get(path,"copy-2.png"),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
){
//2. buffer
MappedByteBuffer inBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
//3. 直接对缓冲区的数据读写操作
byte[] temp = new byte[inBuffer.limit()];
inBuffer.get(temp);
outBuffer.put(temp);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 通道之间的传输
*/
@Test
public void test3(){
try(
//1. channel
FileChannel inChannel = FileChannel.open(Paths.get(path,"save.png"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get(path,"copy-3.png"),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
){
//3. 直接对通道操作 以下2选1
outChannel.transferFrom(inChannel, 0, inChannel.size());
// inChannel.transferTo(0,inChannel.size(),outChannel);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
- 分散读取:从Channel中的读取的数据分散到多个Buffer中去。(按照缓冲区的顺序,依次填满)
- 聚集写入:多个Buffer中的数据聚集到Channel中。
字符集Charset
@Test
public void test6() throws CharacterCodingException {
Charset cs1 = Charset.forName("GBK");
//获取编码器
CharsetEncoder ce = cs1.newEncoder();
//获取解码器
CharsetDecoder cd = cs1.newDecoder();
CharBuffer charBuffer = CharBuffer.allocate(1024);
charBuffer.put("编码器解码器");
charBuffer.flip();
ByteBuffer byteBuffer = ce.encode(charBuffer);
for (int i = 0; i < 12; i++) {
System.out.println(byteBuffer.get(i));
}
CharBuffer charBuffer1 = cd.decode(byteBuffer);
System.out.println(charBuffer1.toString());//编码器解码器
System.out.println("---------StandardCharsets.UTF_8---------");
Charset cs2 = StandardCharsets.UTF_16;
cs2 = Charset.forName("GBK");
byteBuffer.flip();
CharBuffer charBuffer2 = cs2.decode(byteBuffer);
System.out.println(charBuffer2.toString());
}
选择器 Selector
创建:
Selector selector = Selector.open();
向选择器注册通道
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
SelectionKey:表示SelectableChannnel和Selector之间的注册关系。每次向选择器注册通道就会选择一个事件(选择键)。选择键包含两个表示为整数的操作集。操作集的每一位都表示该键的通道所支持的一类可选择操作。
方法 | 描述 | 方法体 |
---|---|---|
int interestOps(); | 获取感兴趣事件集合 | 无(abstract) |
int readyOps | 获取通道已经准备就绪的操作的集合 | 无(abstract) |
SelectableChannel channel(); | 获取注册通道 | 无(abstract) |
Selector selector(); | 返回选择器 | 无(abstract) |
boolean isReadable() | 检测Channel中读事件是否就绪 | return (readyOps() & OP_READ) != 0; |
boolean isWritable() | 检测Channel中写事件是否就 | return (readyOps() & OP_WRITE) != 0; |
boolean isConnectable() | 检测Channel中连接是否就绪 | return (readyOps() & OP_CONNECT) != 0; |
boolean isAcceptable() | 检测Channel中接收是否就绪 | return (readyOps() & OP_ACCEPT) != 0; |
ps:
public static final int OP_ACCEPT = 1 << 4;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_WRITE = 1 << 2;
public static final int OP_READ = 1 << 0;
//若注册时不止监听一个事件,可以用‘|’连接
int interestSet = SelectionKey.OP_ACCEPT | SelectionKey.OP_CONNECT;
demo:
@Test
public void client(){
try (
//1. get channel
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
// FileChannel fileChannel = FileChannel.open(Paths.get(path,"save.png"), StandardOpenOption.READ);
) {
//1.2切换非阻塞式
socketChannel.configureBlocking(false);
//2. buffer
ByteBuffer buf = ByteBuffer.allocate(1024);
//3. upload file
// while (fileChannel.read(buf)!=-1){
// buf.flip();
// socketChannel.write(buf);
// buf.clear();
// }
//4. upload data
buf.put(new Date().toString().getBytes());
buf.flip();
socketChannel.write(buf);
buf.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void server() {
SocketChannel sChannel = null;
SocketChannel selectChannel = null;
final InetSocketAddress local = new InetSocketAddress(9898);
try (
//1. get ServerSocket channel
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2. 获取选择器
Selector selector = Selector.open();
// FileChannel fileChannel = FileChannel.open(Paths.get(path,"server-copy.png"),StandardOpenOption.WRITE, StandardOpenOption.CREATE);
) {
//1.2切换非阻塞式
ssChannel.configureBlocking(false);
//1.3 bind port
ssChannel.bind(local);
//2.3 注册选择器
ssChannel.register(selector, SelectionKey.OP_ACCEPT );
//2.4 轮询时获取选择器上已经就绪的事件
while (selector.select()>0){
//2.5获取当前选择器中所有注册的选择键
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey sk = iterator.next();
if (sk.isAcceptable()){
//3. get socket channel
sChannel = ssChannel.accept();
//3.2 切换成非阻塞式
sChannel.configureBlocking(false);
//3.3 将该通道注册到选择器上
sChannel.register(selector,SelectionKey.OP_READ);
}else if (sk.isReadable()){
//4 获取当前选择器上都就绪状态的通道
selectChannel = (SocketChannel) sk.channel();
//4.2 读取数据
ByteBuffer buf = ByteBuffer.allocate(1024);
int len=0;
while ((len=selectChannel.read(buf))!=-1){
buf.flip();
System.out.println(new String(buf.array(), 0, len));
buf.clear();
}
}
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (sChannel!=null){
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (selectChannel!=null){
try {
selectChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
DatagramChannel
demo
@Test
public void send(){
try (
//1. get channel
DatagramChannel dc = DatagramChannel.open();
Scanner sc = new Scanner(System.in);
){
//1.2 非阻塞式
dc.configureBlocking(false);
//2 buffer
ByteBuffer buf = ByteBuffer.allocate(1024);
//3. send data
while (sc.hasNext()){
String s = sc.nextLine();
buf.put((new Date().toString()+":\n"+s).getBytes());
dc.send(buf,new InetSocketAddress("127.0.0.1",9898));
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void receive(){
try (
//1. get channel
DatagramChannel dc = DatagramChannel.open();
//2 get selector
Selector selector = Selector.open();
Scanner sc = new Scanner(System.in);
){
//1.2 非阻塞式
dc.configureBlocking(false);
//1.3 bind listen port
dc.bind(new InetSocketAddress(9898));
//2.2
dc.register(selector, SelectionKey.OP_READ);
//2.4 轮询时获取选择器上已经就绪的事件
while (selector.select()>0){
//2.5获取当前选择器中所有注册的选择键
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey sk = iterator.next();
if (sk.isReadable()){
//3 buffer
ByteBuffer buf = ByteBuffer.allocate(1024);
//3.2 读取数据
dc.receive(buf);
buf.flip();
System.out.println(new String(buf.array(), 0, buf.limit()));
buf.clear();
}
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
管道Pipe
- NIO 管道是2个线程之间的单向数据连接。
- Pipe有一个source通道和一个sink通道。数据会被写到sink通道,而从source通道读取
@Test
public void test1() throws IOException {
Pipe pipe = Pipe.open();
ByteBuffer buf = ByteBuffer.allocate(1024);
try(
Pipe.SinkChannel sinkChannel = pipe.sink();
Pipe.SourceChannel sourceChannel = pipe.source();
) {
buf.put("通过管道发送数据".getBytes());
buf.flip();
sinkChannel.write(buf);
// read 通过管道接收数据
buf.flip();
sourceChannel.read(buf);
System.out.println(new String(buf.array(),0,buf.limit()));
} catch (IOException e) {
e.printStackTrace();
}
}