CurrentHashMap巧妙利用位运算获取数组指定下标元素

先来了解一下数组对象在堆中的存储形式【数组长度,数组元素类型信息等】+ 【存放元素对象的空间】

Ma

基础信息实例数据内存填充
Mark Word,ClassPointer,数组长度第一个元素第二个元素固定的填充内容

所以我们想要获取某个下标的元素首先要获取这个元素的起始位置和每个元素长度

例如我们要取第二个元素的,首先知道第二个元素的起始位置,从其实位置开始按照每个元素的长度取指定的位数,就相当于获取了第二个元素的全部信息。

再来了解一下Java中一个特殊的类sun.misc.Unsafe

sun.misc.Unsafe是JDK提供的用于很底层编程的类,位于sun.misc包中。在有些底层编程的场景,Java语言层面办不到的事情,我们可能需要使用JNI,借助C语言去实现。但是使用JNI并不是唯一的选择,使用JNI会将代码绑定到特定的平台,使用Unsafe类可以保留Java语言代码对平台的独立性,又实现底层编程。

Unsafe类并没有public的构造函数,只提供了一个静态工厂方法,这个静态方法还只提供给JDK标准库自身的类调用,在我们自己随便建的一个普通的类调用这个静态工厂方法还会抛异常。

这个静态工厂方法的代码如下:

@CallerSensitive
public static Unsafe getUnsafe() {
    Class<?> caller = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(caller.getClassLoader()))
        throw new SecurityException("Unsafe");
    return theUnsafe;
}

可以使用反射的方式

import sun.misc.Unsafe;
import java.lang.reflect.Field;
 
public class TestUnsafe {
 
    private static Unsafe unsafe;
 
    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);
        } catch (Exception e) {
            //
        }
    }
 
}

 Unsafe功能列表

  • allocateMemory/freeMemory,分配、释放堆外内存DirectMemory(和c/cpp中的malloc一样)
  • CAS操作
  • copyMemory
  • defineClass(without security checks)
  • get/put address 使用堆外内存地址进行数据的读写操作
  • get/put volatile 使用堆外内存地址进行数据的读写操作 - volatile版本
  • loadFence/storeFence/fullFence 禁止指令重排序
  • park/unpark 阻塞/解除阻塞线程

Unsafe的数组操作

unsafe中,有两个关于数组的方法:

public native int arrayBaseOffset(Class<?> arrayClass);
public native int arrayIndexScale(Class<?> arrayClass);

arrayBaseOffset 数组第一个元素相对于数组对象起始地址的偏移量

arrayIndexScale就是指数组中每个元素所占用的空间大小,比如int[] scale就是4,long[] scale就是8,object[] scale就是4(指针大小)

// Unsafe mechanics
    private static final sun.misc.Unsafe U;
    private static final long SIZECTL;
    private static final long TRANSFERINDEX;
    private static final long BASECOUNT;
    private static final long CELLSBUSY;
    private static final long CELLVALUE;
    private static final long ABASE;
    private static final int ASHIFT;

    static {
        try {
            U = sun.misc.Unsafe.getUnsafe();
            Class<?> k = ConcurrentHashMap.class;
            SIZECTL = U.objectFieldOffset
                (k.getDeclaredField("sizeCtl"));
            TRANSFERINDEX = U.objectFieldOffset
                (k.getDeclaredField("transferIndex"));
            BASECOUNT = U.objectFieldOffset
                (k.getDeclaredField("baseCount"));
            CELLSBUSY = U.objectFieldOffset
                (k.getDeclaredField("cellsBusy"));
            Class<?> ck = CounterCell.class;
            CELLVALUE = U.objectFieldOffset
                (ck.getDeclaredField("value"));
            Class<?> ak = Node[].class;
            ABASE = U.arrayBaseOffset(ak);
            int scale = U.arrayIndexScale(ak);
            if ((scale & (scale - 1)) != 0)
                throw new Error("data type scale not a power of two");
            ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
        } catch (Exception e) {
            throw new Error(e);
        }
    }

