startFromBuffer

news/2024/9/18 3:43:47/文章来源:https://www.cnblogs.com/yuqiu2004/p/18408781

1.Buffer类的底层实现

以IntBuffer和HeapIntBuffer为例讲解Buffer的实现机制

  1. 核心内容

    public abstract class Buffer {// 这四个变量的关系: mark <= position <= limit <= capacity// 这些变量就是Buffer操作的核心了,之后我们学习的过程中可以看源码是如何操作这些变量的private int mark = -1;private int position = 0;private int limit;private int capacity;// 直接缓冲区实现子类的数据内存地址long address;...
    }
    

    成员解读:

    • mark: 用于position回溯的一个标记,如果标记小于0的时候reset会抛出异常
    • position: 用于记录写入或读取的位置的索引
    • limit: 缓冲区的一个读取限制 初始和capacity相同 但是如果执行'压缩'之后limit可能变化
    • capacity: 缓冲的容量 或 底层数组的长度 真实的尺寸

    Buffer类的子类,包括我们认识到的所有基本类型(除了boolean类型之外):

    • IntBuffer - int类型的缓冲区。
    • ShortBuffer - short类型的缓冲区。
    • LongBuffer - long类型的缓冲区。
    • FloatBuffer - float类型的缓冲区。
    • DoubleBuffer - double类型的缓冲区。
    • ByteBuffer - byte类型的缓冲区。
    • CharBuffer - char类型的缓冲区。

    (注意我们之前在JavaSE中学习过的StringBuffer虽然也是这种命名方式,但是不属于Buffer体系)

  2. 使用关键

    • 读取和写入时会造成position的改动,比如写入数据后,如果需要从头读,需要flip进行重置

    • 复制、切分时底层使用的同一个数组,也就是操作实际上是对同一个数组进行操作

2.常用api

  • public abstract IntBuffer put(int i); - 在当前position位置插入数据,由具体子类实现

  • public abstract IntBuffer put(int index, int i); - 在指定位置存放数据,也是由具体子类实现

  • public final IntBuffer put(int[] src); - 直接存放所有数组中的内容(数组长度不能超出缓冲区大小)

  • public IntBuffer put(int[] src, int offset, int length); - 直接存放数组中的内容,同上,但是可以指定存放一段范围

  • public IntBuffer put(IntBuffer src); - 直接存放另一个缓冲区中的内容

  • public abstract int get(); - 直接获取当前position位置的数据,由子类实现

  • public abstract int get(int index); - 获取指定位置的数据,也是子类实现

  • public IntBuffer get(int[] dst); - 将数据读取到给定的数组中

  • public IntBuffer get(int[] dst, int offset, int length); - 同上,加了个范围

  • public int[] array(); - 直接返回底层存储的数组

  • public final Buffer mark() - 标记当前位置

  • public final Buffer reset() - 让当前的position位置跳转到mark当时标记的位置

  • public abstract IntBuffer compact() - 压缩缓冲区,由具体实现类实现

    • 源码

    • public IntBuffer compact() {int pos = position();   //获取当前位置int lim = limit();    //获取当前最大position位置assert (pos <= lim);   //断言表达式,position必须小于最大位置,肯定的int rem = (pos <= lim ? lim - pos : 0);  //计算pos距离最大位置的长度System.arraycopy(hb, ix(pos), hb, ix(0), rem);   //直接将hb数组当前position位置的数据拷贝到头部去,然后长度改成刚刚计算出来的空间position(rem);   //直接将position移动到rem位置limit(capacity());   //pos最大位置修改为最大容量discardMark();   //mark变回-1return this;
      }
      
  • public IntBuffer duplicate() - 复制缓冲区,会直接创建一个新的数据相同的缓冲区

  • public abstract IntBuffer slice() - 划分缓冲区,会将原本的容量大小的缓冲区划分为更小的出来进行操作

  • public final Buffer rewind() - 重绕缓冲区,其实就是把position归零,然后mark变回-1

  • public final Buffer clear() - 将缓冲区清空,所有的变量变回最初的状态

  • public boolean equals(Object ob) - 比较剩余的内容

    • public boolean equals(Object ob) {if (this == ob)   //要是两个缓冲区是同一个对象,肯定一样return true;if (!(ob instanceof IntBuffer))  //类型不是IntBuffer那也不用比了return false;IntBuffer that = (IntBuffer)ob;   //转换为IntBufferint thisPos = this.position();  //获取当前缓冲区的相关信息int thisLim = this.limit();int thatPos = that.position();  //获取另一个缓冲区的相关信息int thatLim = that.limit();int thisRem = thisLim - thisPos; int thatRem = thatLim - thatPos;if (thisRem < 0 || thisRem != thatRem)   //如果剩余容量小于0或是两个缓冲区的剩余容量不一样,也不行return false;//注意比较的是剩余的内容for (int i = thisLim - 1, j = thatLim - 1; i >= thisPos; i--, j--)  //从最后一个开始倒着往回比剩余的区域if (!equals(this.get(i), that.get(j)))return false;   //只要发现不一样的就不用继续了,直接falsereturn true;   //上面的比较都没问题,那么就true
      }private static boolean equals(int x, int y) {return x == y;
      }
      
  • public int compareTo(IntBuffer that) - 比较剩余的内容

    • public int compareTo(IntBuffer that) {int thisPos = this.position();    //获取并计算两个缓冲区的pos和remainint thisRem = this.limit() - thisPos;int thatPos = that.position();int thatRem = that.limit() - thatPos;int length = Math.min(thisRem, thatRem);   //选取一个剩余空间最小的出来if (length < 0)   //如果最小的小于0,那就返回-1return -1;int n = thisPos + Math.min(thisRem, thatRem);  //计算n的值当前的pos加上剩余的最小空间for (int i = thisPos, j = thatPos; i < n; i++, j++) {  //从两个缓冲区的当前位置开始,一直到n结束int cmp = compare(this.get(i), that.get(j));  //比较if (cmp != 0)return cmp;   //只要出现不相同的,那么就返回比较出来的值}return thisRem - thatRem; //如果没比出来个所以然,那么就比长度
      }private static int compare(int x, int y) {return Integer.compare(x, y);
      }
      

