diff --git a/blogs/Java/口水话/集合源码.md b/blogs/Java/口水话/集合源码.md index d11754d..eed7897 100644 --- a/blogs/Java/口水话/集合源码.md +++ b/blogs/Java/口水话/集合源码.md @@ -16,6 +16,7 @@ 10. CopyOnWriteArrayList 11. ConcurrentHashMap 12. SparseArray +13. ArrayMap #### ArrayList @@ -124,7 +125,7 @@ ConcurrentHashMap 的 get 和 HashMap 基本无差。 SparseArray 是 Android 中一种特有的数据结构,用来替代 HashMap 的。它里面有两个数组,一个是 int[] 数组存放 key,一个是 Object[] value 数组。也就是它的 key 只能为 int;在 put 时,会根据传入的 key 进行二分查找找到合适的插入位置,如果当前位置有值或者是 DELETED 节点,就直接覆盖,否则就需要拷贝数组后移一位,空出一个位置让其插入。如果数组满了但是还有 DELETED 节点,就需要调用 gc 方法,gc 方法所做的就是把 DELETED 节点后面的数前移,也就是真正的把 DELETED 节点删掉然后在插入,否则就只能扩容了。 -调用 remove 时,并不会直接把 key 从 int[] 数组里面删掉,而是把当前 key 指向的 value 设置成 DELETED 节点,这样做是为了减少 int[] 数组的结构调整,结构调整就意味着数据拷贝。但是当我们调用 keyAt/valueAt 获取索引时,如果有 DELETED 节点就必须得调用 gc,不然获得的 index 肯定不对。 +调用 remove 时,并不会直接把 key 从 int[] 数组里面删掉,而是把当前 key 指向的 value 设置成 DELETED 节点,这样做是为了减少 int[] 数组的结构调整,结构调整就意味着数据拷贝。但是当我们调用 keyAt/valueAt 获取索引时,如果有 DELETED 节点就必须得调用 gc,不然获得的 index 肯定不对。延迟回收的好处适合频繁删除和插入来回执行的场景,性能很好。 get 方法就比较简单了,二分查找获取 key 对应的索引 index,返回 values[index] 即可。 @@ -132,3 +133,16 @@ get 方法就比较简单了,二分查找获取 key 对应的索引 index, 除了 SparseArray,Android 还提供了 SparseIntArray(int:int)、SparseBooleanArray(int:boolean)、SparseLongArray(int:long) 等,其实就是把对应的 value 换成基本数据类型。 +#### ArrayMap + +SparseArray 只能存 key 为 int 的键值对,如果需要存其他类型的 key,就可以使用 ArrayMap 了。ArrayMap 的底层和 SparseArray 类似,都是有两个数组,不过 ArrayMap 的一个数组存 hashCode 值,一个数组存 key-value 键值对,键值对数组的大小显然要是 hashCode 数组大小的两倍。为了减少频繁的创建和回收 Map 对象,ArrayMap 还采用了两个大小为 10 的缓存队列来分别保存大小为 4 和 8 的 ArrayMap 对象。为了节省内存,还有内存扩张和内存收缩策略。 + +ArrayMap 在 put/remove 时,和 SparseArray 基本一致,也是通过二分查找求数组索引,然后在执行相应的操作。不同的是 ArrayMap 的扩容机制和缩容机制。 + +在 put 需要扩容时,如果容量小于 4 就给 4,小于 8 就给 8,其次就是扩容 1.5 倍。之所以给 4 或 8 是因为可以利用缓存的 ArrayMap 对象;在 remove 时,如果数组长度大于 8 但是存储的数据不足数据大小的 1/3 时,就会缩容,mSize 小于等于 8,则设置新大小为 8,否则就设置为 mSize 的 1.5 倍,也就是说在内存使用量不足 1/3 时,内存数据收紧 50%。 + +这个缓存还是很有必要的,毕竟 ArrayMap 的使用量还是蛮大的,Bundle 的底层就是用 ArrayMap 来存数据的,可想而知了。但是可以思考一下 Bundle 为啥用 ArrayMap 而不用 SparseArray 呢? + +除了 put 方法,ArrayMap 和 SparseArray 都有一个 append 方法,它和 put 很相似,append 的差异在于该方法不会去做扩容操作,是一个轻量级的插入方法。在明确知道肯定会插入队尾的情况下使用 append 性更好,因为 put 一上来就做二分查找,时间复杂度 O(logn),而 append 时间复杂度为 O(1)。 + +ArraySet 也是 Android 特有的数据结构,用来替代 HashSet 的,和 ArrayMap 几乎一致,包含了缓存机制、扩容机制。 \ No newline at end of file