这是CurrentHashMap中的源码我们需要注意的地方有:

Class<?> ak = Node[].class; 
ABASE = U.arrayBaseOffset(ak);
ABASE:数组第一个元素相对于数组对象起始地址的偏移量
int scale = U.arrayIndexScale(ak);数组中每个元素所占用的空间大小,返回的是所占空间的字节数
scale为2的n次方,2的n次方二进制表示就是某个位置为1其余位置为0
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
Integer.numberOfLeadingZeros(scale) 计算int型参数二进制表示数值最高位前面有几个0
由于int一共有32位,出去位1的一位剩下31位,31 - Integer.numberOfLeadingZeros(scale)就表示scale二进制表示最低位有几个连续的0
                                                                        32
                                                                        32位
000000000000000000000000000(27位)100000(5位)
Integer.numberOfLeadingZeros()31 - Integer.numberOfLeadingZeros()

    @Test
    public void printObjectArrayScale() throws NoSuchFieldException, IllegalAccessException {
        Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafeInstance.setAccessible(true);
        Unsafe U = (Unsafe) theUnsafeInstance.get(Unsafe.class);
        Class<?> objectArr = Object[].class;
        Class<?> intArr = int[].class;
        Class<?> longArr = long[].class;
        Class<?> byteArr = byte[].class;
        int objectScale = U.arrayIndexScale(objectArr);
        int intScale = U.arrayIndexScale(intArr);
        int longScale = U.arrayIndexScale(longArr);
        int byteScale = U.arrayIndexScale(byteArr);
        System.out.println("Object[]的sacle=" + objectScale);
        System.out.println("int[]的sacle=" + intScale);
        System.out.println("long[]的sacle=" + longScale);
        System.out.println("byte[]的sacle=" + byteScale);
}

可以通过上面的方法测试,引用类型的数组的scale是4,因为虽然不同对象占用内存大小不一,但是对象数组每个元素是指向对应对象的引用,即内存地址

常规的想法是通过 scale * index 来计算某个元素相对于起始位置的偏移量

    @Test
    public void findArrayByBaseAndScale() throws NoSuchFieldException, IllegalAccessException {
        Class<Unsafe> clazz = Unsafe.class;
        Field unsafeField = clazz.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe)unsafeField.get(null);

        int[] temp = {1, 2, 3 , 4};
        int base = unsafe.arrayBaseOffset(int[].class);
        int scale = unsafe.arrayIndexScale(int[].class);

        System.out.println(unsafe.getInt(temp , base + 0 * scale));
        System.out.println(unsafe.getInt(temp , base + 1 * scale));
        System.out.println(unsafe.getInt(temp , base + 2 * scale));
        System.out.println(unsafe.getInt(temp , base + 3 * scale));
}

 可以看到顺利取到了数组元素

这里看看CurrentHashMap是怎么计算元素的偏移量的

    @SuppressWarnings("unchecked")
    static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }

这里测试一下它的这种方法

    @Test
    public void findArrayByBaseAndScale1() throws NoSuchFieldException, IllegalAccessException {
        Class<Unsafe> clazz = Unsafe.class;
        Field unsafeField = clazz.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe)unsafeField.get(null);

        int[] temp = {1, 2, 3 , 4};
        int base = unsafe.arrayBaseOffset(int[].class);
        int scale = unsafe.arrayIndexScale(int[].class);

        int ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);

        System.out.println("第一个元素:" + unsafe.getInt(temp , base + (0 << ASHIFT) ));
        System.out.println("第二个元素:" + unsafe.getInt(temp , base + (1 << ASHIFT) ));
        System.out.println("第三个元素:" + unsafe.getInt(temp , base + (2 << ASHIFT) ));
        System.out.println("第四个元素:" + unsafe.getInt(temp , base + (3 << ASHIFT) ));

    }

 可以看到也顺利取到了数组的元素

熟悉位运算的很容易理解,其实这就是利用了一个位运算的技巧,通过向左移来实现扩大为2的n次方倍,而scale正好是2的n次方,所以可以这样计算

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java 有什么必看的书?

