最新面试必备:ArrayMap源码解析,java开发面试题带答案

最后

按照上面的过程,4个月的时间刚刚好。当然Java的体系是很庞大的,还有很多更高级的技能需要掌握,但不要着急,这些完全可以放到以后工作中边用别学。

学习编程就是一个由混沌到有序的过程,所以你在学习过程中,如果一时碰到理解不了的知识点,大可不必沮丧,更不要气馁,这都是正常的不能再正常的事情了,不过是“人同此心,心同此理”的暂时而已。

道路是曲折的,前途是光明的!”

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

this(capacity, false);

}

//指定容量和identityHashCode

public ArrayMap(int capacity, boolean identityHashCode) {

mIdentityHashCode = identityHashCode;

//数量< 0,构建一个不可变的ArrayMap

if (capacity < 0) {

mHashes = EMPTY_IMMUTABLE_INTS;

mArray = EmptyArray.OBJECT;

//数量= 0,构建空的mHashes mArray

} else if (capacity == 0) {

mHashes = EmptyArray.INT;

mArray = EmptyArray.OBJECT;

} else {//数量>0,分配空间初始化数组

allocArrays(capacity);

}

mSize = 0;

}

//扩容

private void allocArrays(final int size) {

//数量< 0,构建一个不可变的ArrayMap

if (mHashes == EMPTY_IMMUTABLE_INTS) {

throw new UnsupportedOperationException(“ArrayMap is immutable”);

}//扩容数量是 8

if (size == (BASE_SIZE*2)) {

synchronized (ArrayMap.class) {

//查看之前是否有缓存的 容量为8的int[]数组和容量为16的object[]数组

//如果有,复用给mArray mHashes

if (mTwiceBaseCache != null) {

final Object[] array = mTwiceBaseCache;

mArray = array;

mTwiceBaseCache = (Object[])array[0];

mHashes = (int[])array[1];

array[0] = array[1] = null;

mTwiceBaseCacheSize–;

if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes

  • " now have " + mTwiceBaseCacheSize + " entries");

return;

}

}

} else if (size == BASE_SIZE) {//扩容数量是4

synchronized (ArrayMap.class) {

//查看之前是否有缓存的 容量为4的int[]数组和容量为8的object[]数组

//如果有,复用给mArray mHashes

if (mBaseCache != null) {

final Object[] array = mBaseCache;

mArray = array;

mBaseCache = (Object[])array[0];

mHashes = (int[])array[1];

array[0] = array[1] = null;

mBaseCacheSize–;

if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes

  • " now have " + mBaseCacheSize + " entries");

return;

}

}

}

//构建mHashes和mArray,mArray是mHashes的两倍。因为它既要存key还要存value。

mHashes = new int[size];

mArray = new Object[size<<1];

}

//利用另一个map构建ArrayMap

public ArrayMap(ArrayMap<K, V> map) {

this();

if (map != null) {

putAll(map);

}

}

//批量put方法:

public void putAll(ArrayMap<? extends K, ? extends V> array) {

final int N = array.mSize;

//确保空间足够存放

ensureCapacity(mSize + N);

//如果当前是空集合,

if (mSize == 0) {

if (N > 0) {//则直接复制覆盖数组内容即可。

System.arraycopy(array.mHashes, 0, mHashes, 0, N);

System.arraycopy(array.mArray, 0, mArray, 0, N<<1);

mSize = N;

}

} else {//否则需要一个一个执行插入put操作

for (int i=0; i<N; i++) {

put(array.keyAt(i), array.valueAt(i));

}

}

}

//确保空间足够存放 minimumCapacity 个数据

public void ensureCapacity(int minimumCapacity) {

//如果不够扩容

if (mHashes.length < minimumCapacity) {

//暂存当前的hash array。后面复制需要

final int[] ohashes = mHashes;

final Object[] oarray = mArray;

//扩容空间(开头讲过这个函数)

allocArrays(minimumCapacity);

if (mSize > 0) {//如果原集合不为空,复制原数据到新数组中

System.arraycopy(ohashes, 0, mHashes, 0, mSize);

System.arraycopy(oarray, 0, mArray, 0, mSize<<1);

}

//释放回收临时暂存数组空间

freeArrays(ohashes, oarray, mSize);

}

}

//释放回收临时暂存数组空间

