java笔记-IO、NIO学习
IO 流
结构图
概念
流是一个抽象概念,可以抽象成一个数据传输的管道,是相对于程序来说的,按照方向来分:程序从外部读取信息时的管道就是输入流(InputStream的子类),把数据传输到外部时就是输出流(OutputStream的子类)。按照流的基本单位来分:传输过程中,传输数据的最基本单位是字节的流为字节流(通常以stream结尾),基本单位是字符的流为字符流(通常以reader和writer结尾)
字符流和字节流、输入流与输出流
[https://blog.csdn.net/zhaoyanjun6/category_6684545.html]:
同步、异步、阻塞、非阻塞
**同步:**在发出一个功能调用之后,在没有得到结果之前,该调用就不会返回。也就是说调用就必须是一件一件是的来做,只能等前一件事做完才能开始下一件事。通俗来说:调用不返回结果,我死等结果
**异步:**当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。通俗来说:调用后不需要管结果是否咋样,给结果以后会在完成后通知我。例如ajax。
**阻塞:**调用结果返回前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行),函数只有在得到结果之后才会返回。与同步不同,针对的对象不一样。同步一般指的是调用者。阻塞一般指的是被调用者
**非阻塞:**指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
Linux 五中IO模型
阻塞I/O(blocking I/O)
非阻塞I/O (nonblocking I/O)
I/O复用(select 和poll) (I/O multiplexing)
信号驱动I/O (signal driven I/O (SIGIO))
5)异步I/O (asynchronous I/O (the POSIX aio_functions))
参考:https://www.cnblogs.com/chaser24/p/6112071.html
BIO(同步阻塞IO)
就是传统的java.io包,它是基于流模型实现的,交互的方式是同步、阻塞方式。也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用顺序是可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈
NIO(同步非阻塞IO)
是 Java 1.4 引入的 java.nio 包,提供了 Channel(通道)、Selector(选择)、Buffer(缓冲) 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。与传统IO最大的区别就是传统IO是面向流的,而NIO面向的是缓冲区。
Channel
概述:
类似于Stream,不同的是Stream是单向的,而Channel是双向的 ,既可以进行读操作,也可以进行写操作
常用类
- FileChannel(文件通道,一般用来处理文件)
- DatagramChannel(数据报通道,一般用来处理UDP发送的数据报)
- SocketChannel(TCP服务端通道)
- ServerSocketChannel(TCP客户端通道)
package com.learn.javaNIO;
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;
/**
* 1.通道(channel):用于源节点与目标节点的连接。在NIO中负责缓冲区中数据的传输。channel本身不存在数据,
* 因此需要配合缓冲区进行传输
*
* 2.通道的主要实现
* java.nio.channels.Channel接口:
* FileChannel
* SocketChannel
* ServerSocketChannel
* DatagramChannel
*
* 3.获取通道
* 1.java针对支持通道的类提供了getChannel()方法
* 本地IO:
* FileInputStream/FileOutputStream
* RandomAccessFile
*
* 网络IO:
* Socket
* ServerSocket
* DatagramSocket
* 2.在JDK1.7后中的NIO2中针对各个通道提供了静态方法open()
* 3.在JDK1.7中NIO2中Files工具的newByteChannel()
*
* 4.通道直接的数据传输
* transferFrom()
* transferTo()
*
* 5.放散(Scatter) 与 聚集(Gather)
* 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中,按照缓冲区的顺序,从
* Channel中读取的数据依次将Buffer填满
* 聚集写入(Gathering writes):将多个缓冲区的数据聚集道通道中,按照缓冲区的顺序,
* 写入position和limit之间的数据到Channel
*
* 6.字符集:Charset
* 编码:字符串->字节数组
* 解码: 字节数组 -> 字符串
*/
public class ChannelTest {
private ByteBuffer buffer;
@Test
public void test1() throws IOException {
//利用通道完成文件的复制
FileInputStream fis = new FileInputStream("E:\\learn_source\\java\\source\\class_fx_zj\\src\\1.txt");
FileOutputStream fos = new FileOutputStream("E:\\learn_source\\java\\source\\class_fx_zj\\src\\2.txt");
//获取通道
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
//分配指定大小的缓冲区
buffer = ByteBuffer.allocate(1024);
//将通道中的数据存入缓冲区中
while ((inChannel.read(buffer)) != -1){
buffer.flip();//切换成读取模式
//将缓冲区的数据写入通道中
outChannel.write(buffer);
buffer.clear();
}
if(outChannel != null){
outChannel.close();
}
if(inChannel != null){
inChannel.close();
}
}
/**
* 使用直接缓冲区完成文件的复制
*/
@Test
public void test2() throws Exception{
FileChannel inChannel = FileChannel.open(Paths.get("E:\\learn_source\\java\\source\\class_fx_zj\\src\\1.txt"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("E:\\learn_source\\java\\source\\class_fx_zj\\src\\3.txt"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
//内存映射文件(只有byteBuffer支持),与allocateDirect一样
MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
//直接对缓冲区进行数据的读写操作
byte[] bytes = new byte[inMappedBuf.limit()];
inMappedBuf.get(bytes);
outMappedBuf.put(bytes);
}
@Test
public void test3() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("E:\\learn_source\\java\\source\\class_fx_zj\\src\\1.txt"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("E:\\learn_source\\java\\source\\class_fx_zj\\src\\5.txt"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
//直接缓冲区
//inChannel.transferTo(0,inChannel.size(),outChannel);
outChannel.transferFrom(inChannel,0,inChannel.size());
inChannel.close();
outChannel.close();
}
@Test
public void test4() throws Exception{
RandomAccessFile raf = new RandomAccessFile("E:\\learn_source\\java\\source\\class_fx_zj\\src\\1.txt","rw");
//获取通道
FileChannel fileChannel = raf.getChannel();
//分配指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(5);
ByteBuffer buffer1 = ByteBuffer.allocate(1024);
//分散读取
ByteBuffer[] bufs = {buffer,buffer1};
fileChannel.read(bufs);
for (ByteBuffer buf : bufs) {
buf.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 raf1 = new RandomAccessFile("E:\\learn_source\\java\\source\\class_fx_zj\\src\\6.txt","rw");
FileChannel channel = raf1.getChannel();
channel.write(bufs);
}
/**
* 字符集
*/
@Test
public void test5(){
SortedMap<String, Charset> charsetMap = Charset.availableCharsets();
Set<Map.Entry<String, Charset>> entries = charsetMap.entrySet();
//查看所支持的字符集
for (Map.Entry<String, Charset> entry : entries) {
System.out.println(entry.getKey()+"="+entry.getValue());
}
}
/**
* 字符集使用
*/
@Test
public void test6() throws CharacterCodingException {
Charset cs1 = Charset.forName("GBK");
//获取编码器
CharsetEncoder encoder = cs1.newEncoder();
//获取解码器
CharsetDecoder decoder = cs1.newDecoder();
//分配一个指定大小的缓冲区
CharBuffer buffer = CharBuffer.allocate(1024);
buffer.put("中国加油,武汉加油");
buffer.flip();
//使用编码器进行编码
ByteBuffer byteBuffer = encoder.encode(buffer);
for (int i = 0; i < byteBuffer.limit(); i++) {
System.out.println(byteBuffer.get());
}
System.out.println("====================");
//使用解码器进行解码
byteBuffer.flip();
CharBuffer buffer2 = decoder.decode(byteBuffer);
System.out.println(buffer2.toString());
System.out.println("====================");
//按照UTF-8解码
byteBuffer.flip();
Charset charset = Charset.forName("UTF-8");
CharBuffer charBuffer = charset.decode(byteBuffer);
System.out.println(charBuffer.toString());
}
}
Buffer
package com.learn.javaNIO;
import org.junit.Test;
import java.nio.ByteBuffer;
/**
* 1、缓冲区:在java NIO总负责数据的存取。缓冲区本质上就是数组,用于存储不同的数据
* 更具数据类型不同(boolean除外),提供了相应类型的缓冲区
* ByteBuffer
* CharBuffer
* ShortBuffer
* IntBuffer
* LongBuffer
* FloatBuffer
* DoubleBuffer
*
* 2、缓冲区存取数据的两个核心方法:
* put():存入数据到缓冲区中
* get():获取缓冲区中的数据
*
* 3、缓冲区中的四个核心属性
* private int mark = -1;//标记,可以记录当前position的位置,可以通过reset()恢复到mark的位置
* private int position= 0;位置,表示缓冲区中正在操作的数据位置
* private int limit;界限,表示缓冲区总可以操作数据的大小。(limit后的数据不能进行读写)
* private int capacity;容量,标识缓冲区中最大存储数据的容量。一旦申明就不能更改
* 0 <= mark <= position <= limit <= capacity
*
*
* 4.直接缓冲区与非直接缓冲区:
* 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在jvm中
* 直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率,但会占用更多的资源
*
*
*/
public class BufferTest {
@Test
public void test01(){
//分配一个指定带下的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.out.println("--------allocate()----------");
System.out.println(buffer.position());//0
System.out.println(buffer.capacity());//1024
System.out.println(buffer.limit());//1024
//2.利用put方法存入数据到缓冲区中(写数据模式)
String str = "abcde";
buffer.put(str.getBytes());
System.out.println("--------put()----------");
System.out.println(buffer.position());//5
System.out.println(buffer.capacity());//1024
System.out.println(buffer.limit());//1024
//3.切换成读取数据模式,调用flip()方法
buffer.flip();
System.out.println("--------flip()----------");
System.out.println(buffer.position());//0
System.out.println(buffer.capacity());//1024
System.out.println(buffer.limit());//5
//4.利用get()读取缓冲区的数据
byte[] dist = new byte[buffer.limit()];
buffer.get(dist);
System.out.println(new String(dist,0,dist.length));
System.out.println("--------get()----------");
System.out.println(buffer.position());//5
System.out.println(buffer.capacity());//1024
System.out.println(buffer.limit());//5
//5.rewind():重新回到读写模式的最初状态
buffer.rewind();
System.out.println("--------rewind()----------");
System.out.println(buffer.position());//0
System.out.println(buffer.capacity());//1024
System.out.println(buffer.limit());//5
//6.clear()清空缓冲区,但是缓冲区的数据依然存在。只是处在“被遗忘”状态
buffer.clear();
System.out.println("--------clear()------------");
System.out.println(buffer.position());//0
System.out.println(buffer.capacity());//1024
System.out.println(buffer.limit());//1024
System.out.println((char)buffer.get());//a
}
@Test
public void test2(){
String str = "abcdefg";
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(str.getBytes());
buffer.flip();
byte[] dist = new byte[buffer.limit()];
buffer.get(dist,0,2);
System.out.println(new String(dist,0,dist.length));
//在此处标记一个mark
buffer.mark();
buffer.get(dist,2,2);
System.out.println(new String(dist,2,2));
System.out.println(buffer.position());
//此时position为4,使用reset恢复到mark标记位置
buffer.reset();
System.out.println(buffer.position());
}
@Test
public void test3(){
//分配直接缓冲区
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
//判断是否是直接缓冲区 可以调用isDirect()
System.out.println(buffer.isDirect());//true
}
}
Selector
package com.learn.javaNIO;
import org.junit.Test;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
/**
* 选择器测试(NIO多路复用)
* Selector:
* 可以注册到很多个Channel上,监听各个Channel上发生的事件,并且能够根据事件情况决定Channel读写。
* 这样,通过一个线程管理多个Channel,就可以处理大量网络连接了。
*
*/
public class SelectorTest {
@Test
public void testServer() throws Exception{
//通过Selector.open()方法来创建一个Selector对象
Selector selector = Selector.open();
//创建一个服务端channel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//设置为异步,要是用selector监听通道,必须将通道设置为非阻塞,所以FileChannel不能用Selector监听,因为其不能设置为非阻塞
serverSocketChannel.configureBlocking(false);
/**
* 将通道注册到selector上
* serverSocketChannel.register()注册方法的返回值,可以用来表示当前通道当前的状态,
* 用在注册方法的参数是,可以用来表示触发当前监听的
*
* register(Selector sel, int ops, Object att):注册通道方法,返回一个SelectionKey对象
* sel: 要注册的选择器对象
* ops:SelectionKey.OP_CONNECT: 连接就绪事件,表示客户端与服务器的连接已经建立成功
* SelectionKey.OP_ACCEPT: 接收连接事件,表示服务器监听到了客户连接,服务器可以接收这个连接了
* SelectionKey.OP_READ: 读 就绪事件,表示通道中已经有了可读的数据,可以执行读操作了
* SelectionKey.OP_WRITE: 写 就绪事件,表示已经可以向通道写数据了
* att: 附加的对象(可选值),一般用来放一个缓冲区
*
* 返回的SelectionKey包含了以下属性
* interest集合:selectionKey.interestOps()
* ready集合:selectionKey.interestOps(),可以用来判断当前处在哪个阶段,也可用以下四个方法判断
* isConnectable()
* isAcceptable()
* isReadable()
* isWritable()
* Channel:
* Selector:
* 附加的对象(可选):
*
*/
//serverSocketChannel注册时需要使用SelectionKey.OP_ACCEPT,其他事件无效且会报非法参数异常
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
//绑定端口号
serverSocketChannel.bind(new InetSocketAddress(9999));
while(true){
int select = selector.select();
if(select == 0){
continue;
}
//拿到所有注册的keys
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()){
SelectionKey next = iterator.next();
if(next.isAcceptable()){//接受就绪状态
ServerSocketChannel channel = (ServerSocketChannel)next.channel();
SocketChannel socketChannel = channel.accept();
socketChannel.configureBlocking(false);
//注册客户端接受的channel
socketChannel.register(selector,SelectionKey.OP_READ,ByteBuffer.allocate(1024));
}
if(next.isConnectable()){//连接就绪状态
System.out.println("isConnectable is true");
}
if(next.isReadable()){//读就绪状态
ByteBuffer buf = (ByteBuffer)next.attachment();
SocketChannel socketChannel = (SocketChannel) next.channel();
while (socketChannel.read(buf) > 0){
buf.flip();
System.out.println(new String(buf.array(),0,buf.limit()));
buf.clear();
}
}
if(next.isWritable()){//写就绪状态
ByteBuffer buf = (ByteBuffer)next.attachment();
SocketChannel socketChannel = (SocketChannel) next.channel();
while (buf.hasRemaining()){
socketChannel.write(buf);
}
}
iterator.remove();
}
}
}
@Test
public void testClient(){
//使用try-with-resource自动关闭socketChannel
try (
//创建socketChannel,并绑定到点前主机的9999端口号
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(InetAddress.getLocalHost(), 9999));
){
//设置为非阻塞
socketChannel.configureBlocking(false);
if(socketChannel.finishConnect()){
socketChannel.write(ByteBuffer.wrap("nextLine".getBytes()));
}
}catch (Exception e){
e.printStackTrace();
}
}
}
AIO(异步非阻塞IO)
是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作
- AsynchronousSocketChannel
- AsynchronousServerSocketChannel
- AsynchronousFileChannel
- AsynchronousDatagramChannel