ArrayMap源码解析

news2024/11/17 19:34:12

一、数据结构

ArrayMap是一个key-value的数据结构,它比HashMap有更高的内存效率

它映射到两个数组结构:一个整数数组mHashes,用来保存key的hashcode;一个对象数组mArray,保存key-value

 它不适用于大量数据的存储,通常会比HashMap慢,因为查找需要二分查找。

二、源码解析

public final class ArrayMap<K, V> implements Map<K, V> 

由上面可知,ArrayMap是Map的子类

1、成员变量

//默认的最小容量
private static final int BASE_SIZE = 4;
//缓存的容量
private static final int CACHE_SIZE = 10;
//是否使用 System.identityHashCode(key)来获取hashcode
private final boolean mIdentityHashCode;
//存放key的hashcode的数组
int[] mHashes;
//按顺序存放key、value的数组
Object[] mArray;
//mHashes的大小
int mSize;
private MapCollections<K, V> mCollections;

注意:mSize表示的是数组mHashes的大小,而mArray的大小为2*mSize。mHashes中升序存放key的hash值,mArray中顺序存储了key和value。若key的hash在mHashes的位置索引为index,那么key在mArray中的位置索引keyIndex=index<<1=index*2,value在mArray中的位置valueIndex=(index<<1)+1=index*2+1。

2、构造函数

public ArrayMap() {
    this(0, false);
}

public ArrayMap(int capacity) {
    this(capacity, false);
}

public ArrayMap(int capacity, boolean identityHashCode) {
    mIdentityHashCode = identityHashCode;
    if (capacity < 0) {
        mHashes = EMPTY_IMMUTABLE_INTS;
        mArray = EmptyArray.OBJECT;
    } else if (capacity == 0) {
        mHashes = EmptyArray.INT;
        mArray = EmptyArray.OBJECT;
    } else {
        allocArrays(capacity);
    }
    mSize = 0;
}

ArrayMap提供了三种构造方法,其中第三个构造方法,从外部传入mHash数组的初始容量capacity。

  • 如果capacity<0,那么mHashes就被赋值一个不可变的int数组,mArray被赋值一个空对象数组。
  • 如果capacity=0,那么mHashes就被赋值一个空int数组,mArray被赋值一个空对象数组。
  • 如果capacity>0,那么就会调用allocArray(capacity)方法,申请指定容量大小的数组。

3、元素查询

int indexOf(Object key, int hash) {
    final int N = mSize;
    // ====== TAG 01 ======
    int index = binarySearchHashes(mHashes, N, hash);
    if (index < 0) {
        return index;
    }
    if (key.equals(mArray[index<<1])) {//走到这里,说明已经通过Hashcode找到对的key,这里再判断下查找到索引位置的key与要找的key是否相同,如果相同,则证明找到了,直接返回。如果不相同,则说明hash冲突了。
        return index;
    }
    // ====== TAG 02 ======
    for (end = index + 1; end < N && mHashes[end] == hash; end++) {
        if (key.equals(mArray[end << 1])) return end;
    }
    for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
        if (key.equals(mArray[i << 1])) return i;
    }
    return ~end;
}

ArrayMap查找元素主要是通过binarySearchHashes二分查找方式来查找index;若HashCode和Key均匹配则为要查找的index;若只有HashCode相同但对象不同(即HashCode冲突),则从当前对应的Index向后和向前分别遍历查找。(ArrayMap处理Hash冲突的解决办法就是,采用开放地址法,即如果hash值对应的index已经存在值了,则需要从index的两边去寻找与当前key匹配时对应的索引。如果没有找到,则表示当前key原本不存在,需要新插入进来,而插入的位置,就是有着相同hash值且连续的子数组序列的末尾位置

4、元素添加

ArrayMap的元素添加有两个方法:put()和append()

(1)put()

public V put(K key, V value) {
    final int osize = mSize;
    final int hash;
    int index;
    if (key == null) {
        hash = 0;
        index = indexOfNull();
    } else {
        hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
        // ====== TAG 01 ====== 
        index = indexOf(key, hash);
    }
    if (index >= 0) {
        // ====== TAG 02 ====== 
        index = (index<<1) + 1;
        final V old = (V)mArray[index];
        mArray[index] = value;
        return old;
    }
    // ====== TAG 03 ====== 
    index = ~index;
    if (osize >= mHashes.length) {
        final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1)) : (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
        final int[] ohashes = mHashes;
        final Object[] oarray = mArray;
        // ====== TAG 04 ======
        allocArrays(n);
        if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
            throw new ConcurrentModificationException();
        }
        // ====== TAG 05 ======
        if (mHashes.length > 0) {
            System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
            System.arraycopy(oarray, 0, mArray, 0, oarray.length);
        }
        freeArrays(ohashes, oarray, osize);
    }
    // ====== TAG 06 ======
    if (index < osize) {
        System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);
        System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
    }

    mHashes[index] = hash;
    mArray[index<<1] = key;
    mArray[(index<<1)+1] = value;
    mSize++;
    return null;
}

