一:Collection接口
Set接口和List接口都继承于Collection接口
1.Set
无序不可重复
(1)HashSet
HashSet其实是一个map,源代码:
public HashSet() { map = new HashMap<E,Object>(); }
add()方法调用的是个map,源代码:
public boolean add(E e) { return map.put(e, PRESENT)==null; }
可以看出HashSet加入的对象做了map的key,因此其中的元素是不可以重复的。
2.List
有序可重复
(1)ArryList
实现是一个Object类型的数组ArrayList的长度增长算法为
“新长度=(旧长度*3)/2+1”,源代码算法如下
int
newCapacity = (oldCapacity *
3
)/
2
+
1;
ArrayList.add()最终调用的
是System.arraycopy(src, srcPos, dest, destPos, length);方法,并没有使用
HashCode
ArrayList为什么线程不安全,请看:
https://blog.csdn.net/u012859681/article/details/78206494
概括的说是扩容时会引起数组下标越界和后一个加入的元素覆盖前一个加入的元素
(2)Vector
线程安全的List,所谓线程安全是它只有在使用add()、remove()、set()、get()等
方法时是线程安全的,但是它在遍历集合的时候并不能做到线程池安全,通过看
它的源码可以发现它在调用add()、remove()、set()、get()等方法时,使用了
Sychronized。
(3)CopyOnWriteArrayList和Collections.synchronizedList()
CopyOnWriteArrayList和Collections.synchronizedList是实现线程安全的列表的
两种方式。两种实现方式分别针对不同情况有不同的性能表现,其中
CopyOnWriteArrayList的多线程写操作性能较差,而多线程的读操作性能较好。
Collections.synchronizedList的写操作性能比CopyOnWriteArrayList在多线程操
作的写操作好很多,而读操作因为是采用了synchronized关键字的方式,其读
操作性能并不如CopyOnWriteArrayList。因此在不同的应用场景下,应该选择不
同的多线程安全实现类。
CopyOnWriteArrayList是java.util.concurrent包中的一个List的实现类。使用
Lock加锁,CopyOnWrite的意思是在写时拷贝,也就是如果需要对CopyOnWriteArrayList
的内容进行改变,首先会拷贝一份新的List并且在新的List上进行修改,最后将原
List的引用指向新的List。CopyOnWriteArrayList可以线程安全的遍历(可以看出
来,读的时候如果有新的数据正在写是无法实时的读取到的,有延时,得等新数
据写完以后,然后才可以读到新的数据)。
源代码:
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }
(4)Queue保持一个队列(先进先出)的顺序
二:Map接口
一组成对的"键值对"对象
1.HashMap
(1)HashMap概述
HashMap实现是一个链表的数组,基于链地址法形成的散列,数组是
由Entry组成,一个Entry包含一个key-value 键值对。
简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,
链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链
表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需
一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂
度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,
仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性
能考虑,HashMap中的链表出现越少,性能才会越好。
可以参考文章:
https://www.cnblogs.com/holyshengjie/p/6500463.html
(2)HashMap是怎么处理Hash冲突的
Hash冲突时在put时产生,做如下操作:
(a) 对key的hashCode()做hash,然后再计算index;
(b)如果没碰撞直接放到bucket里;
(c)如果碰撞了,用equals方法进行比较,元素不存在集合中则以链表的形式存在buckets前面(1.8是放在buckets后面),因为放在头部所需要遍历的节点最小
如果元素存在集合中则新的元素覆盖旧的元素。
(d)如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),就把链表转换
成红黑树;
(e)如果节点已经存在就替换old value(保证key的唯一性)
(f)如果bucket满了(超过load factor*current capacity),就要resize。
put时源码:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
//1.根据传入key的hash值获取数组下标
int i = indexFor(hash, table.length);
//遍历”1“处数组下标对应的连边
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//3.如果发现key已经在链表中存在,则修改并返回旧的值
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//4.如果遍历链表没发现这个key,则会调用以下代码
modCount++;
addEntry(hash, key, value, i);
return null;
}
addEntry源码:
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
Entry的构造方法:
Entry( int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; }
get时的逻辑:
(a)bucket里的第一个节点,直接命中;
(b)如果有冲突,则通过key.equals(k)去查找对应的entry
(c)若为树,则在树中通过key.equals(k)查找,O(logn);
(d)若为链表,则在链表中通过key.equals(k)查找,O(n)。
Hashmap多线程的环境下会出现死锁,具体原因参考文章:
https://coolshell.cn/articles/9606.html
get时源码:
public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); //先定位到数组元素,再遍历该元素处的链表 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }
(3)HashMap为什么是线程不安全的
下文摘自博客:
http://www.cnblogs.com/qiumingcheng/p/5259892.html
put方法不是线程安全的
(a)扩容方法线程不安全扩容方法也不是同步的,通过代码我们知道在扩容
过程中,会新生成一个新的容量的数组,然后对原数组的所有键值对重
新进行计算和写入新的数组,之后指向新生成的数组。
(4)1.8对HashMap的优化
JDK 1.8对HashMap进行了比较大的优化,底层实现由之前的“数组+链表”改为
“数组+链表+红黑树”,当链表长度太长(默认超过8)时,链表就转换为红黑
树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树
的插入、删除、查找等算法。
什么是红黑树以及红黑树的优点查看:
https://www.sohu.com/a/201923614_466939,看后面别看前面,前面都是废话
2.HashTable
Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。
它并不继承自Collection,把它写在这里纯粹是为了方便和HashMap比较。
HashTable和HashMap采用相同的存储机制,二者的实现基本一致,不同的是:
(1)HashMap是非线程安全的,HashTable是线程安全的,内部的方法基本都是
synchronized。
(2)HashTable不允许有null值的存在。
在HashTable中调用put方法时,如果key为null,直接抛出
NullPointerException
3.TreeMap以及TreeMap和HashMap的比较
(1)非线程安全的
(2)基于红黑树实现。TreeMap没有调优选项,因为该树总处于平衡状态。
(3)TreeMap适用于按自然顺序或自定义顺序遍历键(key);HashMap适用于在
Map中插入、删除和定位元素。
4.ConcurrentHashMap和HashTable的区别
ConcurrentHashMap继承自AbstractMap,实现了ConcurrentMap接口,使得它
具有Map的属性,同时又有多线程相关的属性,它并没有继承Collection,写在
这纯粹是为了比较方便
ConcurrentHashMap的性能优于HashTable,ConcurrentHashMap的锁是基于
Lock的,而HashTable的锁是基于synchronized的
6.Lock和sychronized:
(1)sychronized获取锁的线程如果被阻塞了,那么其它等待锁的线程需要一直等
待下去,而lock就可以使线程不处于一直等待的状态,可以只等待一段时间或
响应中断。
(2)当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作
会发生冲突现象,但是读操作和读操作不会发生冲突现象。如果使用
sycnronized读操作和读操作会发生冲突,而使用lock读操作和读操作不会发生
冲突。
(3)lock可以知道线程获取锁是否成功,而sychronized却不能。
(4)sychronized可以自动释放锁,lock需要手动释放锁(经常在finally中)。
(5)锁类型:
sychronized:可重入 不可中断 非公平(非公平锁和排队顺序无关,而公平
锁和排队顺序有关)
lock:可重入 可中断 可公平(两者皆可)
7.了解ConcurrentHashMap:
ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然
后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他
段的数据也能被其他线程访问。有些方法需要跨段,比如size()和containsValue(),
它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作
完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现
死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也
是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需
要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment
是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,
HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment
数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment
里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个
Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改
时,必须首先获得它对应的Segment锁。其结构如下图:
参考文章:http://www.importnew.com/16142.html
关于ArrayList的5道面试题请查看网址:http://blog.csdn.net/quentain/article/details/51365432