1. 三个核心组件 channel、buffer、selector
- 每个channel都会对应一个buffer
- selector对应一下线程,一个线程对应多个channel
- 程序切换到哪个channel是由事件event决定的
4.selector会根据不同的事件在各个通道上切换 - buffer是一个内存块。底层是有一个数组的
- 数据的读取写入是通过buffer。BIO要么是输入流或者是输出流,不能双向,但是NIO是可以读也可以写的,需要flip方法切换。
- channel是双向的,可以反映底层操作系统的情况。比如linux,底层的操作系统通道是双向的。
2. 面向缓冲区、或者面向块编程的
数据读取到一个它稍后处理的缓冲区,需要时可以在缓冲区中前后移动,增加了处理过程的灵活性,使用它可以提供非阻塞的高伸缩性网络。
3. 非阻塞
一个线程从某通道发送请求或者读取数据,但是它仅得到目前可用的数据。如果目前没有数据可用时,就什么都不会获取,而彼不是保持线程阻塞,所以直至数据变得可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如如,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以做其他的事情
4. BIO和NIO的比较
- BIO以流的方式处理数据,而NIO以块的方式处理数据,块IO的效率比流IO高很多
- BIO通过字节流和字符流进行操作,而NIO基于channel和buffer进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中
- selector用于监听多个通道的事件(连接请求、数据到达等)。因为使用单个线程就可以监听多个客户端通道
5. HTTP2.0使用了多路复用技术,做到了同一个连接并发处理多个请求,而且并发数量比HTTP1.1大了好几个数量级。
6. Buffer
-
Buffer可以理解成数组,它通过以下3个值描述状态:
a) position:下一个元素的位置;
b) limit:可读取或写入的元素总数,position总是小于或者等于limit;
c) capacity:Buffer最大容量,limit总是小于或者等于capacity
package com.sgg.buffer;
import java.nio.IntBuffer;
/**
* @description: Buffer 基本使用
* @date : 2020/6/6 19:25
* @author: zwz
*/
public class BasicBuffer {
public static void main(String[] args) {
//举例说明一个buffer的使用
//创建一个buffer,大小为5,即可以存放5个int
IntBuffer intBuffer = IntBuffer.allocate(5);
// intBuffer.put(10);
// intBuffer.put(11);
for (int i = 0; i < intBuffer.capacity(); i++) {
intBuffer.put(i * 2);
}
//从buffer读取数据
//将buffer读写切换
intBuffer.flip();
while (intBuffer.hasRemaining()) {
System.out.println(intBuffer.get());
}
}
}
package com.sgg.buffer;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/**
* @description: MappedByteBuffer
* 1. 可以让文件直接在内存(堆外内存)中修改,操作系统不需要拷贝一次
* 堆外内存 https://blog.csdn.net/ZYC88888/article/details/80228531
* @date : 2020/6/6 21:33
* @author: zwz
*/
public class MappedByteBufferTest {
public static void main(String[] args) throws IOException {
/*
* RandomAccessFile
* 三、对该工具类的价值分析
1、大型文本日志类文件的快速定位获取数据:
得益于seek的巧妙设计,我认为我们可以从超大的文本中快速定位我们的游标,例如每次存日志的时候,我们可以建立一个索引缓存,索引是日志的起始日期,value是文本的poiniter 也就是光标,这样我们可以快速定位某一个时间段的文本内容
2、并发读写
emmm也是得益于seek的设计,我认为多线程可以轮流操作seek控制光标的位置,从未达到不同线程的并发写操作。
3、更方便的获取二进制文件
通过自带的读写转码(readDouble、writeLong等),我认为可以快速的完成字节码到字符的转换功能,对使用者来说比较友好。
*
原文链接:https://blog.csdn.net/qq_31615049/article/details/88562892
*
*/
RandomAccessFile randomAccessFile = new RandomAccessFile("E:\\CODE\\Architecture\\netty\\netty-all\\file01.txt", "rw");
FileChannel channel = randomAccessFile.getChannel();
/*
* 1. 使用读写模式
* 2. 可以直接修改的起始位置
* 3. 是映射内存的大小,即将file01.txt的多少个字节映射到内存.
*/
MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
map.put(0, (byte) 'H');
//put 超过5 下标越界
map.put(3, (byte) '9');
randomAccessFile.close();
}
}
package com.sgg.buffer;
import java.nio.ByteBuffer;
/**
* @description: 按相应的类型获取
* @date : 2020/6/6 21:19
* @author: zwz
*/
public class NIOByteBufferPutGet {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(64);
buffer.putInt(100000000);
buffer.putLong(9);
buffer.putChar('尚');
buffer.putShort((short) 4);
buffer.flip();
System.out.println();
System.out.println(buffer.getShort());
System.out.println(buffer.getInt());
System.out.println(buffer.getLong());
System.out.println(buffer.getChar());
}
}
package com.sgg.buffer;
import java.nio.ByteBuffer;
/**
* @description: 只读
* @date : 2020/6/6 21:19
* @author: zwz
*/
public class NIOByteBufferReadOnly {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(64);
for (int i = 0; i < 64; i++) {
buffer.put((byte) i);
}
buffer.flip();
//得到一个只读的buffer
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
System.out.println(readOnlyBuffer.getClass()); // class java.nio.HeapByteBufferR
while (readOnlyBuffer.hasRemaining()) {
System.out.println(readOnlyBuffer.get());
}
readOnlyBuffer.put((byte) 5); //java.nio.ReadOnlyBufferException
}
}
package com.sgg.buffer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
/**
* @description: ScatteringAndGatheringTest
* Scattering 将数据写入到buffer时,可以采用buffer数据依次写 分散
* Gatherging 从buffer读取数据时,可以采用buffer数组,依次读 聚合
*
* byteRead=8
* position=5,limit=5
* position=3,limit=3
* byteRead=8 byteWrite=8 messageLength=8
*
* cmd
* telnet 127.0.0.1 7000
* send hell000
*
* @date : 2020/6/6 21:46
* @author: zwz
*/
public class ScatteringAndGatheringTest {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
//绑定端口到socket并启动
serverSocketChannel.socket().bind(inetSocketAddress);
ByteBuffer[] byteBuffers = new ByteBuffer[2];
byteBuffers[0] = ByteBuffer.allocate(5);
byteBuffers[1] = ByteBuffer.allocate(3);
//等待客户端连接 telnet
SocketChannel socketChannel = serverSocketChannel.accept();
int messageLength = 8; //从客户端接收8个字节
//循环读取
while (true) {
long byteRead = 0;
while (byteRead < messageLength) {
//从SocketChannel中读取数据到byteBuffers.从position处开始写
long read = socketChannel.read(byteBuffers);
byteRead += read;
System.out.println("byteRead=" + byteRead);
//使用流打印,看看当前的这个buffer的position和limit
Arrays.stream(byteBuffers).map(byteBuffer ->
"position=" + byteBuffer.position() + ",limit=" + byteBuffer.limit())
.forEach(System.out::println);
}
//limit = position,position = 0
Arrays.asList(byteBuffers).forEach(Buffer::flip);
//将数据读出显示到客户端
long byteWrite = 0;
while (byteWrite < messageLength) {
//write方法使得byteBuffer的position到limit中的元素写入通道中
long write = socketChannel.write(byteBuffers);
byteWrite += write;
}
//复位
Arrays.asList(byteBuffers).forEach(Buffer::clear);
System.out.println("byteRead=" + byteRead + " byteWrite=" + byteWrite + " messageLength=" + messageLength);
}
}
}
7. channel
7.1 FileChannel
字符串写入本地文件
package com.sgg.channel;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @description:
* @date : 2020/6/6 20:21
* @author: zwz
*/
public class NIOFileChannel01 {
public static void main(String[] args) throws IOException {
String str = "hello sgg";
//创建一个输出流
FileOutputStream fileOutputStream = new FileOutputStream("E:\\CODE\\Architecture\\netty\\netty-all\\file01.txt");
//通过输出流获取对应的 FileChannel
//FileChannel 真实的类型时 FileChannelImpl
FileChannel fileChannel = fileOutputStream.getChannel();
//创建一个缓冲区 ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//将str放入到byteBuffer
byteBuffer.put(str.getBytes());
//对byteBuffer进行flip
byteBuffer.flip();
//将byteBuffer 数据写入到 fileChannel
fileChannel.write(byteBuffer);
fileOutputStream.close();
}
}
文件流包裹了channel
文件写入程序
package com.sgg.channel;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @description:
* @date : 2020/6/6 20:21
* @author: zwz
*/
public class NIOFileChannel01 {
public static void main(String[] args) throws IOException {
String str = "hello sgg";
//创建一个文件输入流
File file = new File("E:\\CODE\\Architecture\\netty\\netty-all\\file01.txt");
FileInputStream inputStream = new FileInputStream(file);
//通过fileInputStream获取对应的FileChannel 实际类型 FileChannelImpl
FileChannel channel = inputStream.getChannel();
//创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate((int) file.length());
//将通道的数据读入到buffer
channel.read(buffer);
//将buffer的字节数据转为string
System.out.println(new String(buffer.array()));
}
}
文件拷贝
package com.sgg.channel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @description:
* @date : 2020/6/6 20:21
* @author: zwz
*/
public class NIOFileChannel03 {
public static void main(String[] args) throws IOException {
//创建一个输出流
FileOutputStream fileOutputStream = new FileOutputStream("E:\\CODE\\Architecture\\netty\\netty-all\\file02.txt");
//通过输出流获取对应的 FileChannel
//FileChannel 真实的类型时 FileChannelImpl
FileChannel fileChannel = fileOutputStream.getChannel();
//创建一个文件输入流
File file = new File("E:\\CODE\\Architecture\\netty\\netty-all\\file01.txt");
FileInputStream inputStream = new FileInputStream(file);
//通过fileInputStream获取对应的FileChannel 实际类型 FileChannelImpl
FileChannel channel = inputStream.getChannel();
//创建一个缓冲区 ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
while (true) {
//将byteBuffer的标记位重置
byteBuffer.clear();
int read = channel.read(byteBuffer);
System.out.println("read: " + read);
if (read == -1) {//-1表示读取完毕
break;
}
//将byteBuffer 数据写入到 fileChannel
byteBuffer.flip();
fileChannel.write(byteBuffer);
}
inputStream.close();
fileOutputStream.close();
}
}
文件拷贝 transFrom
package com.sgg.channel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
/**
* @description:
* @date : 2020/6/6 20:21
* @author: zwz
*/
public class NIOFileChannel04 {
public static void main(String[] args) throws IOException {
//创建一个输出流
FileOutputStream fileOutputStream = new FileOutputStream("E:\\CODE\\Architecture\\netty\\netty-all\\2.png");
//通过输出流获取对应的 FileChannel
//FileChannel 真实的类型时 FileChannelImpl
FileChannel destChannel = fileOutputStream.getChannel();
//创建一个文件输入流
File file = new File("E:\\CODE\\Architecture\\netty\\netty-all\\redis持久化的意义.png");
FileInputStream inputStream = new FileInputStream(file);
//通过fileInputStream获取对应的FileChannel 实际类型 FileChannelImpl
FileChannel sourceChannel = inputStream.getChannel();
//使用transfrom完成拷贝
destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
sourceChannel.close();
destChannel.close();
inputStream.close();
fileOutputStream.close();
}
}
注意
7.2
7.3
8. Selector 选择器
- 当客户端连接时,会通过serverSocketChannel 得到 socketChannel
- 将socketChannel注册到selector上,register。selector上可以注册多个SocketChannel
- 注册后返回一个SelectionKey,会和该Selector关联(集合)
- Selector 进行监听Select方法,返回有事件发生的通道的个数。
- 进一步得到各个SelectionKey(有事件发生的)
- 在通过selectionKey反向获取socketChannel,方法channel()
-
可以通过得到的channel,完成业务处理
网友评论