private static void freeArrays(final int[] hashes, final Object[] array, final int size) {

//如果容量是8, 则将hashes 和array 缓存起来,以便下次使用

if (hashes.length == (BASE_SIZE*2)) {

synchronized (ArrayMap.class) {

if (mTwiceBaseCacheSize < CACHE_SIZE) {

//0存,前一个缓存的cache

array[0] = mTwiceBaseCache;

//1 存 int[]数组

array[1] = hashes;

//2+ 元素置空 以便GC

for (int i=(size<<1)-1; i>=2; i–) {

array[i] = null;

}

//更新缓存引用为array

mTwiceBaseCache = array;

//增加缓存过的Array的数量

mTwiceBaseCacheSize++;

if (DEBUG) Log.d(TAG, "Storing 2x cache " + array

  • " now have " + mTwiceBaseCacheSize + " entries");

}

}//相同逻辑,只不过缓存的是int[] 容量为4的数组

} else if (hashes.length == BASE_SIZE) {

synchronized (ArrayMap.class) {

if (mBaseCacheSize < CACHE_SIZE) {

array[0] = mBaseCache;

array[1] = hashes;

for (int i=(size<<1)-1; i>=2; i–) {

array[i] = null;

}

mBaseCache = array;

mBaseCacheSize++;

if (DEBUG) Log.d(TAG, "Storing 1x cache " + array

  • " now have " + mBaseCacheSize + " entries");

}

}

}

}

小结:

* 扩容时,会查看之前是否有缓存的 int[]数组和object[]数组

* 如果有,复用给mArray mHashes

4 增 、改


4.1 单个增改 put(K key, V value)

//如果key存在,则返回oldValue

public V put(K key, V value) {

//key的hash值

final int hash;

//下标

int index;

// 如果key为null,则hash值为0.

if (key == null) {

hash = 0;

//寻找null的下标

index = indexOfNull();

} else {

//根据mIdentityHashCode 取到 hash值

hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();

//根据hash值和key 找到合适的index

index = indexOf(key, hash);

}

//如果index>=0,说明是替换(改)操作

if (index >= 0) {

//只需要更新value 不需要更新key。因为key已经存在

index = (index<<1) + 1;

//返回旧值

final V old = (V)mArray[index];

mArray[index] = value;

return old;

}

//index<0,说明是插入操作。 对其取反,得到应该插入的下标

index = ~index;

//如果需要扩容

if (mSize >= mHashes.length) {

//如果容量大于8,则扩容一半。

//否则容量大于4,则扩容到8.

//否则扩容到4

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

//临时数组

final int[] ohashes = mHashes;

final Object[] oarray = mArray;

//分配空间完成扩容

allocArrays(n);

//复制临时数组中的数组进新数组

if (mHashes.length > 0) {

if (DEBUG) Log.d(TAG, “put: copy 0-” + mSize + " to 0");

System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);

System.arraycopy(oarray, 0, mArray, 0, oarray.length);

}

//释放临时数组空间

freeArrays(ohashes, oarray, mSize);

}

//如果index在中间,则需要移动数组,腾出中间的位置

if (index < mSize) {

if (DEBUG) Log.d(TAG, "put: move " + index + “-” + (mSize-index)

  • " to " + (index+1));

System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);

System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);

}

//hash数组,就按照下标存哈希值

mHashes[index] = hash;

//array数组,根据下标,乘以2存key,乘以2+1 存value

mArray[index<<1] = key;

mArray[(index<<1)+1] = value;

mSize++;//修改size

return null;

}

//返回key为null的 下标index

int indexOfNull() {

//N为当前集合size

final int N = mSize;

//如果当前集合是空的,返回~0

if (N == 0) {//

return ~0;

}

//根据hash值=0,通过二分查找,查找到目标index

int index = ContainerHelpers.binarySearch(mHashes, N, 0);

//如果index《0,则hash值=0之前没有存储过数据

if (index < 0) {

return index;

}

//如果index>=0,说明该hash值,之前存储过数据,找到对应的key,比对key是否等于null。相等的话,返回index。说明要替换。

//关于array中对应数据的位置,是index2 = key ,index2+1 = value.

if (null == mArray[index<<1]) {

return index;

}

//以下两个for循环是在出现hash冲突的情况下,找到正确的index的过程:

//从index+1,遍历到数组末尾,找到hash值相等,且key相等的位置,返回

int end;

for (end = index + 1; end < N && mHashes[end] == 0; end++) {

if (null == mArray[end << 1]) return end;

}

//从index-1,遍历到数组头,找到hash值相等,且key相等的位置,返回

for (int i = index - 1; i >= 0 && mHashes[i] == 0; i–) {

if (null == mArray[i << 1]) return i;

}

// key没有找到,返回一个负数。代表应该插入的位置

return ~end;

}

