Java集合类知识点总结

  • 格式:docx
  • 大小:24.08 KB
  • 文档页数:12

Java集合类知识点总结

Java集合类

Java集合类 (1)

1.Map (3)

1.1.HashMap (3)

1.1.1.底层实现 (3)

1.1.2.特点 (3)

1.1.3.源码分析 (4)

1.1.4.多线程可能出现的问题 (5)

1.2.ConcurrentHashMap (6)

1.2.1.底层实现 (6)

1.2.2.源码分析 (7)

1.3.HashTable (9)

1.3.1.HashTable是线程安全的,因为所有方法上都加了synchronized关键

字。9

1.3.2.HashTable的key和value都不可以为null。 (9)

1.3.3.扩容时,capacity=2*capacity+1 (9)

1.3.4.数组默认大小为11 (9)

1.3.5.查找下标时,没有使用hash&length-1,而是直接进行计算的 (9)

1.4.TreeMap (9)

1.4.1.底层实现为红黑树 (9)

1.4.

2.TreeMap是一个有序的key-value集合,基于红黑树实现。该映射根据

其键的自然顺序进行排序,或者根据创建时提供的Comparator进行排序 (10)

1.4.3.接口实现 (10) 1.4.4.Entry (11)

1.5.LinkedHashMap (11)

1.5.1.底层是数组+链表+红黑树+双向链表 (11)

1.5.2.维护链表顺序和访问顺序 (11)

1.5.3.LinkedHashMap 可以通过构造参数 accessOrder 来指定双向链表是否在

元素被访问后改变其在双向链表中的位置。 (11)

1.5.4.当accessOrder为true时,get方法和put方法都会调用recordAccess

方法使得最近使用的Entry移到双向链表的末尾;当accessOrder为默认值

false时,recordAccess方法什么也不会做。 (11)

1.5.5.LRU实现 (11)

2.Collection (11)

2.1.List (12)

2.1.1.ArrayList (12)

2.1.2.LinkedList (13)

2.1.3.CopyOnWriteArrayList (13)

2.2.Set (14)

2.2.1.HashSet (14)

2.2.2.TreeSet (14)

2.2.3.LinkedHashSet (15)

1.Map

1.1.HashMap

1.1.1.底层实现

1.7 数组+链表

数组的优点是访问速度快,但是插入删除操作慢

因为数组在内存中是连续存放的,因此存取很快

链表的优点是插入删除速度快,但是访问速度慢

由于链表不是连续存放的,因此插入删除时,只需要修改前后指针的指

向即可,不需要移动元素位置

1.8 数组+链表+红黑树

拉链法由头插法改为了尾插法

因为头插法在多线程的时候可能会导致死循环

链表长度大于8的时候转化为红黑树

红黑树的时间复杂度为logn,线性表查找的平均时间复杂度为n/2,因此在

链表长度为8时进行转化效率最高

红黑树的转化也是比较消耗性能的

链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成

链表

1.1.

2.特点

存取的时间复杂度为O(1)

1.1.3.源码分析

put()

1.判断key是否为null,如果为null,调用putlForNullKey,将key插入到

数组下标为0的位置

2.调用hash()方法计算key的hashcode,得到hash值

3.调用indexFor()方法进行取模运算,得到元素的下标位置

1.indexFor方法为:h&(length - 1)

2.使用与运算,计算速度更快,因为二进制运算比十进制运算效率更高

(十进制运算还需要将二进制转化为十进制)

3.length之所以要设定为2次幂,就是为了这个indexFor方法服务

4.可以让散列更加均匀,length-1的最后一位为1,因此进行与运算时,

可以散列到奇数和偶数的下标位置,如果对length直接取模,由于

length为2次幂,所以最后一位一定为0,所以与运算的结果一定是偶

数,这也就导致奇数下标的位置不能被散列到。

4.依次和该下标位置上的链表中的node节点比较key是否相等

e.hash == hash && ((k = e.key) == key || key.equals(k))

首先判断e.hash==hash是因为不同的key值也可能被散列到同一个位

置,因此首先判断hash值,如果不相等则两个key肯定不等

如果相等,再通过==和equals比较是否相等,之所以要先判断hash值是否相等,是因为equal()很耗性能,因此先判断hash值能够提高效率

