-
Collection 包结构与 Collections 的区别
- Collections 是提供了对集合进行操作的强大方法的工具类 ,它包含有各种有关集合操作的静态多态方法。此类不能实例化
- Collection用于存放单个对象,map用于存放键值对,Collection中分为两种不用类型的接口List(支持重复对象)和Set,List常用的实现类ArrayList,LinkedList,Vector和Stack,Set中实现类有HashSet和TreeSet。
-
ArrayList的实现(数组)
- 创建:调用空构造器会创建一个大小为10的Object数组,或者传入数组大小capacity
- 添加:首先基于已有的元素数量加 1,生成一个minCapacity 变量,然后比较minCapacity 变量和当前 Object 数 组的大小(当前允许装载的容量),如果minCapacity 大,那现将当前的Object数组赋值给一个数组对象,然后产生一个新的数组容量值。新的数组容量值=当前的数组容量值*1.5+1,如果新的容量值比minCapacity 小,那就以minCapacity 作为新的容量值。 之后调用 Arrays.copyOf 来生成新的数组对象。继承 ArrayList 并覆盖 ensureCapacity 可以调整容量的增长策略。
-
LinkedList(双向链表)
- 创建:首先创建一个element属性为null、next属性为null及previous属性为null 的Entry对象,并赋值给全局的header 属性。header 的 next 及 previous 都指向 header。
-
Vector(数组)
- 创建:默认创建一个大小为10的Object的数组,并将capacityIncrement设置为0
- 添加元素:Vector的add方法加了synchronized关键字,是线程安全的,但数组大小不够时,如果capacityincrement等于小于0,则将Object数组的大小扩大为现有size的两倍
-
Vector,ArrayList,LinkedList的区别
- Vector,ArrayList都是类似数组的形式存储的内存中,LinkedList则以链表的形式进行存储
- Vector线程同步,ArrayList,LinkedList线程不同步
- LinkedList适合插入删除操作,不适合查找,ArrayList,Vector适合查找,不适合指定位置的插入,删除
- ArrayList在元素填满容器时候,会自动扩容50%,而Vector是100%
-
Stack(继承Vector)
- push:通过调用Vector中的add Element来完成
- pop:通过调用peek来获取元素,并删除最后一个元素
- peek:通过获取当前Object数组的大小,获取数组上的最后一个元素
-
HashSet
- 每个对象都有自己默认的散列码hashCode,对象的地址(String不同),添加元素时,插入到表中hashCode%散列单元中,如果发现有数据并且与新数据相同的话,就不插入,否则就替换或者插入。
- 散列单元的大小:默认大小全部都是2的幂次方,初始为16,默认的加载因子是0.75,当16条链表中的75%连接有数据的时候,Hashset开始重新散列,重新开辟一个散列单元为32的散列结果,并重新计算各个数据的存储位置。
- 查找效率:根据add的机制得来,根据散列码和散列表的数据大小计算除余后,就可得到所在数组的位置,然后再查找时候有这个数据即可,查找的代价主要是在链表中,但是真正的链表数据很少
- hashcode和equals方法必须兼容:因为Hashset不允许相同元素同时存在结构中,比较方法不一致会导致插入进相同的散列单元所在的列表中,降低查询效率
- Hashset是无序的,不是线性结构
-
HashMap
- size:表示HashMap中存放KV的数量(链表和树中的KV的总和)
- capacity:容量,capacity子HashMap中桶的数量,默认值是16,一般第一次扩容会到64,之后是2倍,
- LoadFactor:转载因子,衡量HashMap满的程度,默认值是0.75,实时装载因子的方法是size/capacity
- threshold:表示当HashMap的size大于thresheold时会执行resize操作 threshold = capacity * loadFactor
-
JDK1.8中,HashMap什么情况下进行扩容和树化,如何进行树化和扩容?
- 如果在创建 HashMap 实例时没有给定 capacity、loadFactor 则默认值分别是 16 和 0.75
- 当有好多bin被映射到了同一个桶中,如果这个桶中bin的数量小于TREEIFY_THRESHOLD 当然不会转化成树形结构
- 如果这个桶中bin的数量大于TREEIFY_THRESHOLD,但是小于MIN_TREEIFY_CAPACITY,依然使用链表存储,此时会进行扩容
- 如果capacity大于MIN_TREEIFY_CAPACITY,则会进行树化
TREEIFY_THRESHOLD | UNTREEIFY_THRESHOLD | MIN_TREEIFY_CAPACITY |
一个桶的树化阈值 | 一个树的链表还原阈值 | 哈希表的最小树形化容量 |
static final intTREEIFY_THRESHOLD = 8 | static final int UNTREEIFY_THRESHOLD = 6 | static final int MIN_TREEIFY_CAPACITY = 64 |
当桶中元素个数超过这个值时,需要使用红黑树节点替换链表节点 | 当扩容时,桶中元素个数小于这个值,就会把树形的桶元素 还原(切分)为链表结构 |
|
-
JDK1.8 中 HashMap 的 put 方法如何执行
- 判断键值对数组table时候为空或者为null,否则执行resize()进行扩容
- 根据键值key计算hash值得到插入的数组索引i,如果table[i] == null,直接新建节点添加,转向第6步,如果table[i]不为空,转向3
- 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向4,这里的相同指的是hashcode以及equals
- 判断talbe[i]是否为treeNode,即table[i]是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向5
- 遍历table[i],判断链表长度是否大于8,大于8的话直接把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作,遍历过程中若发现key已经存在直接覆盖value即可
- 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
-
LinkedHashMap保证Map顺序
-
HashTable,HashMap,TreeMap的区别
- hashtable线程同步,HashMap非线程同步
- hashtable不允许键值对空值,hashmap允许键值对有空值
- hashtable使用Enumeration,HashMap使用Iterator
- hashtable中hash数组的默认大小是11,增加方式old*2+1,hashMap中hash数组的默认大小是16,增长方式一定是2的指数倍
- Treemap能够把它保存的记录根据键排序,默认是按升序排序
-
HashMap的长度为什么一定要是2幂次方?
- length为2的幂次方,h&(length - 1)就相当于堆length取模,保证了散列的均匀,提升了效率
- length为2的幂次方,为偶数,,这样 length-1 为奇数,奇数的最后一位是 1,这样便保证了 h&(length-1)
的最后一位可能为 0,也可能为 1(这取决于 h 的值),即与后的结果可能为偶数,也可能为奇数,这样便可以保证 散列的均匀性,而如果 length 为奇数的话,很明显 length-1 为偶数,它的最后一位是 0,这样 h&(length-1)的最后一 位肯定为 0,即只能为偶数,这样任何 hash 值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间,因此,length 取 2 的整数次幂,是为了使不同 hash 值发生碰撞的概率较小,这样就能使元素在哈希表中均匀地散列。
-
Vector,list,set和map适用场景
- ArrayList适用于通过位置来读取元素的场景
- LinkedList适用于要头尾操作及插入指定位置的场景
- Vector适用于要线程安全的ArrayList场景
- Stack 适用线程安全的 LIFO场景;
- HashSet 适用于对排序没有要求的非重复元素的存放;
- TreeSet 适用于要排序的非重复元素的存放;
- HashMap 适用于大部分 key-value 的存取场景;
- TreeMap 适用于须排序存放的 key-value 的场景
-
BlockingQueue 工作原理
- BlockingQueue 接口继承自Queue 接口
- 添加元素:
- add:添加元素到队列里,添加成功返回 true,由于容量满了添加失败会抛出 IllegalStateException 异常
- offer:添加元素到队列里,添加成功返回 true,添加失败返回 false
- put:添加元素到队列里,如果容量满了会阻塞直到容量不满
- 删除元素:
- poll:删除队列头部元素,如果队列为空,返回 null。否则返回元素
- remove:基于对象找到对应的元素,并删除。删除成功返回 true,否则返回 false
- take:删除队列头部元素,如果队列为空,一直阻塞到队列有元素并删除
- drainTo:一次性从 BlockingQueue 获取所有可用的数据对象(还可以指定获取数据的个数), 通过该方法,可以提 升获取数据效率;不需要多次分批加锁或释放锁
- 获取元素:
- peek:查询队列头部的元素,队列为空时,返回 null
- element:查询队列头部的元素,队列为空时,抛出异常
-
常用的阻塞队列具体类有 ArrayBlockingQueue、LinkedBlockingQueue、 DelayQueue、PriorityBlockingQueue、 SynchronousQueue 等
- ArrayBlockingQueue(有界队列)的原理就是使用一个可重入锁和这个锁生成的两个条件对象进行并发控制。ArrayBlockingQueue 是一个带有长度的阻塞队列,初始化的时候必须要指定队列长度,且指定长度之后不允许进行修改。ArrayBlockingQueue 只有1个锁,添加数据和删除数据的时候只能有 1 个被执行,不允许并行执行。
- LinkedBlockingQueue(无界队列)是一个使用链表完成队列操作的阻塞队列,链表是单向链表。LinkedBlockingQueue 有 2 个锁,放锁和拿锁,添加数据和删除数据是可以并行进行的,当然添加数据和删除数据的时候只能有 1 个线程 各自执行。LinkedBlockingQueue 在高并发读写操作都多的情况下,性能比 ArrayBlockingQueue 好一些,在遍历以及 删除元素则要两把锁都锁住。