Java NIO
简介
Java NIO是java 1.4之后新出的一套IO接口,这里的的新是相对于原有标准的Java IO和Java Networking接口。NIO提供了一种完全不同的操作方式。
标准的IO编程接口是面向字节流和字符流的。而NIO是面向通道和缓冲区的,数据总是从通道中读到buffer缓冲区内,或者从buffer写入到通道中。
Java NIO使我们可以进行非阻塞IO操作。比如说,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,线程再继续处理数据。写数据也是一样的。
NIO中有一个“slectors”的概念。selector可以检测多个通道的事件状态(例如:链接打开,数据到达)这样单线程就可以操作多个通道的数据。 所有这些都会在后续章节中更详细的介绍。
概念
NIO包含下面几个核心的组件:
Channels
Buffers
Selectors
通道和缓冲区(Channels and Buffers)
通道(Channel)
通道(Channel) :由java.nio.channels包定义 的。Channel 表示I0源与目标打开的连接。 Channel类似于传统的“流”。只不过Channel 本身不能直接访问数据,Channel 只能与Buffer进行交互。
通道 Channel 用于源节点与目标的连接。在Java NIO 中负责缓冲区中数据传输。Channel本身不存储数据,因此配合缓冲区进行传输。
通道主要实现类
java.nio.channels.Channel 接口
FileChannel
SocketChannel
ServerSocketChannel
DatagramChannel
获取通道
1 java 针对支持通道的类提供了getChannel()方法
本地IO
FileInputStrem/FileOutputStream
RandomAccessFile
网络IO
Socket
ServerSocket
DatagramSocket
2 在JDK 1.7 中的NIO .2 针对各个通道提供了静态方法open();
3 在JDK 1.7 中的NIO.2 的Files 工具类的 new ByteChannel();
缓冲区
缓存区 (Buffer) 在NIO 中负责数据的存取。缓冲区就是数组,用于不同类型的数据的存储。
根据数据类型不同 Boolean 除外 ,提供了相应的类型的缓冲区:
ByteBuffer 最常用
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
以上缓冲区管理方式几乎一致,通过 allocate()获取缓冲区。
2 缓冲区存取数据的2个核心方法
put() 存入数据到缓冲区
get() 获取缓冲区的数据
3 缓冲区四个核心属性
private int mark = -1; 用于标记 记录当前 position 位置,可以通过reset 恢复到mark位置。
private int position = 0; 位置,表示缓冲区正在操作的数据,position <= limit <= capacity
private int limit; 界限,表示缓冲区可以操作数据的大小。limit 后面的数据不能进行读写。
private int capacity; 容量,表示缓冲区最大存储数据的容量,一旦声明,不能改变。
NIO缓冲区写数据
第一行是初始化缓冲区三个参数
第二行表示写数据模式
第三行表示读数据模式
初始化缓冲区,给缓冲区写入数据,读取缓冲区数据,可重复读数据,清空缓冲区数据后三个参数的变化。
//1 分配一个指定大小的缓冲器
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
System.out.println("-------------------- allocate 初始化分配 ---------------------- ");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
// 2 利用Put() 存入数据到缓冲区
String str = "12345";
byteBuffer.put(str.getBytes());
System.out.println("-------------------- put 写数据 ---------------------- ");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
// 3 切换到读数据模式
byteBuffer.flip();
System.out.println("-------------------- flip 切换读模式 ---------------------- ");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
//4 利用get() 读取缓冲区中的数据
byte dst[] = new byte[byteBuffer.limit()];
byteBuffer.get(dst);
System.out.println("读取到数据 : " + new String(dst, 0, dst.length));
System.out.println("-------------------- get 读取数据 ---------------------- ");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
// 5 rewind 可重复读数据 (可以重新从读模式开始重新读取数据)
byteBuffer.rewind();
System.out.println("-------------------- rewind 可重复读数据 ---------------------- ");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
// 6 clear 清空缓冲区 但是缓冲区数据依然存在,但是出于“被遗忘状态”。
byteBuffer.clear();
System.out.println("-------------------- clear 清空缓冲区 ---------------------- ");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
System.out.println(" 获取被遗忘数据 " + (char) byteBuffer.get());
结果
-------------------- allocate 初始化分配 ----------------------
0
1024
1024
-------------------- put 写数据 ----------------------
5
1024
1024
-------------------- flip 切换读模式 ----------------------
0
5
1024
读取到数据 : 12345
-------------------- get 读取数据 ----------------------
5
5
1024
-------------------- rewind 可重复读数据 ----------------------
0
5
1024
-------------------- clear 清空缓冲区 ----------------------
0
1024
1024
规律
0 <= mark <= position <= limit <= capacity
直接缓冲区与非直接缓冲区
非直接缓冲区 : 通过allocate() 方法分配缓冲区,将缓冲区建立在JVM内存中
直接缓冲区:通过allocateDirect() 方法分配直接缓冲区,将缓冲区建立在操作系统物理内存中。
直接与非直接缓冲区区别
直接缓冲区获取
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
//是否是直接缓冲区
System.out.println(byteBuffer.isDirect());
选择器(Selectors)
Selector是Java NIO中的一个组件,用于检查一个或多个NIO Channel的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。
选择器 Selector : 是 SelectableChannel 的多路复用。用于监控 SelectableChannel 的IO状况。
mark()标记和reset()恢复标记位置方法
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byte dst[] = new byte[byteBuffer.limit()];
String str = "abcdef";
//存
byteBuffer.put(str.getBytes());
byteBuffer.flip();
//读取前两个
byteBuffer.get(dst, 0, 2);
System.out.println(new String(dst));
System.out.println(byteBuffer.position());//2
//标记
byteBuffer.mark();
//读取后两个
byteBuffer.get(dst, 2, 2);
System.out.println(new String(dst, 2, 2));
System.out.println(byteBuffer.position());//4
// reset() 恢复到mark 标记 位置
byteBuffer.reset();
System.out.println(byteBuffer.position()); //2
//常用方法
//判断缓冲区是否剩余的数据
if (byteBuffer.hasRemaining()) {
//获取缓冲区剩余数据
System.out.println(byteBuffer.remaining()); //4 刚才 position 被 reset 恢复到 2 所以剩4个
}
FileChannel文件通道
Java NIO中的FileChannel是用于连接文件的通道。通过文件通道可以读、写文件的数据。Java NIO的FileChannel是相对标准Java IO API的可选接口。
FileChannel不可以设置为非阻塞模式,只能在阻塞模式下运行。
FileChannel 文件读取和复制
// 1 利用通道完后曾文件的复制(非直接缓冲区)
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
FileChannel inputStreamChannel = null;
FileChannel outputStreamChannel = null;
try {
//输入
inputStream = new FileInputStream("1.jpg");
//输出
outputStream = new FileOutputStream("2.jpg");
//1 获取通道
inputStreamChannel = inputStream.getChannel();
//输出通道
outputStreamChannel = outputStream.getChannel();
//2 分配指定大小的缓冲区
ByteBuffer allocate = ByteBuffer.allocate(1024);
//3 将通道中的数据存入缓冲区中
while (inputStreamChannel.read(allocate) != -1) {
//4 切换读模式
allocate.flip();
//5 将缓冲区数据写入通道中
outputStreamChannel.write(allocate);
//6 清空缓冲区
allocate.clear();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (outputStreamChannel != null) {
try {
outputStreamChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStreamChannel != null) {
try {
inputStreamChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
直接缓冲区复制文件
//直接缓冲区 channel (内存映射文件方式 ) 内存读取写文件 效率高,但是不稳定 垃圾回收机制不及时释放,会占用系统资源。
static void channel2() {
FileChannel open = null;
FileChannel write = null;
try {
// StandardOpenOption 枚举 提供读 写 模式
//将本地文件读到内存中
open = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
//读写通道 将内存中的文件读到写通道然后再写到本地
//CREATE 不管存不存在都创建覆盖; CREATE_NEW 不存在就创建,存在就报错
write = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
//内存映射文件
// 读模式 从0开始读有多少复制多少 将本地文件读到内存映射文件中
MappedByteBuffer inMappedBuf = open.map(FileChannel.MapMode.READ_ONLY, 0, open.size());
//将内存文件 读到写通道中然后再写到本地
MappedByteBuffer outMappedBuf = write.map(FileChannel.MapMode.READ_WRITE, 0, open.size());
byte[] dst = new byte[inMappedBuf.limit()];
//读文件
inMappedBuf.get(dst);
//写文件
outMappedBuf.put(dst);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (open != null) {
try {
open.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (write != null) {
try {
write.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
通道数据传输(直接缓冲区)
FileChannel in = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel out = FileChannel.open(Paths.get("4.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//从in通道传输到out通道 都是表示从in传输数据到out通道
in.transferTo(0, in.size(), out);
//out.transferFrom(out,0,in.size());
out.close();
in.close();
分散与读取
分散读取:把通道中的数据分散(按顺序)到多个缓冲区中。
聚集写入:将多个缓冲区的数据写入一个通道中。
//读文件
FileInputStream fileInputStream = new FileInputStream("NIO-Demo.iml");
//写入
FileOutputStream fileOutputStream = new FileOutputStream("NIO-Demo-test.txt");
FileChannel inputStreamChannel = fileInputStream.getChannel();
FileChannel outputStreamChannel = fileOutputStream.getChannel();
//缓冲区
ByteBuffer allocate1 = ByteBuffer.allocate(100);
ByteBuffer allocate2 = ByteBuffer.allocate(1024);
ByteBuffer[] byteBuffers = {allocate1, allocate2};
//通道 分散读
inputStreamChannel.read(byteBuffers);
for (ByteBuffer b : byteBuffers) {
//开启读模式
b.flip();
}
//缓冲区读到的数据
System.out.println(new String(byteBuffers[0].array(), 0, byteBuffers[0].limit()));
System.out.println("------------- ");
System.out.println(new String(byteBuffers[1].array(), 0, byteBuffers[1].limit()));
//通道 聚集写入
outputStreamChannel.write(byteBuffers);
outputStreamChannel.close();
inputStreamChannel.close();
管道
管道是两个线程之间单向数据连接
source通道负责读取,sink通道负责写
//1 获取管道
Pipe pipe = Pipe.open();
//缓冲区数据
ByteBuffer allocate = ByteBuffer.allocate(100);
//2 将缓冲区数据写入管道中
Pipe.SinkChannel sink = pipe.sink();
//通过单向管道发送数据
String str = "我是数据!";
allocate.put(str.getBytes());
//切换读模式
allocate.flip();
sink.write(allocate);
//3 从管道读取数据
Pipe.SourceChannel source = pipe.source();
allocate.flip();
int read = source.read(allocate);
System.out.println(new String(allocate.array(),0,read));
allocate.clear();
source.close();
sink.close();
阻塞NIO
阻塞NIO分以下两种tcp和udp
TCP:
ScoketChannel
ServerSocketChannel
UDP:
DatagramChannel
//发送
static void send() throws IOException {
// 1 获取通道 SocketChannel 是 tcp 非阻塞
SocketChannel open = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
// 2 切换非阻塞魔术
open.configureBlocking(false);
// 3 分配指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//4 发送数据
byteBuffer.put("发偶是那个数据!".getBytes());
byteBuffer.flip();
//数据
open.write(byteBuffer);
byteBuffer.clear();
//5 关闭通道
open.close();
}
//接受
static void reveice() throws IOException {
// 1 获取tcp 非阻塞通道
SocketChannel open = SocketChannel.open();
// 2 切换非阻塞模式
open.configureBlocking(false);
//3 绑定端口号
open.bind(new InetSocketAddress(8888));
//4 选择器
Selector selector = Selector.open();
//注册到选择器 同时指定模式 这里是读模式
open.register(selector, SelectionKey.OP_READ);
//轮训获取选择
while (selector.select() > 0) {
//获取所有选择键
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey sk = iterator.next();
//判断可读状态
if (sk.isReadable()) {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//切换读模式
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(), 0, byteBuffer.limit()));
}
}
}
//发送数据
open.close();
selector.close();
}