当前位置: 首页>后端>正文

数据结构总结

ArrayMap实现原理:

int[] mHashes:存储的是key的hashCode,存取数据时首先根据二分查找法查找key的hashcode值在数组中的索引位置i
object[] mArray:根据key的hashcode值在mHashes中的索引查找到mArray中的key, value,key, value与索引位置的对应关系是2i与2i+1;
ArrayMap解决hash冲突不同于HashMap的链地址法,它使用的是开放寻址法。

1.查找效率HashMap因为其根据hashcode的值直接算出index,所以其查找效率是随着数组长度增大而增加的。ArrayMap使用的是二分法查找,所以当数组长度每增加一倍时,就需要多进行一次判断,效率下降
2.扩容数量HashMap初始值16个长度,每次扩容的时候,直接申请双倍的数组空间。ArrayMap每次扩容的时候,如果size长度大于8时申请size*1.5个长度,大于4小于8时申请8个,小于4时申请4个。这样比较ArrayMap其实是申请了更少的内存空间,但是扩容的频率会更高。因此,如果数据量比较大的时候,还是使用HashMap更合适,因为其扩容的次数要比ArrayMap少很多。
3.扩容效率HashMap每次扩容的时候重新计算每个数组成员的位置,然后放到新的位置。ArrayMap则是直接使用System.arraycopy,所以效率上肯定是ArrayMap更占优势。
4.内存消耗上因为ArrayMap采用了一种独特的方式,实现了静态变量全局缓存释放的数组,可以很方便下次ArrayMap的使用。能够重复的利用因为数组扩容而释放下来的数组空间,方便下一个ArrayMap的使用。而HashMap没有这种设计。 由于ArrayMap只缓存了长度是4和8的数组,所以如果频繁的使用到Map,而且数据量都比较小的时候,ArrayMap无疑是最节省内存的。
5.总结综上所述,数据量比较小,并且需要频繁的使用Map存储数据的时候,推荐使用ArrayMap。 而数据量比较大的时候,则推荐使用HashMap。

ConcurrentHashMap:
在 JDK 1.8 中,添加元素时首先会判断容器是否为空,如果为空则使用 volatile 加 CAS 来初始化。如果容器不为空则根据存储的元素计算该位置是否为空,如果为空则利用 CAS 设置该节点;如果不为空则使用 synchronize 加锁,遍历桶中的数据,替换或新增节点到桶中,最后再判断是否需要转为红黑树,这样就能保证并发访问时的线程安全了。
ConcurrentHashMap设置了一个属性值sizeCtl,多线程之间,以volatile的方式读取sizeCtl属性,来判断ConcurrentHashMap当前所处的状态。
通过cas设置sizeCtl属性,告知其他线程ConcurrentHashMap的状态变更。
0:数组未初始化, 且数组的初始容量为16
-1:表示正在初始化,
-N:不是-1,表示有N-1个线程正在进行扩容。
正数:如果数组未初始化,那么其记录的是数组的初始容量;如果数组已经初始化,那么其记录的是数组的扩容阈值(数组的初始容量*0.75)
而实际的初始化是在put()方法中加载table数组。

SparseArray实现原理:适合int值为key的小数据量存储。存储方法由于SparseArray存储的结构比HashMap简单,不需要维护链表等。另外不像HashMap使用稀疏数组存储所以更节省内存,HashMap 为了避免过多的哈希冲突,引入了负载因子,打个比方,负载因子使用默认值 0.75,这意味着容量达到了 75%,就会开始扩容,也就是必然有 25% 的空间是不存储数据而被浪费的。而 SparseArray 可以把数组利用到最后一个空间。hashMap先添加数据后扩容,如果扩容之后没有数据加入就会造成一次无效的扩容,造成内存空间的浪费,所以存储上比HashMap好。

int[] mKeys:保存key的数组mKeys和保存value的数组mValues索引是一一对应的
Object[] mValues:存储值value的数组

  • int作为键,减少自动装箱操作;
  • value使用纯粹的数组,结构相比hashmap简单,更加节约内存
  • 二分查找法查找int 数组mKeys中的key的索引位置进而定位到mValues数组中的value值,小数据量下更高效
  • 延时回收,remove只是修改值引用为DELETE状态,不删数据,不会频繁移动数组,在gc()函数后真正删除
  • 当mGarbage标志位为true,且mSize大于mKeys.length的时候会进行gc操作,remove操作时会修改mGarbge标识位。(gc后重新计算数组索引位置i,在位置i中插入数组mKeys和mValues中)
  • 既然底层使用了数组,数组的缺点是什么?—— 删除数据时需要搬移元素。SparseArray 对数组的删除不做数据搬移,引入 DELETE 标记,以此达到在删除时做到 O(1) 的时间复杂度。

LruCahce原理:LruCache 是最近最少使用算法的一种实现,将访问的元素根据访问的顺序 进行排序,主要用于内存缓存

初始化的时候, 会限制缓存占据内存空间的总容量 maxSize;底层维护的是 LinkedHashMap, 使用 LruCache 最好要重写 sizeOf 方法, 用于计算每个被缓存的对象, 在内存中存储时, 占用多少空间;

在 put 操作时, 首先计算新的缓存对象, 占多少空间, 再根据 key, 移除老的对象,占用内存大小 = 之前占用的内存大小 + 新对象的大小 - 老对象的大小;
put 操作最后总会根据 maxSize, 在拿到 LinkedHashMap.EntrySet 中链表的头节点, 循环判断, 只要当前缓存对象占据的总内存超出 maxSize, 就移除一个头节点, 一直到符合要求;

lruCache 和 LinkedHashMap 的关系:LinkedHashMap 中维护一个双向链表, 并维护 head 和 tail 指针
,lruCache 使用了 LinkedHashMap accessOrder 为 true 的属性, 只要访问了某个 key,包括 get 和 put, 就把当前这个 entry 放在链表的尾节点, 所以链表的头节点, 是最老访问的节点, 尾节点是最新访问的节点,所以, lruCache 就很巧妙的利用了这个特点, 完成了 Least Recently Used 的需求;

注意:LruCache 是强引用,LruCache 本身并没有释放内存存,它只是把 LinkedHashMap 中的数据移除,如果数据还在其他地方引用,还是无法释放内存,可以手动释放;

LruCache和DiskLruCache底层均为LinkedHashMap 是一个由包含KV的结点形成的双向链表,DiskLru相比于Lru来说 更注重于对文件的读写 和文件操作的保护。


https://www.xamrdz.com/backend/37y1923366.html

相关文章: