Java的NIO入门
一、介绍
Java NIO
是从Java 1.4版本开始引入的一个新的IO
,在传统的IO
模型中,使用的是同步阻塞IO
,也就是blocking IO
。
而NIO
指的是New IO
,代指新IO
模型。有些博客指的是not blocking IO
,非阻塞IO,叫哪种都行,都是NIO
。
在NIO
中,最重要的两个东西就是缓冲Buffer
和通道Channel
了。继续往下看!
二、Buffer
缓冲区Buffer
,可以理解成是一个含数组的容器对象,该对象提供了一组方法,可以更轻松地使用其中的数据。该对象记录了一些状态值,能够跟踪和记录缓冲区的状态变化情况。
后续的通道Channel
的读取、写入操作都经过缓冲。
Buffer
是一个抽象类,它的实现类有很多,但我们最常用的还是ByteBuffer
,因为要和字节打交道嘛
它里面有四个最重要的状态值,分别是
mark
:标记position
:当前读取或存储数据的索引位置,位置limit
:当前缓冲最大可以写入或读取到的位置,极限capacity
:当前缓冲的容量,容量
其中,mark
<= position
<=limit
<=capacity
,具体是什么作用,稍等看看演示代码,建议打开java的api文档来同步进行查看
1)初识缓冲
建议DEBUG,进入后查看上面的四个状态值的变化
package com.banmoon.test; | |
import java.nio.IntBuffer; | |
public class BufferTest01 { | |
public static void main(String[] args) { | |
// 创建容量大小为5的一个int缓冲 | |
IntBuffer buffer = IntBuffer.allocate(5); | |
// 放入数据,初始化时position=0,每put一次,position++,不能大于limit | |
buffer.put(1); | |
buffer.put(2); | |
buffer.put(3); | |
buffer.put(4);// position=4, limit=5 | |
// 写入完毕,反转进行读取,反转过后,position=0,limit=4 | |
buffer.flip(); | |
// 获取数据,每获取一次,position++,不能大于limit | |
System.out.println(buffer.get()); | |
System.out.println(buffer.get()); | |
System.out.println(buffer.get()); | |
System.out.println(buffer.get());// position=4,limit=4 | |
// 如果还想写入数据,清空此缓冲,再进行写入,重置position和limit | |
buffer.clear(); | |
} | |
} |
2)BufferUnderFlowException
BufferUnderFlowException
异常,存的类型和取的类型不一致所导致的异常。
不同的类型的存储大小空间不同,所以会导致读取的异常
package com.banmoon.test; | |
import java.nio.ByteBuffer; | |
public class BufferTest02 { | |
public static void main(String[] args) { | |
// 创建容量大小为5的一个byte缓冲 | |
ByteBuffer buffer = ByteBuffer.allocate(100); | |
// 放入数据 | |
buffer.putShort((short) 1); | |
buffer.putInt(1); | |
buffer.putLong(100); | |
// 反转 | |
buffer.flip(); | |
// 取出数据 | |
System.out.println(buffer.getLong()); | |
System.out.println(buffer.getFloat()); | |
System.out.println(buffer.getLong()); | |
} | |
} |
3)只读缓冲
可以将一个缓冲设置为只读,也就是说在缓冲有数据后,可以得到一个只读的缓冲,此缓冲不再支持写入。
package com.banmoon.test; | |
import java.nio.ByteBuffer; | |
public class BufferTest03 { | |
public static void main(String[] args) { | |
// 创建容量大小为5的一个缓冲 | |
ByteBuffer buffer = ByteBuffer.allocate(5); | |
// 放入数据 | |
for (int i = 0; i < buffer.capacity(); i++) { | |
buffer.put((byte) i); | |
} | |
// 反转 | |
buffer.flip(); | |
// 获取只读缓冲 | |
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer(); | |
// 循环读取 | |
while (readOnlyBuffer.hasRemaining()) { | |
System.out.println(readOnlyBuffer.get()); | |
} | |
// 写入只读异常会抛出ReadOnlyBufferException | |
// readOnlyBuffer.put(1); | |
} | |
} |
三、Channel
Channel
,称为通道,类似流,但与流有下面几点区别
- 通道可以同时进行读写,而流只能读或者写
- 通道可以实现异步读写数据
- 通道可以从缓冲读数据,可以写入数据到缓冲
Channel
是一个接口,常用的实现类如下
FileChannelImpl
:文件相关的通道ServerSocketChannel
:类似BIO
中的ServerSocket
,用于TCP的连接SocketChannel
:类似BIO
中的Socket
,用于TCP的连接DatagramChannel
:用于UDP数据的读写
多说无益,先来看看他们的使用好吧,建议打开java的api文档来同步进行查看
1)写入输出文件
package com.banmoon.test; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.nio.ByteBuffer; | |
import java.nio.channels.FileChannel; | |
public class ChannelTest01 { | |
public static void main(String[] args) throws IOException { | |
String str = "你好,半月无霜"; | |
// 创建一个输出流 | |
FileOutputStream outputStream = new FileOutputStream("E:\\repository\\test.txt"); | |
// 通过输出流获取通道 | |
FileChannel channel = outputStream.getChannel(); | |
// 将文字放入字节缓冲 | |
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes()); | |
// 将字节缓冲写入到通道 | |
channel.write(byteBuffer); | |
// 关闭输出流 | |
outputStream.close(); | |
} | |
} |
2)读取指定文件
package com.banmoon.test; | |
import java.io.File; | |
import java.io.FileInputStream; | |
import java.io.IOException; | |
import java.nio.ByteBuffer; | |
import java.nio.channels.FileChannel; | |
public class ChannelTest02 { | |
public static void main(String[] args) throws IOException { | |
File file = new File("E:\\repository\\test.txt"); | |
// 获取文件输入流 | |
FileInputStream inputStream = new FileInputStream(file); | |
// 通过输入流获取通道 | |
FileChannel channel = inputStream.getChannel(); | |
// 创建字节缓冲 | |
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length()); | |
// 将通道中的数据读取到字节缓冲 | |
channel.read(byteBuffer); | |
// 查看数据 | |
System.out.println(new String(byteBuffer.array())); | |
} | |
} |
3)拷贝文件
拷贝文件,也就是使用同一个Buffer完成读写,首先我们先准备好一个文件,我们重新创建一个文件hello.txt
,如下
你好,半月无霜! | |
1、飞流直下三千尺,不及汪伦送我情。 | |
2、醒时同交欢,儿女忽成行。 | |
3、路漫漫其修远兮,壮士一去兮不复返! | |
4、后宫佳丽三千人,铁杵磨成绣花针。 | |
5、问世间情为何物,两岸猿声啼不住。 |
开始拷贝啦
package com.banmoon.test; | |
import java.io.FileInputStream; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.nio.ByteBuffer; | |
import java.nio.channels.FileChannel; | |
public class ChannelTest03 { | |
public static void main(String[] args) throws IOException { | |
// 获取文件输入流 | |
FileInputStream inputStream = new FileInputStream("E:\\repository\\hello.txt"); | |
// 通过输入流获取通道 | |
FileChannel channel01 = inputStream.getChannel(); | |
// 获取文件输出流 | |
FileOutputStream outputStream = new FileOutputStream("E:\\repository\\hello_copy.txt"); | |
// 通过输出流获取通道 | |
FileChannel channel02 = outputStream.getChannel(); | |
// 创建字节缓冲,故意设置小一点,可以多次进入循环,大家可以DEBUG感受一下是如何运行的 | |
ByteBuffer byteBuffer = ByteBuffer.allocate(16); | |
// 循环对文件进行读取,读取后进行写入 | |
while (true) { | |
// 清空缓冲,清空上次循环的数据 | |
byteBuffer.clear(); | |
// 读取到缓冲 | |
int read = channel01.read(byteBuffer); | |
// -1代表读取结束,退出循环 | |
if (read == -1) | |
break; | |
// 反转缓冲 | |
byteBuffer.flip(); | |
// 写入通道 | |
channel02.write(byteBuffer); | |
} | |
// 关闭流 | |
inputStream.close(); | |
outputStream.close(); | |
} | |
} |
4)快速拷贝文件
拷贝文件,与上面不同的是,上面是自己写缓冲进行读写,这一次直接使用channel
的api
进行拷贝,方便快捷。
package com.banmoon.test; | |
import java.io.FileInputStream; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.nio.channels.FileChannel; | |
public class ChannelTest04 { | |
public static void main(String[] args) throws IOException { | |
// 获取文件输入流 | |
FileInputStream inputStream = new FileInputStream("E:\\repository\\hello.txt"); | |
// 通过输入流获取通道 | |
FileChannel channel01 = inputStream.getChannel(); | |
// 获取文件输出流 | |
FileOutputStream outputStream = new FileOutputStream("E:\\repository\\hello_copy.txt"); | |
// 通过输出流获取通道 | |
FileChannel channel02 = outputStream.getChannel(); | |
// 转移复制通道 | |
channel01.transferTo(0, channel01.size(), channel02); | |
// 或者可以这样写 | |
// channel02.transferFrom(channel01, 0, channel01.size()); | |
// 关闭流 | |
inputStream.close(); | |
outputStream.close(); | |
} | |
} |
四、最后
NIO
在上面的入门示例中,完全没有展现出NIO
的同步非阻塞的特点与优势,后续会开单章补上。
先简简单单入个门吧,最主要的NIO
就是缓冲和通道。
我是半月,祝你幸福!!!