JAVA集合
常用集合
set
Set不按特定方式进行排序,并且没有重复的对象,它的有些实现类能对集合中的对象按照特定的顺序排序。主要有两个实现类:HashSet和TreeSet
HashSet按照哈希算法来存取集合中的对象,存取速度比较快。
TreeSet实现了SortSet接口,具有排序功能。
List
List主要特征是以线性方式存储,集合中允许存放重复对象主要实现类包括ArrayList和LinkList
ArrayList代表长度可变的数组,允许对元素进行快速的随机访问,但是向ArrayList中插入与删除元素较慢。
LinkList在实现中采用链表的结构,插入和删除元素较快,随机访问则相对较慢。
LinkList单独具有addFirst(),addLast(),getFirst(),getLast(),removeFirst(),removeLast()等方法这些方法使得LinkList可以作为堆栈,队列和双向队列使用
Queue
Queue队列中的对象按照先进先出的规则来排列。在队列末尾添加元素,在队列的头部删除元素。可以有重复对象。
双向队列Deque则允许在队列的尾部和头部添加和删除元素。
因为LinkList可以作为双向队列使用,所以Queue和Deque使用比较少
Map
HashMap不保证映射的顺序,特别是它不保证该顺序恒久不变。(也就是无序)
HashMap底层基于散列表实现,由数组加链表组成,数组是HashMap的主体,链表用来解决Hash冲突
采用key的hashCode()方法结合数组长度通过无符号右移>>> 异或^ 计算出数组索引,通过链表法解决哈希冲突(如果key值equals()则替换旧的value),
平方去中法 取余数 伪随机数法
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// aka 16 默认初始容量是2的n次幂,可以减少哈希碰撞3&(8-1)=3 2&(8-1)=2 | 3&(9-1)=0 2&(9-1)=0
DEFAULT_LOAD_FACTOR=0.75 代表数组满的程度,如果小 则数组利用率不高,太大 则更容易产生Hash碰撞(每个桶的链表会变多)
JDK1.8之后链表长度超过8,并且数组长度大于64会进化成红黑树(红黑树查询O(logn) 链表O(n))
JDK1.7的时候HashMap是头插,扩容的时候链表顺序会倒序。 1.8时是尾插,扩容后链表顺序不变 (也可以防止并发扩容的时候 变成环链)。
TreeMapTreeMap基于红黑树(Red-Black tree)的 NavigableMap
实现,有序。
HashMap是使用hashCode和equals方法去重的。而TreeMap依靠Comparable或Comparator来实现key去重
并发类容器(集合)
在java集合框架中,Set List Queue Map的实现类都没有采取同步机制。在单线程环境中,这种实现方式会提高操纵集合的效率,java虚拟机不必会因为管理用不锁而产生额外的开销。
在多线程环境中,可能会有多个线程同时操纵一个集合,比如一个线程在为集合排序,而另外一个线程正不断的向集合中添加新的元素。
为了避免并发问题,可以直接采用java.util.concurrent并发包提供线程安全的集合。列如
ConcurrentHashMap 、ConcurrentSkipListMap、ConcurrentSkipListSet和ConcurrentLinkedQueue。
这些集合的底层实现采用了复杂的算法,保证多线程访问集合时,既能保证线程之间的同步,又具有高效的并发性能。
Hashtable:
Hashtable,用作键的对象必须实现 hashCode
方法和 equals
方法。HashTable 由于所有方法都加了 synchronized 关键字所以是线程安全的。
如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
历史集合类 :Vector Hashtable Stack Enumeration 在实现中都采用了同步机制,并发性能较差,因此不提倡使用它们。
ConcurrentHashMap:
JDK7:ConcurrentHashMap采用了分段锁的,把容器默认分成16段,put值的时候 只是锁定16断中的一个部分,就是把锁给细化了
JDK8:采用的CAS自旋
疑问:HashMap有线程安全的ConcurrentHashMap 但是TreeMap为什么没有ConcurrentTreeMap
因为CAS操作用在红黑树实现起来太复杂
所以用ConcurrentSkipListMap用CAS实现排序(跳表代替Tree)
跳表:在链表的基础上一层一层的加一些个关键元素的链表,加了个索引。跳表的查找效率比链表本身要高,同时它的CAS实现难度比TreeMap容易很多。
HashMap为什么是线程不安全的
1.首先我们解释下HashMap的扩容:当HashMap的元素个数超过:数组长度乘以负载因子DEFAULT_LOAD_FACTOR(16*0.75)=12时,就会把数组长度扩大为16X2=32
关于死循环的问题,在Java8中个人认为是不存在了,在Java8之前的版本中之所以出现死循环是因为在resize的过程中对链表进行了倒序处理(头插);在Java8中不再倒序处理(尾插),自然也不会出现死循环。