集合框架
1、List、Set、Map区别:
List:List接口存储不唯一有序的对象。
Set:不允许重复。
Map:键值对存储,两个Key可以引用相同的对象,但Key不能重复。
2、ArrayList和LinkedList区别:
a.保证线程安全。都是不同步的,即不保证县线程安全。
b.底层数据结构。ArrayList底层是Object数组;LinkedList底层是双向链表结构。
c.插入/删除和元素位置的关系。ArrayList数组存储,受元素位置影响。LinkedList链表存储,不受元素位置的影响。
d.快速随机访问。ArrayList支持,LinkedList不支持。快速随机访问是通过元素的序号快速获取元素对象。
e.内存空间占用。ArrayList体现在list列表的结尾会预留一定容量空间。LinkedList体现在其每一个元素都需要消耗更多空间(存放直接后继和直接前驱及数据)。
3、list遍历方式:
实现RandomAccess接口的list,优先选择普通for循环,其次foreach。
未实现RandomAccess接口的list,优先选择iterator遍历(foreach遍历的底层也是通过iterator实现的),大size的数据,不适用普通for循环。
4、双向链表和双向循环链表
双向链表。两个指针,一个prev指向前一个节点,一个next指向后一个节点。
双向循环链表。最后一个节点的next指向head,而head的prev指向最后一个节点,构成一个环。
5、ArrayList和Vector的区别:
Vector类的所有方法都是同步的,两个线程可以安全的访问一个Vector对象,但不适用于一个线程,一个线程访问Vector,代码在同步操作上耗费大量时间。
ArrayList不是同步的,用在不需要保证线程安全时。
6、HashMap和Hashtable的区别:
a.线程安全。HashMap非线程安全。hashTable线程安全,内部方法经synchronized修饰。
b.效率。HashMap效率比HashTable高。
c.Null key 和 Null value。HashMap中,null可作为键,只能有一个可以有一个/多个键对应的值为null。HashTable中put的键值不能为null,若有抛出NullPointerException。
d.初始容量、每次扩容大小。
①默认初始大小。HashMap为16,每次扩充,容量是原来的2倍。HashTable为11,每次扩容,容量是原来的2n+1倍。
②创建时给定容量初始值时,HashMap会将其扩充为2的幂次方。HahsTable直接使用给定的大小。
e.底层数据结构。HashMap在链表长度>阈值(默认8)时,将链表转化为红黑树,减少搜索时间。HashTable则没有该机制。
下面方法保证了 HashMap 总是使用2的幂作为哈希表的大小。
/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
7、HashMap 和 HashSet 区别:
HashSet底层基于HashMap实现,其方法(除了clone()、writeObject()、readObject()外)是直接调用Hashmap的方法。
HahMap | HashSet |
实现Map接口 | 实现Set接口 |
存储键值对 | 存储对象(仅) |
put()方法添加元素 | add()方法添加元素 |
使用键(key)计算HashCode | 使用成员对象计算HashCode值,两个对象的hashcode可能相同,故用equals()方法判断对象相等性。 |
HashSet检查重复:把对象加入HashSet时,先计算对象的和hashcode值判断对象加入的位置,并与其它加入的对象hashcode值比较,若不相等,即认为没有重复出现。若存在相同hashcode值的对象,会调用equals()方法检查是否真的相同。两者还相同,HashSet不会让对象加入成功。
hashCode() 和 equals():
a.两个对象相等,则hashCode一定相同。
b.两个对象相等,两个对象的equals方法返回true。
c.两个对象有相同的hashcode值,但不一定相等。
d.equals方法若被覆盖,则hashCode方法必须被覆盖。
e.hashCode()默认是对堆上的对象产生独特值。若没有重写hashCode(),则该class的两个对象一定不会相等(即使指向相同的数据)。
8、== 和 equals()的区别:
a.==:判断两个变量/实例是否指向同一个内存空间。equals:判断两个变量/实例指向的内存空间的值是否相同。
b.==:对内存地址进行比较。equals:对字符串内容进行比较。
c.==:引用是否相同。equals:值是否相同。
9、HashMap底层实现
JDK1.8之前:
HashMap底层是数组+链表的链表散列。通过key的hashCode经扰动函数处理得到hash值,再通过(n-1)&hash判断当前元素存放的位置(n是数组长度),若当前位置存在元素,则判断两个元素(该元素和要存入的元素)的hash值、key是否相同。若相同,直接覆盖;不同就通过拉链法解决冲突。
注:扰动函数:HashMap的hash方法,减少碰撞。使用hash方法是为了防止一些实现比较差的hashClde()方法。
JDK1.8:hash方法更简化,原理不变。
拉链法:将链表和数组相结合,即创建一个链表数组,数组中每一格是一个链表。遇到哈希冲突,就将冲突的值加到链表中。
JDK1.8之后:解决哈希冲突有较大变化,当链表长度>阈值(默认8)时,链表转化为红黑树,减少搜索时间。
TreeMap、TreeSet和HashMap(JDK1.8之后)底层都用红黑树。红黑树是为了解决二叉查找树的缺陷,因为二叉查找树某些情况会退化成一个线性结构。
10、HashMap长度是2的幂次方:
数组下标计算方法:(n-1)&hash,n是数组长度。
取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。
11、ConcurrentHashMap 和 HashTable 的区别:狐妖体现在实现线程安全的方式上。
a.底层数据结构。ConcurrentHashMap在1.7采用分段数组+链表;1.8的和HashMap一样是数组+链表/红黑二叉树。hashTable和1.8之前的HashMap采用数组+链表形式。|| 数组是HashMap的主体,链表主要为了解决哈希冲突而存在。
b.实现线程安全的方式(重要)。①JDK1.7之前,ConcurrentHashMap(分段锁)对整个桶分割分段(Segment),每把锁只锁容器中一部分数据,多线程访问容器里不同数据段的数据,就不会产生锁竞争,提高并发访问率。
JDK1.8 直接用Node 数组+链表+红黑树的结构,并发控制用synchronized和CAS操作,摒弃Segment概念。
②HashTable(同一把锁):使用synchronized保证线程安全,效率低。在一个线程访问同步方法时,其他线程也访问同步方法就可能进入阻塞或轮询状态。如用put添加元素,另一个线程不能使用put添加元素,也不能用get,竞争越来越激烈、效率越来越低。
ConcurrentHashMap线程安全的实现
JDK1.7:将数据分成一段一段来存储,再给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其它段的数据也能被其他线程访问。
ConcurrentHashMap是由Segment数组结构和HashEntry数据结构组成。Segment实现ReentrantLock,是一种可重入锁,扮演锁的角色。HashEntry用于存储键值对数据。
static class Segment<K,V> extends ReentrantLock implements Serializable {
}
一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
JDK1.8:
ConcurrentHashMap采用CAS、synchronized保证并发安全,取消了Segment分段锁。数据结构和HashMap1.8类似为数组+链表/红黑二叉树。Java 8在链表长度>阈值(8)时,将链表(寻址时间复杂度O(N))转换为红黑树(寻址时间复杂度0(log(N))。
synchronized只锁定当前链表或红黑二叉树的首节点,只要hash不冲突,就不会产生并发,效率又提升N倍。
12、comparable和Comparator的区别
comparable接口出自java.lang包,排序方法compareTo(Object obj)
comparator接口出自java.util包,排序方法compare(Object obj1,Object obj2)。
13、集合框架底层数据结构总结
Collection
------List
-----Arraylist:Object数组
-----Vector:Object数组
-----LinkedList:双向链表(JDK1.6之前循环链表,JDK1.7取消循环)
------Set
-----HashSet(无序、唯一/不重复):基于HashMap,底层采用HashMap保存元素。
-----LinkedHashSet:LinkedHashSet继承HashMap,其内部通过LinkedHashMap实现。
-----TreeSet(有序、唯一/不重复):红黑树(自平衡排序二叉树)
Map
------HahsMap:JDK1.8之前HashMap由数组+链表组成,数组是主题,链表为了解决哈希冲突(拉链法解决冲突)。
JDK1.8之后在解决哈希冲突上变化较大,当链表长度>阈值(默认8)时,链表转化为红黑树,减少搜索时间。
------LinkedHashMap:继承自HahsMap,底层仍是拉链式散列结构即数组+链表/红黑树,另外LinkedHahsMap在此基础上增加了一条双向链表,使得保证键值对的插入顺序等。
------HashTable:数组+链表组成,数组是主体,链表为了解决哈希冲突。
------TreeMap:红黑树(自平衡排序二叉树)。
14、集合的选用:根据各自特点选用。
根据键值获取元素值,选Map接口的集合。
------需要排序时,选TreeMap;
------不需要排序时,选HashMap;
------需要保证线程安全时,选ConcurrentHashMap;
只需要存放元素时,选实现Collention接口的集合;
需要保证元素唯一时,选实现Set接口的集合,如TreeSet、HashSet;
不需要保证元素唯一时,选实现List接口的集合,如ArrayList、LinkedList;
再根据实现这些接口的集合的特点选用。