TAG 01:主要用于二分查找,在mHashes数组中查找HashCode相等的key

TAG 02:如果index>0说明已经找到,即有对应HashCode相等的Key,然后更新其Value值。

TAG 03:在index<0,说明没有找到,即没有对应的HashCode相等的Key,此时需要插入新数据。

这里需要注意下,会先判断是否需要扩容,如果mHashes数组已满,那么就需要调用allocArrays扩容,这里扩容的大小:

final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1)) : (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);

可以看出,如果扩容前的容量大小是否>=BASE_SIZE*2(即8),那么扩容为之前容量的1.5倍。如果扩容前容量小于8再判断之前容量是否>=4,如果>=4,那么扩容为8;如果<4那么扩容为4.

TAG 04:当需要扩容时,可采用allocArrays()方式分配更大的内存空间;

TAG 05:然后通过System.arraycopy将老的数组数据拷贝到新的数组中,再通过freeArrays()释放老的数组内存。

TAG 06:当需要插入的元素不在末尾时,拷贝完数据之后需要将index后移一位。

(2)append()

public void append(K key, V value) {
    int index = mSize;
    final int hash = key == null ? 0 : (mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
    if (index >= mHashes.length) {
        throw new IllegalStateException("Array is full");
    }
    if (index > 0 && mHashes[index-1] > hash) {
        RuntimeException e = new RuntimeException("here");
        e.fillInStackTrace();
        // ====== TAG ======
        put(key, value);
        return;
    }
    mSize = index+1;
    mHashes[index] = hash;
    index <<= 1;
    mArray[index] = key;
    mArray[index+1] = value;
}

append方法会优先判断需要插入到的数组中的位置,如果要插入到的位置在mHashes数组的中间,则调用put方法,如果插入的位置在mHashes数组的末尾,直接在末尾处添加。

5、元素删除

public V remove(Object key) {
    final int index = indexOfKey(key);
    if (index >= 0) {
        return removeAt(index);
    }
    return null;
}
public V removeAt(int index) {
    final Object old = mArray[(index << 1) + 1];
    final int osize = mSize;
    final int nsize;
    // ====== TAG 01 ======
    if (osize <= 1) {
        final int[] ohashes = mHashes;
        final Object[] oarray = mArray;
        mHashes = EmptyArray.INT;
        mArray = EmptyArray.OBJECT;
        freeArrays(ohashes, oarray, osize);
        nsize = 0;
    } else {
        nsize = osize - 1;
        // ====== TAG 02 ======
        if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
            final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);
            final int[] ohashes = mHashes;
            final Object[] oarray = mArray;
            allocArrays(n);

            if (index > 0) {
                System.arraycopy(ohashes, 0, mHashes, 0, index);
                System.arraycopy(oarray, 0, mArray, 0, index << 1);
            }
            if (index < nsize) {
                System.arraycopy(ohashes, index + 1, mHashes, index, nsize - index);
                System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1, (nsize - index) << 1);
            }
        } else {
            // ====== TAG 03 ======
            if (index < nsize) {
                System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);
                System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1, (nsize - index) << 1);
            }
            mArray[nsize << 1] = null;
            mArray[(nsize << 1) + 1] = null;
        }
    }
    mSize = nsize;
    return (V)old;
}

TAG 01:当数组只有一个要删除的元素时,直接将mHashes和mArray置空并通过freeArrays释放内存

TAG02:当数组内存大小大于8并且元素的数量少于1/3空间大小时,通过allocArrays进行减少分配内存

TAG03:当删除其中一个元素时,需要将该元素之后的所有元素向前移动一位

ArrayMap为了避免频繁的创建和销毁,提供了mBaseCache和mTwiceBaseCash两个数组缓冲池,同时提供了allocArrays和freeArrays内存分配和释放的方法,两者相互对应,都通过缓冲池分配和释放内存。

private void allocArrays(final int size) {
    if (size == (BASE_SIZE*2)) {
        synchronized (ArrayMap.class) {
            if (mTwiceBaseCache != null) {
                final Object[] array = mTwiceBaseCache;
                mArray = array;
                mTwiceBaseCache = (Object[])array[0];
                mHashes = (int[])array[1];
                array[0] = array[1] = null;
                mTwiceBaseCacheSize--;
                return;
            }
        }
    } else if (size == BASE_SIZE) {
        ...
    }
    mHashes = new int[size];
    mArray = new Object[size<<1];
}