Java必看经典书有这两本&#xff1a; 1、Java核心技术速学版&#xff08;第3版&#xff09; 经典Java开发基础书CoreJava速学版本&#xff01;Java入门优选书籍&#xff0c;更新至Java17&#xff0c;内容皆是精华&#xff0c;让Java学习更简单&#xff0c;让Java知识应用更快速…

fasttext工具介绍

fastText是由Facebook Research团队于2016年开源的一个词向量计算和文本分类工具。尽管在学术上并未带来巨大创新&#xff0c;但其在实际应用中的表现却非常出色&#xff0c;特别是在文本分类任务中&#xff0c;fastText往往能以浅层网络结构取得与深度网络相媲美的精度&#x…

STM32CubeMX实现4X5矩阵按键(HAL库实现)

为了实现计算器键盘&#xff0c;需要使用4X5矩阵按键&#xff0c;因此&#xff0c;我在4X4矩阵键盘上重新设计了一个4X5矩阵按键。原理图如下&#xff1a; 原理描述&#xff1a; 4X5矩阵按键&#xff0c;可以设置4个引脚为输出&#xff0c;5个引脚为输入模式&#xff0c;4个引…

MPS---MPQ86960芯片layout设计总结

MPQ86960 是一款内置功率 MOSFET 和栅极驱动的单片半桥。它可以在宽输入电压 (VIN) 范围内实现高达 50A 的连续输出电流 (IOUT)&#xff0c;通过集成MOSFET 和驱动可优化死区时间 (DT) 并降低寄生电感&#xff0c;从而实现高效率。 MPQ86960 兼容三态输出控制器&#xff0c;另…

Ubantu22.04 通过FlatPak安装微信

Ubuntu22.04 下使用Flatpak稳定安装微信&#xff01; 国际惯例&#xff0c;废话不多说&#xff0c;先上效果图。为啥使用Flatpak,因为Wechat官方只在FlatPak发布了最新的版本。之前使用了Wine以及Dock安装Wechat,效果都不是很理想&#xff0c;bug很多。所以使用了FlatPak。 Fl…

GRPC使用之ProtoBuf

1. 入门指导 1. 基本定义 Protocol Buffers提供一种跨语言的结构化数据的序列化能力&#xff0c;类似于JSON&#xff0c;不过更小、更快&#xff0c;除此以外它还能用用接口定义(IDL interface define language)&#xff0c;通protoc编译Protocol Buffer定义文件&#xff0c;…

【Spring Cloud】微服务的简单搭建

文章目录 &#x1f343;前言&#x1f384;开发环境安装&#x1f333;服务拆分的原则&#x1f6a9;单一职责原则&#x1f6a9;服务自治&#x1f6a9;单向依赖 &#x1f340;搭建案例介绍&#x1f334;数据准备&#x1f38b;工程搭建&#x1f6a9;构建父子工程&#x1f388;创建父…

关闭vue3中脑瘫的ESLine

在创建vue3的时候脑子一抽选了ESLine,然后这傻卵子ESLine老是给我报错 博主用的idea开发前端 ,纯粹是用不惯vscode 关闭idea中的ESLine,这个只是取消红色波浪线, 界面中的显示 第二步,在vue.config.js中添加 lintOnSave: false 到这里就ok了,其他的我试过了一点用没有

Google Java Style Guide深度解读:打造优雅的代码艺术

在软件工程的世界里&#xff0c;代码不仅仅是实现功能的工具&#xff0c;它也是团队之间沟通的桥梁&#xff0c;是软件质量和可维护性的直接反映。Google Java Style Guide作为一套广受认可的编码规范&#xff0c;不仅定义了代码的书写规则&#xff0c;更深刻地影响着Java开发者…

绿色金融相关数据合集(2007-2024年 具体看数据类型)

数据类型&#xff1a; 1.绿色债券数据&#xff1a;2014-2023 2.绿色信贷相关数据&#xff1a;2007-2022 3.全国各省及地级市绿色金融指数&#xff1a;1990-2022 4.碳排放权交易明细数据&#xff1a;2013-2024 5.绿色金融试点DID数据&#xff1a;2010-2023 数据来源&#…