//根据key和key的hash值,返回index

int indexOf(Object key, int hash) {

//N为当前集合size

final int N = mSize;

//如果当前集合是空的,返回~0

if (N == 0) {

return ~0;

}

//根据hash值,通过二分查找,查找到目标index

int index = ContainerHelpers.binarySearch(mHashes, N, hash);

//如果index《0,说明该hash值之前没有存储过数据

if (index < 0) {

return index;

}

//如果index>=0,说明该hash值,之前存储过数据,找到对应的key,比对key是否相等。相等的话,返回index。说明要替换。

if (key.equals(mArray[index<<1])) {

return index;

}

//以下两个for循环是在出现hash冲突的情况下,找到正确的index的过程:

//从index+1,遍历到数组末尾,找到hash值相等,且key相等的位置,返回

int end;

for (end = index + 1; end < N && mHashes[end] == hash; end++) {

if (key.equals(mArray[end << 1])) return end;

}

//从index-1,遍历到数组头,找到hash值相等,且key相等的位置,返回

for (int i = index - 1; i >= 0 && mHashes[i] == hash; i–) {

if (key.equals(mArray[i << 1])) return i;

}

// key没有找到,返回一个负数。代表应该插入的位置

return ~end;

}

4.2 批量增 putAll(Map

//批量增,接受更为广泛的Map参数

public void putAll(Map<? extends K, ? extends V> map) {

//确保空间容量足够

ensureCapacity(mSize + map.size());

for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {

//分别调用单个增方法 add

put(entry.getKey(), entry.getValue());

}

}

小结:

* 增的流程:1 先根据key得到hash值,2 根据hash值得到index 3 根据index正负,得知是插入还是替换 4 如果是替换直接替换值即可 5 如果是插入,则先判断是否需要扩容,并进行扩容 6 挪动数组位置,插入元素(类似ArrayList)

  • 插入允许key为null,value为null。

  • 每次插入时,根据key的哈希值,利用二分查找,去寻找key在int[] mHashes数组中的下标位置。

  • 如果出现了hash冲突,则从需要从目标点向两头遍历,找到正确的index。

  • 如果index>=0,说明之前已经存在该key,需要替换(改)。

  • 如果index<0,说明没有找到。(也是二分法特性)对index去反,可以得到这个index应该插入的位置。

  • 根据keyhash值在mHashs中的index,如何得到key、valuemArray中的下标位置呢?key的位置是index*2value的位置是index*2+1,也就是说mArray是利用连续的两位空间去存放key、value

  • 根据hash值的index计算,key、valueindex也利用了位运算。index<<1 和 (index<<1)+1

5 删


5.1 单个删

//如果对应key有元素存在,返回value。否则返回null

public V remove(Object key) {

//根据key,找到下标

final int index = indexOfKey(key);

if (index >= 0) {

//如果index>=0,说明key有对应的元素存在,则去根据下标删除

return removeAt(index);

}

//否则返回null

return null;

}

//根据下标删除元素

最后

关于面试刷题也是有方法可言的,建议最好是按照专题来进行,然后由基础到高级,由浅入深来,效果会更好。当然,这些内容我也全部整理在一份pdf文档内,分成了以下几大专题:

  • Java基础部分

  • 算法与编程

  • 数据库部分

  • 流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)

这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。

作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

,这些内容我也全部整理在一份pdf文档内,分成了以下几大专题:

  • Java基础部分

[外链图片转存中…(img-yMqv7Jni-1715686449008)]

  • 算法与编程

[外链图片转存中…(img-IH1Ndhhi-1715686449009)]

  • 数据库部分

[外链图片转存中…(img-DQm2dZCs-1715686449010)]

  • 流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)

[外链图片转存中…(img-AQAXL2BZ-1715686449010)]

这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。

作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 11
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值