当需要分配内存大小为BASE_SIZE或BASE_SIZE*2时,会先查看对应的缓存池中取出mArray和mHashes;其方式是先将缓存池指向上一条缓存地址,将缓存池的第 array[1] 个元素赋值为 mHashes ,再把 mArray 的第 array[0] 和第 array[1] 个位置的数据置为 null

private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
    if (hashes.length == (BASE_SIZE*2)) {
        synchronized (ArrayMap.class) {
            if (mTwiceBaseCacheSize < CACHE_SIZE) {
                array[0] = mTwiceBaseCache;
                array[1] = hashes;
                for (int i=(size<<1)-1; i>=2; i--) {
                    array[i] = null;
                }
                mTwiceBaseCache = array;
                mTwiceBaseCacheSize++;
            }
        }
    } else if (hashes.length == BASE_SIZE) {
        ...
    }
}

当内存需要释放时,释放大小为 BASE_SIZEBASE_SIZE * 2 时,会将数组加入到缓冲池中;其方式是先将原缓冲池和哈希数组分别指向 array 前两位,之后的元素全部置空,最后将缓存池的头部指向最新的数组位置;

三、HashMap和ArrayMap对比

1、查找效率

  • HashMap因为其根据hashcode的值直接算出index,所以其查找效率是随着数组长度增大而增加的
  • ArrayMap使用的是二分查找法,所以每当数组长度增加一倍时,就需要多进行一次判断,效率下降。

2.扩容数量

  • HashMap初始值16个长度,每次扩容时,直接申请双倍的数组空间
  • ArrayMap每次扩容时候,如果size长度大于8时申请size*1.5个长度,大于4小于8申请8个,小于4时申请4个。
  • 这样比较,ArrayMap是申请了更少的内存空间,但是扩容的频率比较高。因此,如果数据量较大的时候,还是使用HashMap比较合适,因为其扩容次数要比ArrayMap少很多。
  • ArrayMap没有实现Serializable,不便在Android bundle进行传输
  • ArrayMap扩容比HashMap高效,因为HashMap扩容需要重新计算hash值和移动元素。而ArrayMap只需要拷贝。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于在使用Word写论文时如何引用其它论文的方法

目录 步骤1步骤2 步骤1 全选需要进行标注的论文标题&#xff0c;然后按照下面的图片演示步骤进行操作。 步骤2 注意&#xff1a;如果没看到&#xff0c;拖动一下窗口的滚条&#xff0c;应该在下面一点。然后就是在论文引用的位置&#xff0c;依次进行插入就行了。

DevOps系列文章 之 Java使用jgit管理git仓库

最近设计基于gitops新的CICD方案,需要通过java读写git仓库&#xff0c;这里简单记录下。 在jgit中&#xff0c;存在最核心的三个组件&#xff1a;Git类&#xff0c;Repository类。Git类中包含了push commit之类的常见git操作&#xff0c;而Repository则实现了仓库的初始化和基…

查看自己所有的工程提交次数

git branch -r --contains [ hash index] 远程仓库是否包含当前的提交 我写的注释什么的很少出现 日期的英文, 所以很好统计 #!/bin/bashCOUNTS0 DATE$(date | awk {print $2}) DIRS$(ls) CHECK_URLgit10.0.128.128:sw/ INDEX0 TODAY$(date | awk {printf "%s %s %s"…

Nginx代理nginx.conf配置——反向代理(对WebSocket支持)

一、需求说明 基于Nginx代理nginx.conf配置——反向代理&#xff0c;如果要添加websocket支持&#xff0c;需要进行如下配置 二、配置内容 在http中添加一下配置&#xff0c;添加对websocket支持 http {# 配置其它内容map $http_upgrade $connection_upgrade {default upgra…

国赛试题解析1:SW3模拟办事处与防火墙之间运行OSPFv2协议

试题内容:(4)SW3模拟办事处产品和营销接口配置为loopback,模拟接口up。SW3模拟办事处与FW2之间运行OSPFv2协议,进程2,区域2,SW3模拟办事处发布loopback2、产品和营销。 SW3模拟 办事处 loopback2 10.1.3.2/32 2001:10:1:3::2/128 vlan110(产品) 10.1.110.1/24

【教学类-36-07】对称蝴蝶(midjounery-niji)(涂色、裁剪、游戏(飞舞的蝴蝶))

作品展示 一、利用midjounery获得简笔画样式的“蝴蝶” Animal Mask , simple stroke, cartoon, black and white outline, uncolored NIJI 5 二、图片切割 用以下代码把上面文件夹里所有的2048*2048的单张图片切割程2*2 # 参考网址&#xff1a;https://blog.csdn.net/weixi…

2023年计算机科学与信息技术国际会议(ECCSIT 2023) | Ei Scopus双检索

