предисловие
Когда данные сериализуются и сохраняются или когда данные передаются по сети, неизбежно преобразование данных в массивы байтов. Чтение и запись байтовых массивов не слишком сложны, но немного громоздки.Чтобы избежать повторения создания колес, jdk представила ByteBuffer, чтобы помочь нам работать с байтовыми массивами, а netty — популярный в настоящее время сетевой IO-фреймворк Java. который внутренне определяет ByteBuf используется для управления массивом байтов, который похож на ByteBuffer
- ByteBuffer
- MappedByteBuffer с нулевым копированием
- Механизм восстановления памяти вне кучи DirectByteBuffer
- ByteBuf нетти
Следите за официальной учетной записью, общайтесь друг с другом и ищите в WeChat: Sneak forward
Буферная структура
public abstract class Buffer {
//关系: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
long address; // Used only by direct buffers,直接内存的地址
- mark: если вызывается метод mark(), значение отметки будет хранить значение текущей позиции.Когда метод reset() вызывается в следующий раз, значение позиции будет установлено на предыдущее значение отметки.
- position: индекс нижнего индекса следующего элемента байта, который будет прочитан и записан
- Предел: является ли индекс индекса массива первого элемента в буфере, который не может быть прочитан или записан, а также можно рассматривать как фактическое количество элементов в буфере
- вместимость: максимальное количество элементов, которое может содержать буфер. Это значение устанавливается при создании буфера и не может быть изменено.
Buffer.API
Buffer(int mark, int pos, int lim, int cap)
//Buffer创建时设置的最大数组容量值
public final int capacity()
//当前指针的位置
public final int position()
//限制可读写大小
public final Buffer limit(int newLimit)
//标记当前position的位置
public final Buffer mark()
//配合mark使用,position成之前mark()标志的位置。先前没调用mark则报错
public final Buffer reset()
//写->读模式翻转,单向的
//position变成了初值位置0,而limit变成了写模式下position位置
public final Buffer flip()
//重置position指针位置为0,mark为-1;相对flip方法是limit不变
public final Buffer rewind() //复位
//和rewind一样,多出一步是limit会被设置成capacity
public final Buffer clear()
//返回剩余未读字节数
public final int remaining()
Структура байтового буфера
public abstract class ByteBuffer extends Buffer
implements Comparable<ByteBuffer>{
final byte[] hb; //仅限堆内内存使用
final int offset;
boolean isReadOnly;
ByteBuffer.API
//申请堆外内存
public static ByteBuffer allocateDirect(int capacity)
//申请堆内内存
public static ByteBuffer allocate(int capacity)
//原始字节包装成ByteBuffer
public static ByteBuffer wrap(byte[] array, int offset, int length)
//原始字节包装成ByteBuffer
public static ByteBuffer wrap(byte[] array)
//创建共享此缓冲区内容的新字节缓冲区
public abstract ByteBuffer duplicate()
//分片,创建一个新的字节缓冲区
//新ByteBuffer的开始位置是此缓冲区的当前位置position
public abstract ByteBuffer slice()
//获取字节内容
public abstract byte get()
//从ByteBuffer偏移offset的位置,获取length长的字节数组,然后返回当前ByteBuffer对象
public ByteBuffer get(byte[] dst, int offset, int length)
//设置byte内存
public abstract ByteBuffer put(byte b);
//以offset为起始位置设置length长src的内容,并返回当前ByteBuffer对象
public ByteBuffer put(byte[] src, int offset, int length长)
//将没有读完的数据移到到缓冲区的初始位置,position设置为最后一没读字节数据的下个索引,limit重置为为capacity
//读->写模式,相当于flip的反向操作
public abstract ByteBuffer compact()
//是否是直接内存
public abstract boolean isDirect()
- ByteBuffer bf = ByteBuffer.allocate(10);`, создайте объект ByteBuffer размером 10
- ввод данных
ByteBuffer buf ByteBuffer.allocate(10);
buf.put("csc".getBytes());
- Вызовите flip, чтобы преобразовать буфер в режим чтения;
buf.flip();
- Прочитать содержимое буфера: get();
System.out.println((char) buf.get());
MappedByteBuffer с нулевым копированием
- Файл отображения общей памяти, соответствующий класс подоперации ByteBuffer, MappedByteBuffer реализован на основе mmap. Для основного принципа mmap с нулевым копированием вы можете увидеть:Статьи о платформе: принцип нулевого копирования Linux, который Xiaobai может понять за считанные секунды. MappedByteBuffer требует, чтобы FileChannel вызывал локальную функцию карты для сопоставления. Код C++ можно посмотреть нижеFileChannelImpl.c — метод Java_sun_nio_ch_FileChannelImpl_map0
- Используя MappedByteBuffer и сопоставление файлов, его чтение и запись могут уменьшить количество копий памяти.
FileChannel readChannel = FileChannel.open(Paths.get("./cscw.txt"), StandardOpenOption.READ);
MappedByteBuffer data = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, 1024 * 1024 * 40);
Механизм восстановления памяти вне кучи DirectByteBuffer
- Здесь мы смотрим на механизм прямого вызова памяти (java8); есть объект внутри Cleaner DirectByteBuffer, и введен в эксплуатацию объект класса восстановления внутренней памяти Deallocator.
class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer
{
//构造函数
DirectByteBuffer(int cap) {
.... //内存分配
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
...
}
private static class Deallocator implements Runnable{
...
public void run() {
if (address == 0) {
// Paranoia
return;
}
unsafe.freeMemory(address); //回收内存
address = 0;
Bits.unreserveMemory(size, capacity);
}
}
- Присмотритесь к Cleaner, унаследованному от PhantomReference, и в
public void clean()
Метод вызовет Deallocator для очистки операции.
public class Cleaner extends PhantomReference<Object> {
//如果DirectByteBuffer对象被回收,相应的Cleaner会被放入dummyQueue队列
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
//构造函数
public static Cleaner create(Object var0, Runnable var1) {
return var1 == null ? null : add(new Cleaner(var0, var1));
}
private Cleaner(Object var1, Runnable var2) {
super(var1, dummyQueue);
this.thunk = var2;
}
private final Runnable thunk;
public void clean() {
if (remove(this)) {
try {
this.thunk.run();
} catch (final Throwable var2) {
....
- Внутри ссылки есть поток демона, который зацикливается, чтобы получить ссылку, и оценивает, является ли он объектом Cleaner, и если да, то вызывает его чистый метод.
public abstract class Reference<T>
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg; tgn != null; g = tgn, tgn = tg.getParent());
Thread handler = new ReferenceHandler(tg, "Reference Handler");
...
handler.setDaemon(true);
handler.start();
...
}
...
//内部类调用 tryHandlePending
private static class ReferenceHandler extends Thread {
public void run() {
while (true) {
tryHandlePending(true);
}
}
...
static boolean tryHandlePending(boolean waitForNotify) {
Cleaner c;
.... //从链表获取对象被回收的引用
// 判断Reference是否Cleaner,如果是则调用其clean方法
if (c != null) {
c.clean(); //调用Cleaner的clean方法
return true;
}
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
ByteBuf нетти
- Принцип ByteBuf
- Bytebuf помогает операциям чтения и записи буфера с помощью двух указателей позиции, readIndex и writeIndex.
* +-------------------+------------------+------------------+
* | discardable bytes | readable bytes | writable bytes |
* | | (CONTENT) | |
* +-------------------+------------------+------------------+
* | | | |
* 0 <= readerIndex <= writerIndex <= capacity
- ByteBuf.API
//获取ByteBuf分配器
public abstract ByteBufAllocator alloc()
//丢弃可读字节
public abstract ByteBuf discardReadBytes()
//返回读指针
public abstract int readerIndex()
//设置读指针
public abstract ByteBuf readerIndex(int readerIndex);
//标志当前读指针位置,配合resetReaderIndex使用
public abstract ByteBuf markReaderIndex()
public abstract ByteBuf resetReaderIndex()
//返回可读字节数
public abstract int readableBytes()
//返回写指针
public abstract int writerIndex()
//设置写指针
public abstract ByteBuf writerIndex(int writerIndex);
//标志当前写指针位置,配合resetWriterIndex使用
public abstract ByteBuf markWriterIndex()
public abstract ByteBuf resetWriterIndex()
//返回可写字节数
public abstract int writableBytes()
public abstract ByteBuf clear();
//设置读写指针
public abstract ByteBuf setIndex(int readerIndex, int writerIndex)
//指针跳过length
public abstract ByteBuf skipBytes(int length)
//以当前位置切分ByteBuf todo
public abstract ByteBuf slice();
//切割起始位置为index,长度为length的ByteBuf todo
public abstract ByteBuf slice(int index, int length);
//Returns a copy of this buffer's readable bytes. //复制ByteBuf todo
public abstract ByteBuf copy()
//是否可读
public abstract boolean isReadable()
//是否可写
public abstract boolean isWritable()
//字节编码顺序
public abstract ByteOrder order()
//是否在直接内存申请的ByteBuf
public abstract boolean isDirect()
//转为jdk.NIO的ByteBuffer类
public abstract ByteBuffer nioBuffer()
- Пример использования
public static void main(String[] args) {
//分配大小为10的内存
ByteBuf buf = Unpooled.buffer(10);
//写入
buf.writeBytes("csc".getBytes());
//读取
byte[] b = new byte[3];
buf.readBytes(b);
System.out.println(new String(b));
System.out.println(buf.writerIndex());
System.out.println(buf.readerIndex());
}
----result----
csc
3
3
- Когда ByteBuf инициализируется, readIndex и WriterIndex равны 0, вызовите
writeXXX()方法
Запишите данные, WriterIndex увеличится (метод setXXX не действует); вызовreadXXX()方法
Чтение данных увеличит readIndex (метод getXXX не действует), но не превысит WriteIndex. - После чтения данных байтовые данные между 0-readIndex рассматриваются как отбрасываемые, и для освобождения этой части пространства вызывается discardReadBytes(), что аналогично компактному методу ByteBuffer.