python操作SQLite3数据库进行增删改查

python操作SQLite3数据库进行增删改查 1、创建SQLite3数据库 可以通过Navicat图形化软件来创建: 2、创建表 利用Navicat图形化软件来创建: 存储在 SQLite 数据库中的每个值(或是由数据库引擎所操作的值)都有一个以下的存储类型: NULL. 值是空值。 INTEGER. 值是有符…

Linux—网络设置

目录 一、ifconfig——查看网络配置 1、查看网络接口信息 1.1、查看所有网络接口 1.2、查看具体的网络接口 2、修改网络配置 3、添加网络接口 4、禁用/激活网卡 二、hostname——查看主机名称 1、查看主机名称 2、临时修改主机名称 3、永久修改主机名称 4、查看本…

【python】pyqt5大学生成绩信息管理系统-图形界面(源码+报告)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

基于支持向量机、孤立森林和LSTM自编码器的机械状态异常检测(MATLAB R2021B)

异常检测通常是根据已有的观测数据建立正常行为模型&#xff0c;从而将不同机制下产生的远离正常行为的数据划分为异常类&#xff0c;进而实现对异常状态的检测。常用的异常检测方法主要有&#xff1a;统计方法、信息度量方法、谱映射方法、聚类方法、近邻方法和分类方法等。 …

飞书 API 2-4:如何使用 API 将数据写入数据表

一、引入 上一篇创建好数据表之后&#xff0c;接下来就是写入数据和对数据的处理。 本文主要探讨数据的插入、更新和删除操作。所有的操作都是基于上一篇&#xff08;飞书 API 2-4&#xff09;创建的数据表进行操作。上面最终的数据表只有 2 个字段&#xff1a;序号和邮箱。序…

巴图自动化PN转Modbus RTU协议转换网关模块快速配置

工业领域中常用的通讯协议有&#xff1a;Profinet协议&#xff0c;Modbus协议&#xff0c;ModbusTCP协议&#xff0c;Profibus协议&#xff0c;Profibus DP协议&#xff0c;EtherCAT协议&#xff0c;EtherNET协议&#xff0c;CAN&#xff0c;CanOpen等&#xff0c;它们在自动化…

kubeadm快速部署k8s集群

文章目录 Kubernetes简介1、k8s集群环境2、linux实验环境初始化【所有节点】3、安装docker容器引擎【所有节点】4、安装cri-dockerd【所有节点】5、安装 kubeadm、kubelet、kubectl【所有节点】6、部署 k8s master 节点【master节点】7、加入k8s Node 节点【node节点】8、部署容…

【链表】【双指针】1、合并两个有序链表+2、分隔链表+3、删除链表的倒数第N个结点+4、链表的中间结点+5、合并两个链表

3道中等2道简单 数组和字符串打算告一段落&#xff0c;正好最近做的几乎都是双指针&#xff0c;所以今天做链表&#xff01; 1、合并两个有序链表&#xff08;难度&#xff1a;简单&#xff09; 该题对应力扣网址 AC代码 思路简单 /*** Definition for singly-linked list.…

昇思25天学习打卡营第12天|简单的深度学习ResNet50图像分类 - 构建ResNet50网络

ResNet主要解决深度卷积网络在深度加深时候的“退化”问题。在一般的卷积神经网络中&#xff0c;增大网络深度后带来的第一个问题就是梯度消失、爆炸&#xff0c;这个问题Szegedy提出BN层后被顺利解决。BN层能对各层的输出做归一化&#xff0c;这样梯度在反向层层传递后仍能保持…

P1392 取数

传送门&#xff1a;取数 如若你看完题解后&#xff0c;仍有问题&#xff0c;欢迎评论 首先说一下 我首先想到的思路 &#xff08; 20%通过率 &#xff09;&#xff1a;通过dfs , 将所有的情况放入priority_queue中&#xff08;greater<int>&#xff09;&#xff0c;维持…