3.只读缓冲

  • 创建只读缓冲

    • public abstract IntBuffer asReadOnlyBuffer(); - 基于当前缓冲区生成一个只读的缓冲区。

    • public IntBuffer asReadOnlyBuffer() {return new HeapIntBufferR(hb,    //注意这里并不是直接创建了HeapIntBuffer,而是HeapIntBufferR,并且直接复制的hb数组this.markValue(),this.position(),this.limit(),this.capacity(),offset);
      }
      
    • protected HeapIntBufferR(int[] buf,int mark, int pos, int lim, int cap,int off)
      {super(buf, mark, pos, lim, cap, off);this.isReadOnly = true;
      }
      
  • 写操作全部抛出异常 禁止进行写操作

4.ByteBuffer与CharBuffer

4.1 ByteBuffer

实现

public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> {final byte[] hb;                  // Non-null only for heap buffersfinal int offset;boolean isReadOnly;                 // Valid only for heap buffers....

4.2 CharBuffer

​ 由于使用了char[] 存储数据,此缓冲可以使用字符串相关的api如append、charAt等

5.直接缓冲区

  • 堆缓冲区的数据实际上保存在堆内存中,我们可以创建一个直接缓冲区,申请堆外内存进行数据保存,采用操作系统本地的io,相比堆缓冲区会快一些

  • 在每次调用基础操作系统的一个本机IO之前或者之后,虚拟机都会避免将缓冲区的内容复制到中间缓冲区(或者从中间缓冲区复制内容),缓冲区的内容驻留在物理内存内,会少一次复制过程,如果需要循环使用缓冲区,用直接缓冲区可以很大地提高性能。虽然直接缓冲区使JVM可以进行高效的I/O操作,但它使用的内存是操作系统分配的,绕过了JVM堆栈,建立和销毁比堆栈上的缓冲区要更大的开销。

  • 创建:allocateDirect(capacity)

  • 知识补充:

    1. Unsafe是位于sun.misc包下的类,提供一些更底层的,访问系统内存资源,和管理系统内存资源的方法,但是因为会访问系统的内存资源 变成和C语言一样的指针,指针的使用是有风险的,所以Unsafe也是有类似的风险,所以在使用的时候需要注意,过度或者不正确的使用可能导致程序出错。
      但是,Unsafe类也使得Java增强了底层操作系统资源的能力。
      同时,Unsafe提供的功能的实现是依赖于本地方法(Native Method)的,本地方法就是Java中使用其他语言写的方法,本地方法用native修饰,java只声明方法,具体实现由本地方法实现。
    2. 虚引用PhantomReference,虚引用是最弱的一种java对象引用方式,其他的引用方式至少还能get到对象,而虚引用的句柄是获取不到对象的,正如它的名字一样:形同虚设。虚引用的作用就是在对象被GC回收时能得到通知。如何通知呢?就是在对象被回收后,把它的弱引用对象(PhantomReference)存入QUEUE对列中,这样我们查看队列就可以得知某个对象被GC回收了
  • 构造

    DirectByteBuffer(int cap) {                   // package-privatesuper(-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 {//通过Unsafe申请内存空间,并得到内存地址base = unsafe.allocateMemory(size);} catch (OutOfMemoryError x) {//申请失败就取消一开始的保留内存Bits.unreserveMemory(size, cap);throw x;}//批量将申请到的这一段内存每个字节都设定为0unsafe.setMemory(base, size, (byte) 0);if (pa && (base % ps != 0)) {// Round up to page boundaryaddress = base + ps - (base & (ps - 1));} else {//将address变量(在Buffer中定义)设定为base的地址address = base;}//创建一个针对于此缓冲区的Cleaner,由于是堆外内存,所以现在由它来进行内存清理cleaner = Cleaner.create(this, new Deallocator(base, size, cap));att = null;
    }
    
  • 清理-守护线程定时检查(或者也可以手动调用cleaner方法显示的回收)

    public class Cleaner extends PhantomReference<Object>{ //继承自鬼引用,也就是说此对象会存放一个没有任何引用的对象//引用队列,PhantomReference构造方法需要private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();//执行清理的具体流程private final Runnable thunk;static private Cleaner first = null;  //Cleaner双向链表,每创建一个Cleaner对象都会添加一个结点private Cleanernext = null,prev = null;private static synchronized Cleaner add(Cleaner cl) {   //添加操作会让新来的变成新的头结点if (first != null) {cl.next = first;first.prev = cl;}first = cl;return cl;}//可以看到创建鬼引用的对象就是传进的缓冲区对象private Cleaner(Object referent, Runnable thunk) {super(referent, dummyQueue);//清理流程实际上是外面的Deallocatorthis.thunk = thunk;}//通过此方法创建一个新的Cleanerpublic static Cleaner create(Object ob, Runnable thunk) {if (thunk == null)return null;return add(new Cleaner(ob, thunk));   //调用add方法将Cleaner添加到队列}//清理操作public void clean() {if (!remove(this))return;    //进行清理操作时会从双向队列中移除当前Cleaner,false说明已经移除过了,直接returntry {thunk.run();   //这里就是直接执行具体清理流程} catch (final Throwable x) {...}}
    

    具体的清理操作

    private static class Deallocator implements Runnable {private static Unsafe unsafe = Unsafe.getUnsafe();private long address;   //内存地址private long size;    //大小private int capacity;   //申请的容量private Deallocator(long address, long size, int capacity) {assert (address != 0);this.address = address;this.size = size;this.capacity = capacity;}public void run() {   //具体的清理操作if (address == 0) {// Paranoiareturn;}unsafe.freeMemory(address);   //这里是直接调用了Unsafe进行内存释放操作address = 0;   //内存地址改为0,NULLBits.unreserveMemory(size, capacity);   //取消一开始的保留内存}
    }
    
  • 清理时机:和堆缓冲区一样,当直接缓冲区没有任何强引用时,就有机会被GC正常回收掉并自动释放申请的内存。

    Reference Handler线程是在一开始就启动了

    static boolean tryHandlePending(boolean waitForNotify) {Reference<Object> r;Cleaner c;try {synchronized (lock) {   //加锁办事//当Cleaner引用的DirectByteBuffer对象即将被回收时,pending会变成此Cleaner对象//这里判断到pending不为null时就需要处理一下对象销毁了if (pending != null) {r = pending;// 'instanceof' 有时会导致内存溢出,所以将r从链表中移除之前就进行类型判断// 如果是Cleaner类型就给到cc = r instanceof Cleaner ? (Cleaner) r : null;// 将pending更新为链表下一个待回收元素pending = r.discovered;r.discovered = null;   //r不再引用下一个节点} else {//否则就进入等待if (waitForNotify) {lock.wait();}return waitForNotify;}}} catch (OutOfMemoryError x) {Thread.yield();return true;} catch (InterruptedException x) {return true;}// 如果元素是Cleaner类型,c在上面就会被赋值,这里就会执行其clean方法(破案了)if (c != null) {c.clean();return true;}ReferenceQueue<? super Object> q = r.queue;if (q != ReferenceQueue.NULL) q.enqueue(r);  //这个是引用队列,实际上就是我们之前在JVM篇中讲解的入队机制return true;
    }
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/795611.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

词表示与语言模型、大模型背后的范式

这张幻灯片讨论了大模型背后的范式,特别是预训练和微调的基本范式可以追溯到迁移学习。以下是主要内容:迁移学习 :预训练和微调的基本范式可以追溯到迁移学习。 人类可以应用之前学到的知识更快地处理新问题,我们希望机器也具有类似的能力。传统机器学习 vs. 迁移学习 :左…

词表示与语言模型

不积跬步,无以至千里;不积小流,无以成江海。

文件对比工具--BeyondCompare

💖简介 Beyond Compare 是一款功能强大的文件和文件夹比较工具,由Scooter Software开发。它可以帮助用户轻松地比较文件和文件夹的差异,并且可以合并变化、同步文件以及备份重要数据 💻环境 windows 📖版本 Beyond Compare v5.0.2 🔗地址 https://www.scootersoftwar…

基于sqli-labs Less-1的sql注入原理详细讲解

SQLi Labs 是一个专为学习和测试 SQL 注入漏洞而设计的实验室平台。它旨在帮助安全研究人员、开发者以及网络安全爱好者深入理解和实践各种 SQL 注入攻击。SQLi Labs 提供了一系列精心设计的实验室环境和挑战,模拟真实的 SQL 注入漏洞,并提供相应的解决方案。 关于sqli-labs靶…

UE4(5)逆向学习笔记(三)——UEDumper源码学习

目录0.前言1.准备2.开始阅读2.1 设置版本和Offset2.2 获取GName2.3 使用GName2.4 获取GUObjectArray2.5 使用GUObjectArray2.6 寻找dump主流程2.6.1 ObjectsManager::copyGObjectPtrs2.6.2 ObjectsManager::copyUBigObjects2.6.3 EngineCore::cacheFNames2.6.4 Engin…

k8s dashboard token 生成/获取

创建示例用户在本指南中,我们将了解如何使用 Kubernetes 的服务帐户机制创建新用户、授予该用户管理员权限并使用与该用户绑定的承载令牌登录仪表板。 对于以下每个和的代码片段ServiceAccount,ClusterRoleBinding您都应该将它们复制到新的清单文件(如)中,dashboard-admin…

个人项目

这个作业属于哪个课程 计科22级12班这个作业要求在哪里 作业要求 这个作业的目标 完成个人项目,实现论文查重的功能,了解软件开发流程Github链接 一.PSP表格PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)Planning 计划 15 20Estimate 估计这个任务…

互联网医疗|基于音视频SDK和即时通讯IM技术实现线上问诊功能

不论是科普医学知识,还是医疗行业者的专业培训、手术示教,采用远程直播培训的方式能够打破空间限制,同时也保留了课堂的互动性,大大节省了讲师和学员的成本。帮助开发者全面监测音视频服务,包括问题定位诊断、洞察质量与体验、业务经营分析、实时监控告警等,低门槛、高效…

Spring boot 2.x validator

1、使用方式 2、常用注解

DBeaver 连接 mysql 报错:Public Key Retrieval is not allowed

前言 DBeaver 连接 mysql 报错:Public Key Retrieval is not allowed 遇到 "Public Key Retrieval is not allowed" 错误时,通常意味着你正在使用的身份验证方法需要加密连接,但是没有正确地配置客户端或服务器来支持这种加密。 解决 第一种 可以在连接字符串中添…

Rest-assured框架详解

Rest-assured框架官网 官网url: https://rest-assured.io/ 一、接口测试介绍 - 什么是接口测试 本质上基于某种协议,发送请求给服务器,服务器返回响应数据,对响应数据进行分析,判断和我们的预期是否一致,从而验证功能是否正确。 - 为什么做接口测试 更早发现问题,降低研…