一、缓冲区
/**
* 缓冲区的管理方式几乎一致,通过allocate()获取缓冲区
*
* 缓冲区的存储数据的两个核心方法:
* 1. put():存入数据到缓冲区中
* 2. get():获取缓冲区中的数据
*
* 缓冲区中的四个核心属性:
* 1. capacity:容量,表示缓冲区中最大数据存储量,一旦声明不能改变
* 2. limit:界限,表示缓冲区中可以操作数据的大小(limit后数据不能进行读写)
* 3. position:位置,表示缓冲区中正在操作数据的位置
* 4. mark:标记,表示记录当前position位置,可通过reset()恢复到mark位置
*
* 0 <= mark <= position <= limit <= capacity
*/
public class TestBuffer {
public static void main(String[] args) {
// 1. 分配一个指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println(buf.capacity()); //1024
System.out.println(buf.limit()); // 1024
System.out.println(buf.position()); // 0
// 2. 利用put方法存入数据到缓冲区
String str = "abcde";
buf.put(str.getBytes(StandardCharsets.UTF_8));
System.out.println(buf.capacity()); //1024
System.out.println(buf.limit()); // 1024
System.out.println(buf.position()); // 5
// 3. 切换到读数据模式
buf.flip();
System.out.println(buf.capacity()); //1024
System.out.println(buf.limit()); // 5
System.out.println(buf.position()); // 0
// 4. 利用get方法读取缓冲区中的数据
byte[] dst = new byte[buf.limit()];
buf.get(dst);
System.out.println(new String(dst, 0, dst.length));
System.out.println(buf.capacity()); //1024
System.out.println(buf.limit()); // 5
System.out.println(buf.position()); // 5
//5. rewind() 可重复读数据 重新开始读
//6. clear() 清空缓冲区 回到最初状态 但是缓冲区中的数据依然存在,只是处于"被遗忘状态"
buf.clear();
System.out.println((char) buf.get()); // a
}
}
1.1 缓冲区的概念
1.2 缓冲区的基本属性
1.3 Buffer常用方法
1.4 缓冲区中对数据的操作
二、直接与非直接缓冲区
2.1 概念
2.2 区别
2.3 源码
// 直接缓冲区与非直接缓冲区
// 1. 非直接缓冲区:通过allocate方法分配缓冲区,将缓冲区建立在JVM内存当中
// 2. 直接缓冲区:通过allocateDirect方法分配直接缓冲区,将缓冲区建立在物理内存中,提高效率
public void test1() {
// 分配非直接缓冲区
ByteBuffer bb1 = ByteBuffer.allocate(1024);
// 分配直接缓冲区
ByteBuffer bb2 = ByteBuffer.allocateDirect(1024);
}
2.3.1 非直接缓冲区源码
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
===================================================================
HeapByteBuffer(int cap, int lim) { // 对字节数组的操作,不涉及操作系统
super(-1, 0, lim, cap, new byte[cap], 0);
}
2.3.2 直接缓冲区源码
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
===========================================================
DirectByteBuffer(int cap) {
super(-1, 0, cap, cap);
// 下面的这个方法就是直接在操作系统内存中操作的方法
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
三、通道
3.1 概念
/**
* 1. 通道(Channel):用于源节点与目标节点的连接。
* 在java 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()
*/
3.2 非直接缓冲区的通道(基于本地文件传输)
// 利用通道完成文件的复制(非直接缓冲区)
public void test() {
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("1.jpg");
fos = new FileOutputStream("2.jpg");
// 获取通道
inChannel = fis.getChannel();
outChannel = fos.getChannel();
// 分配指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 将通道中的数据存入到缓冲区中
while (inChannel.read(buffer) != -1) { // 还有数据
// 切换到读数据模式
buffer.flip();
// 将缓冲区中的数写入到通道中
outChannel.write(buffer);
// 清空缓冲区 以便于下次读取
buffer.clear();
}
}catch (Exception e) {
e.printStackTrace();
}finally {
if (outChannel != null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inChannel != null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.3 直接缓冲区的通道(基于本地文件传输)
// 使用通道完成文件的复制(直接缓冲区--内存映射文件)
public void test2() throws Exception{
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.READ,StandardOpenOption.WRITE, StandardOpenOption.CREATE);
// 进行内存映射文件
MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
// 直接对缓存区数据进行读写操作
byte[] dst = new byte[inMappedBuf.limit()];
inMappedBuf.get(dst);
outMappedBuf.put(dst);
inChannel.close();
outChannel.close();
}
// 通道之间直接的数据传输(直接缓冲区) transferFrom() transferTo()
public void test3() throws Exception{
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.READ,StandardOpenOption.WRITE, StandardOpenOption.CREATE);
inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
outChannel.close();
}
四、分散和聚集
4.1 概念
4.2 代码
/**
* 分散读取(Scattering Reads): 将通道中的数据分散到多个缓冲区中
* 聚集写入(Gathering Writes): 将多个缓冲区中的数据聚集到通道中
*/
public void test4() throws Exception{
RandomAccessFile raf1 = new RandomAccessFile("a.txt", "rw");
// 1 获取通道
FileChannel channel1 = raf1.getChannel();
// 2 分配指定大小的缓冲区
ByteBuffer buf1 = ByteBuffer.allocate(256);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
// 3 分散读取
ByteBuffer[] buffers = new ByteBuffer[]{buf1, buf2};
channel1.read(buffers);
// 4 查看缓冲区中的数据
for (ByteBuffer buffer : buffers) {
buffer.flip();
}
System.out.println(new String(buffers[0].array(), 0, buffers[0].limit()));
System.out.println("++++++++++++++++++++++++++++++");
System.out.println(new String(buffers[1].array(), 0, buffers[1].limit()));
// 5 聚集写入
RandomAccessFile raf2 = new RandomAccessFile("b.txt", "rw");
FileChannel channel2 = raf2.getChannel();
channel2.write(buffers);
}
===================================运行结果=======================================
linux -- SVN -- Git -- Dubbo -- SpringSession -- Redis -- RabbitMQ -- Mycat -- Nginx -- FastDFS -- Springboot -- Spring Cloud Netflix -- Spirng Cloud Alibaba -- Docker -- K8S
Maven -- Linux -- Redis6 -- Git -- Docker -- Elasticsearch -- Dubbo -- Nginx
++++++++++++++++++++++++++++++
-- ActiceMQ -- Spring Security -- JVM -- JUC -- MySql高级 -- Mycat -- k8s -- Shiro
Maven -- Linux -- Git -- Redis -- Dubbo -- SpringSession -- RabbitMQ -- Nginx -- ActiveMQ -- Shiro -- Spring Security -- Docker -- Elasticsearch -- k8s -- Mysql高级 -- Mycat
4.3 FileChannel常用方法
五、字符集
/**
* 字符集:Charset
* 编码: 字符串 -> 字符数组
* 解码: 字符数组 -> 字符串
*/
public void test5() throws Exception{
/*
// 获取所有字符集
SortedMap<String, Charset> map = Charset.availableCharsets();
Set<Map.Entry<String, Charset>> set = map.entrySet();
for (Map.Entry<String, Charset> stringCharsetEntry : set) {
System.out.println(stringCharsetEntry.getKey() + "=" + stringCharsetEntry.getValue());
}*/
Charset cs1 = Charset.forName("GBK");
// 获取编码器
CharsetEncoder ce = cs1.newEncoder();
// 获取解码器
CharsetDecoder cd = cs1.newDecoder();
CharBuffer cBuf = CharBuffer.allocate(1024);
cBuf.put("中北大学!");
cBuf.flip();
// 编码
ByteBuffer bBuf = ce.encode(cBuf);
for (int i = 0; i < bBuf.limit(); i++) {
System.out.println(bBuf.get());
}
// 解码
bBuf.flip();
CharBuffer cBuf2 = cd.decode(bBuf);
System.out.println(cBuf2.toString());
}
六、NIO 非阻塞式网络通信
6.1 基础知识
/**
* 使用NIO完成网络通信的三个核心:
* 1. 通道(Channel):负责连接
* java.nio.channels.Channel接口:
* |-> SelectableChannel
* |->SocketChannel
* |->ServerSocketChannel
* |->DatagramChannel
*
* |->Pipe.SinkChannel
* |->Pipe.SourceChannel
*
* 2. 缓冲区(Buffer):负责数据的存取
* 3. 选择器(Selector):是SelectableChannel的多路服用器。用于监控SelectableChannel的IO状况
*/
6.2 阻塞式
// 客户端
@Test
public void client() throws Exception{
// 获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
// 分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
// 读取本地文件,并发送到服务端
while (inChannel.read(buf) != -1) {
buf.flip();
sChannel.write(buf);
buf.clear();
}
// 关闭通道
inChannel.close();
sChannel.close();
}
// 服务端
@Test
public void server() throws Exception {
// 获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.READ);
// 绑定连接
ssChannel.bind(new InetSocketAddress(9898));
// 获取客户端连接的通道
SocketChannel sChannel = ssChannel.accept();
// 分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
// 接收客户端数据,并保存到本地
while (sChannel.read(buf) != -1) {
buf.flip();
outChannel.read(buf);
buf.clear();
}
// 关闭通道
sChannel.close();
outChannel.close();
ssChannel.close();
}
6.3 非阻塞式
6.3.1 (SocketChannel & ServerSocketChannel)
@Test
public void client() throws Exception {
// 获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
// 切换为非阻塞模式
sChannel.configureBlocking(false);
// 分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
// 发送数据给客户端
buf.put(new Date().toString().getBytes());
buf.flip();
sChannel.write(buf);
buf.clear();
// 关闭通道
sChannel.close();
}
@Test
public void server() throws Exception {
// 获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
// 切换为非阻塞模式
ssChannel.configureBlocking(false);
// 绑定连接
ssChannel.bind(new InetSocketAddress(9898));
// 获取选择器
Selector selector = Selector.open();
// 将通道注册到选择器上,并指定 监听'接收'事件
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
// 轮询式的获取选择器上已经 准备就绪 的事件
while (selector.select() > 0) {
// 获取当前选择器中所有已经注册的 选择键(已就绪的监听事件)
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
// 获取 准备就绪 的事件
SelectionKey selectionKey = it.next();
// 判断具体是什么事件准备就绪
if (selectionKey.isAcceptable()) {
// 若 接收就绪 获取客户端连接
SocketChannel sChannel = ssChannel.accept();
// 切换非阻塞模式
sChannel.configureBlocking(false);
// 将该通道注册到选择器上
sChannel.register(selector, SelectionKey.OP_READ);
}else if (selectionKey.isReadable()) {
// 获取当前选择器上 读就绪 状态的通道
SocketChannel sChannel = (SocketChannel) selectionKey.channel();
// 读取数据
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();
}
}
// 取消选择键
it.remove();
}
}
ssChannel.close();
}
6.2.2 (DatagramChannel)
@Test
public void client() throws Exception{
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
String str = sc.next();
buf.put((LocalDateTime.now().toString() + "->" + str).getBytes());
buf.flip();
dc.send(buf, new InetSocketAddress("127.0.0.1", 9898));
buf.clear();
}
dc.close();
}
@Test
public void server() throws Exception{
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
dc.bind(new InetSocketAddress(9898));
Selector selector = Selector.open();
dc.register(selector, SelectionKey.OP_READ);
while (selector.select() > 0) {
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey sk = it.next();
if (sk.isReadable()) {
ByteBuffer buf = ByteBuffer.allocate(1024);
dc.receive(buf);
buf.flip();
System.out.println(new String(buf.array(), 0, buf.limit()));
buf.clear();
}
it.remove();
}
}
}
七、管道
7.1 使用
八、补充知识(NIO2 / Path / Paths / Files / try语法糖)