重写了hashcode()方法就必须重写equals方法

5.如果相等,更新value值,如果不相等,使用头插法(1.7)/尾插法(1.8)将

entry(1.7)/Node(1.8)插入到链表中

get()

和put()方法类似,获取到桶的下标,再在链表上查找key值,再获取key 对应的value值

resize()

当hashmap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容

扩容时,令 capacity 为原来的两倍。

1.7时,需要new 一个新数组,并对旧数组上的所有元素进行indexFor()操

作确定下标地址,这一步很费时,1.8时只需判断hash值的左边新增的那一位是否为1,即可判断此节点是留在原地lo还是移动去高位hi,如果为1,则移动去高位,否则不变 1.7时,扩容的时候可能出现死循环,1.8没有这个问题

构造方法

在第一次put()的时候,数组才初始化

数组的长度为大于指定值的最小二次幂

数组默认大小为16

1.1.4.多线程可能出现的问题

1.扩容时可能出现死循环

2.put的时候可能被失效/覆盖

线程A,B同时调用addEntry方法,同时获取到了相同的头节点,然后A写入新的头结点之后,B也写入新的头结点,那B的写入操作就会覆盖A的写入操作造成A的写入操作丢失。

3.修改的时候可能被覆盖

线程A,B先后修改同一key值的value,会导致覆盖

4.put非null元素后get出来的却是null

扩容时调用的transfer方法,在获取数组的每个头节点的时候,在将e=头节点之后,都会将头节点置空,此时get可能导致获取到的值为0

1.2.ConcurrentHashMap

1.2.1.底层实现

1.7 segment数组+HashEntry数组(数组+链表)

chm由一个segment数组组成

segment

每个segment元素包含一个HashEntry数组,每个HashEntry包含一个链表

HashEntry大部分成员变量都为final

final k key

volatile V value

final int hash

final HashEntry next

1.8 数组+链表+红黑树 1.2.2.源码分析

put()

基本流程

1.7 通过两次hash确定

第一次Hash定位到Segment

通过segmentFor()函数进行,计算方式也和indexFor()相同

SegmentMask

ssize-1

SegmentShift

32-sshift

ssize

是大于ConcurrentLevel的最小二次幂

第二次Hash定位到元素所在的链表的头部

定位方法和HashMap中的indexFor()相同

通过segment.lock加锁

1.8通过两次hash确定

通过CAS+synchronized加锁

1.如果没有hash冲突就直接通过CAS插入

2.如果有hash冲突或者CAS操作失败,说明存在并发情况,使用

synchronized加锁

3.如果插入成功就调用addCount()方法统计size,并且检查是否需要

扩容

源码分析

1.ensureSegment

1.判断是否被其他线程初始化,这里使用了getObjectVolatile()方法

2.使用segment[0]的属性来初始化其他槽

3.使用while()循环,内部使用CAS操作,尝试初始化槽

2.segment.put() get()

get不需要加锁,因为HashEntry的value值设定为了volatile

如果get()到的是null值,则可能这个key,value对正在put的过程中,如果出现这种情况,那么就通过lock加锁来保证取出的value是完整的

resize()

构造函数

先根据ConcurrentLevel构造出Segment数组

Segment数组大小是不大于concurrentLevel的最大的2的指数

每个Segment中的HashEntry数组的大小都是大于指定大小的最小二次幂

每个hashEntry的大小为大于initialCapacity/concurrentLevel的最小二次幂

初始参数

initialCapacity(每个HashEntry的长度)

loadFactor:扩容因子

concurrencyLevel:并发度,指Segment数组的长度

remove

在定位到待删除元素的位置以后,程序就将待删除元素前面的那一些元素全部复制一遍,然后再一个一个重新接到链表上去。尾结点指向e的下一个结点。e后面的结点不需要复制,它们可以重用。

因为HashEntry中的next是final,所以只能先把待删除之前的元素复制了再删除

size

size操作就是遍历了两次Segment,每次记录Segment的modCount值,然后将两次的modCount进行比较,如果相同,则表示期间没有发生过写入操作,就将原先遍历的结果返回,如果不相同,就需要将所有的

Segment都锁住,然后一个一个遍历了,

1.3.HashTable