Java NIO 和 IO 的区别
缓冲区存取数据的两个核心方法
put:存入数据到缓冲区
get:获取缓冲区中的数据
缓冲区的四个核心属性
capacity:容量,表示缓冲区中最大存储数据的容量,一旦声明不能改变
position:位置,表示缓冲区中正在操作数据的位置
limit:界限,表示缓冲区中可以操作数据的大小。(limit后的数据不能进行读写)
mark:标记,表示记录当前position的位置,可以通过reset恢复到mark的位置
0 <= mark <= position <= limit <= capacity
直接缓冲区与非直接缓冲区
非直接缓冲区:通过allocate()方法分配的缓冲区,将缓冲区建立在JVM的内存中
直接缓冲区:通过allocateDirect()方法分配的缓冲区,将缓冲区建立在操作系统的物理内存中。可以提高效率
非直接缓冲区工作原理图
直接缓冲区工作原理图
通道
通道(Channel):由java.nio.channels包定义的。channel表示IO源与目标打开的连接。channel类似于传统的“流”。只不过channel本身不能直接访问数据,channel只能与buffer进行交互,在Java NIO中负责缓冲区数据的传输。
应用程序向系统发起读写请求,调用操作系统的IO接口,IO接口由CPU统一调配,当读写请求过大,会大大占用CPU的资源,会严重影响效率,CPU要处理大量的IO请求,分配IO接口,就没法做其他事情了。
CPU:中央处理器
进行了修改,添加了DMA,直接存储器;当应用程序向操作系统发起IO请求,首先DMA会向CPU申请权限,如果CPU给与权限,那么后续的读写请求就全权由DMA负责操作;这样的好处就是在执行IO请求时,CPU可以不进行干预,去处理其他事情
但是DMA仍然有缺点,比如当一个大型的应用程序发起大量的IO请求,DMA仍然要向CPU请求资源,影响效率
在IO接口和内存之间,会有一个DMA传输数据总线
通道,可以理解为一个完全独立的处理器,专门用于IO操作;通道仍然依附于CPU,但是它有自己的一套指令,是独立的处理器
通道的主要实现类
JDK1.7以后有三种方式
1、Java针对支持通道的类提供了getChannel()方法
本地IO:FileInputStream/FileOutputStream/RandomAccessFile
网络IO:Socket/ServerSocket/DatagramSocket
2、在JDK1.7中的NIO.2 针对各个通道提供了一个静态方法 open()
3、在JDK1.7中的NIO.2 的Files工具栏的newByteChannel()
//用非直接通道完成文件的传输
@Test
public void test5(){
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
//获取文件流
fis = new FileInputStream("E:\\休闲生活\\桌面壁纸\\王丽坤.jpg");
fos = new FileOutputStream("E:\\休闲生活\\桌面壁纸\\2.jpg");
// 1. 获取通道
inChannel = fis.getChannel();
outChannel = fos.getChannel();
// 2. 分配缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
// 3. 读取数据
while (inChannel.read(buf) != -1){
// 4. 切换读模式
buf.flip();
// 5. 写数据
outChannel.write(buf);
buf.clear(); // 缓冲区循环重复读写数据
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 6. 关闭通道,关闭流
if(inChannel!=null){
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(outChannel!=null){
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//用直接通道完成文件的传输
@Test
public void test6(){
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
//FileChannel.open()的两个参数:路径path,模式
//StandardOpenOption.READ 读模式
inChannel = FileChannel.open(Paths.get("E:\\休闲生活\\桌面壁纸\\王丽坤.jpg"), StandardOpenOption.READ);
//StandardOpenOption.CREATE_NEW 创建模式,当路径下有同名文件时报错,没有就创建
//StandardOpenOption.CREATE 创建模式,当路径下有同名文件时会覆盖,没有就创建
outChannel = FileChannel.open(Paths.get("E:\\休闲生活\\桌面壁纸\\2.jpg"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
// 内存映射文件
MappedByteBuffer inMappedBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
//读写文件
byte[] bytes = new byte[inMappedBuffer.limit()];
inMappedBuffer.get(bytes);
outMappedBuffer.put(bytes);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(inChannel!=null){
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(outChannel!=null){
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
通道间的数据传输
transferTo()
transferFrom()
// 通道之间的数据传输
@Test
public void test7() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("E:\\学习视频\\JavaNIO\\nio\\1. 尚硅谷_NIO_NIO 与 IO 区别.avi"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("E:\\学习视频\\JavaNIO\\nio\\1.avi"),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
inChannel.transferTo(0,inChannel.size(),outChannel);
// outChannel.transferFrom(inChannel,0,inChannel.size());
inChannel.close();
outChannel.close();
}
分散(Scatter)与聚集(Gather)
分散读取:Scattering Reads,将通道中的数据分散到多个缓冲区中
聚集写入:Gathering Writes,将多个缓冲区中的数据聚集到通道中
//分散与聚集
@Test
public void test8() throws IOException {
RandomAccessFile file = new RandomAccessFile("C:\\Users\\FMM.000\\Desktop\\spring ioc流程.txt","rw");
// 获取通道
FileChannel fileChannel = file.getChannel();
// 分配指定大小的缓冲区
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
// 分散读取
ByteBuffer[] bufs = {buf1,buf2};
fileChannel.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 file1 = new RandomAccessFile("C:\\Users\\FMM.000\\Desktop\\ioc流程.txt","rw");
FileChannel fileChannel1 = file1.getChannel();
fileChannel1.write(bufs);
// 关闭通道
fileChannel.close();
fileChannel1.close();
}
// 编码解码
@Test
public void test10() throws CharacterCodingException {
Charset charset = Charset.forName("GBK");
// 获取编码器
CharsetEncoder encoder = charset.newEncoder();
// 获取解码器
CharsetDecoder decoder = charset.newDecoder();
CharBuffer charBuffer = CharBuffer.allocate(1024);
charBuffer.put("尚硅谷威武!");
// 切换读模式
charBuffer.flip();
// 编码
ByteBuffer buffer = encoder.encode(charBuffer);
for (int i = 0; i < 12; i++) {
System.out.println(buffer.get());
}
// 解码
buffer.flip();
CharBuffer cb = decoder.decode(buffer);
System.out.println(cb.toString());
System.out.println("============================");
buffer.flip();
Charset cs = Charset.forName("UTF-8");
CharBuffer cBuf = cs.decode(buffer);
System.out.println(cBuf.toString());
}
使用NIO完成网络通信的三大核心:
1、通道(Channel):负责连接
java.nio.channels.Channel 接口:
|--SelectableChannel
|--SocketChannel
|--ServerSocketChannel //上两个是TCP
|--DatagramChannel //UDP,都是用于网络 IO
|--Pipe.SinkChannel
|--Pipi.SourceChannel
2、缓冲区(Buffer):负责数据的存取
3、选择器(Selector):是 SelectableChannel 的多路复用器,用于监控 SelectableChannel 的 IO 状况
SelectionKey:表示 SelectableChannel 和 Selector 之间的注册关系。每次向选择器注册通道时就会选泽一个事件(选择键)
示例1:阻塞式IO
当client端向server端发送请求时,如果server端不能确定client请求的读/写的数据,server端会处于阻塞状态,阻塞状态下server端下的此线程不能做其他操作,一直等待,当server有client端需要读/写的数据时,会将数据读/写给用户,然后释放资源
// 模拟网络IO,客户端
@Test
public void nioClient(){
SocketChannel socketChannel = null;
FileChannel fileChannel = null;
try {
// 1. 获取通道
socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));
fileChannel = FileChannel.open(Paths.get("E:\\休闲生活\\桌面壁纸\\无情的战争.jpg"), StandardOpenOption.READ);
// 2. 分配指定大小缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
// 3. 读取本地文件,并发送到服务器
while (fileChannel.read(buf)!=-1){
buf.flip();
socketChannel.write(buf);
buf.clear();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭通道
try {
if(fileChannel!=null){
fileChannel.close();
}
if (socketChannel!=null){
socketChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 服务端
@Test
public void nioServer(){
ServerSocketChannel ssChannel = null;
FileChannel outChannel = null;
try {
// 1. 获取通道
ssChannel = ServerSocketChannel.open();
outChannel = FileChannel.open(Paths.get("E:\\休闲生活\\桌面壁纸\\2.jpg"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);
// 2. 绑定连接
ssChannel.bind(new InetSocketAddress(9898));
// 3. 获取客户端通道
SocketChannel sChannel = ssChannel.accept();
// 4. 分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
// 5. 接收客户端数据,并保存到本地
while (sChannel.read(buf)!=-1){
buf.flip();
outChannel.write(buf);
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭通道
try {
if(outChannel!=null){
outChannel.close();
}
if(ssChannel!=null){
ssChannel.close();
}
} catch (Exception e){
e.printStackTrace();
}
}
}
非阻塞式IO
示例1:非阻塞式IO
public class TestNonBlockingIO {
@Test
public void client() throws IOException {
//获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//切换成非阻塞式
sChannel.configureBlocking(false);
//分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//发送数据给服务端
// buf.put(LocalDateTime.now().toString().getBytes());
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String str = scanner.next();
buf.put((new Date().toString() +"\n" + str).getBytes());
buf.flip();
sChannel.write(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){
// 7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()){
// 8. 获取准备“就绪”的事件
SelectionKey sk = it.next();
// 9. 判断具体是什么事件准备就绪
if(sk.isAcceptable()){
// 10. 若“接收就绪”,获取客户端连接
SocketChannel sChannel = ssChannel.accept();
// 11. 切换非阻塞模式
sChannel.configureBlocking(false);
// 12. 将该通道注册到选择器上
sChannel.register(selector,SelectionKey.OP_READ);
} else if(sk.isReadable()){
// 13. 获取当前选择器上“读就绪”状态的通道
SocketChannel sChannel = (SocketChannel) sk.channel();
// 14. 读取数据
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();
}
}
// 15. 取消选择键,SelectionKey
it.remove();
}
}
}
}