会议简介 Brief Introduction 2023年计算机科学与信息技术国际会议(ECCSIT 2023) 会议时间&#xff1a;2023年12月15日-17日 召开地点&#xff1a;中国北海 大会官网&#xff1a;www.eccsit.org 2023年计算机科学与信息技术国际会议(ECCSIT 2023)由西南交通大学、西南财经大学、…

使用easyexcel出现的错误

说明&#xff1a;easyexcel&#xff08;官网&#xff1a;https://easyexcel.opensource.alibaba.com/&#xff09;是阿里巴巴提供的&#xff0c;用于项目中读取/导出excel文件的工具&#xff0c;本文介绍使用easyexcel常见的两种错误。 错误一&#xff1a;java.lang.NoSuchMet…

代谢组学分析 PCA PLS-DA OPLS-DA 在R语言中的实现

主成分分析&#xff08;Principal Component Analysis&#xff0c;PCA&#xff09;是一种无监督降维方法&#xff0c;能够有效对高维数据进行处理。但PCA对相关性较小的变量不敏感&#xff0c;而PLS-DA&#xff08;Partial Least Squares-Discriminant Analysis&#xff0c;偏最…

el-table 添加合计,合计某一列

效果图&#xff1a; 1. 使用elementui 官网上的方法 如果是只要是数值&#xff0c;就要合并&#xff0c;就只设置show-summary 即可。 2. html&#xff1a; <!--cell-style 改变某一列行的背景色 --><!-- tree-props 配置树形子表row-click: 单击事件highlight-cu…

node修改版本、npm修改版本、yarn无法加载文件、node_modules\sharp: Command failed解决方法

1、node修改版本 步骤1&#xff1a;从node官网下载node压缩包或者exe文件 如果是下载的是exe文件就直接找到原来的node.exe文件替换掉就可以了&#xff0c;环境变量配置不变 如果是下载的node压缩包&#xff0c;需要解压后&#xff0c;修改本地的环境变量配置&#xff08;查看步…

微服务实例构建成 docker 镜像实例

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

抖音seo源码部署搭建--代码分享

一、 开发环境搭建 抖音SEO源码部署环境搭建可以分为以下几个步骤&#xff1a; 安装必要的软件和工具&#xff1a;需要安装Node.js、NPM、Git等软件和工具&#xff0c;具体安装方法可以参考官方文档。 下载源码&#xff1a;从GitHub或其他源码托管平台下载抖音SEO源码。 安装…

SpringBoot 配置文件:什么时配置文件?配置文件能干什么?

文章目录 &#x1f387;前言1.配置文件的格式2. properties配置文件说明2.1 properties基本语法2.2 读取配置文件 3. yml 配置文件说明3.1 yml 基本语法 4.properties与yml 对比 &#x1f387;前言 学习一个东西&#xff0c;我们先要知道它有什么用处。整个项目中所有重要的数…

Vscode 绿色系清新主题

炎炎夏日&#xff0c;上班上的心浮气躁&#xff0c;敲代码的时候&#xff0c;只觉昏昏沉沉&#xff0c;浑浑噩噩... 给vscode换一个一个清新美好的绿色主题&#xff0c;充满活力和希望吧。 朋友们&#xff0c;收藏起来&#xff0c;每个季节换一个主题&#xff0c;打工快乐&am…

[工业互联-14]:机器人操作系统与ROS

目录 第1章 简介 第2章 历史 第3章 特点 &#xff08;1&#xff09;点对点设计 &#xff08;2&#xff09;不依赖编程语言 &#xff08;3&#xff09;精简与集成 &#xff08;4&#xff09;便于测试 &#xff08;5&#xff09;开源 &#xff08;6&#xff09;强大的库及…

LVS-DR负载群集的优势和部署实例

目录 一、DR模式数据包流向分析 二、DR模式的特点 三、DR模式中需要解决的问题 四、LVS-DR部署实例 1.配置NFS共享存储器 2.配置节点web服务&#xff08;两台的配置相同&#xff09; 3.配置LVS负载调度器 一、DR模式数据包流向分析 1.Client 客户端发送请求到 Director …

OpenCV库实现了一个简单的图像放缩工具

这里是详细的代码解析: #include <opencv2/opencv.hpp> // 引入OpenCV主要库。 #include <opencv2/highgui/highgui.hpp> // 引入高级GUI模块,它包含用于显示图像和获取用户输入的函数。 #include <opencv2/imgproc/imgproc.hpp> // 引入图像处理模块,它…

mysql数据类型char和varchar的区别

&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&…

如何将Windows的文件存储到铁威马NAS里?

总是能听到小伙伴问&#xff0c;有没有办法可以省去登入TOS的操作就可以直接存取铁威马NAS上的文件及资料呢&#xff1f;很简单&#xff0c;其实只要将NAS的共享文件夹映射到Windows的网络驱动器&#xff0c;就饿可以让我们节省登入TOS的操作直接存取TNAS的文件&#xff0c;编辑…