Java容器
- Collection
- List
- ArrayList
- LinkedList
- Vector(线程安全)
- Set
- HashSet
- TreeSet
- LinkedHashSet
- List
- Map
- HashMap
- LinkedHashMap
- Hashtable
- TreeMap
1、List
List接口下的类存储元素,有序、可重复的。
1.1、ArrayList
ArrayList内部实现是一个可变长度的数组,初始化创建的时候,这个数组长度为0,是一个空数组。在add第一个元素的时候,长度会变成默认长度10。之后每次扩容,长度都会变成原来的1.5倍。newLength=oldLength+(oldLength>>>1)
。
ArrayList的特点,支持快速随机取元素,插入删除中间元素效率慢,需要修改后面或者前面的所有元素位置。
1.2、LinkedList
LinkedList内部实现是一个双向链表(jdk1.7及以前是双向循环链表)。一个节点的数据结构为:
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
LinkedList不支持快速随机读取元素,插入和删除元素效率高。因为这里是一个双向链表,删除元素时候,只需要将要删除的节点前后节点关联就行。
1.3、RandomAccess接口(随机访问)
public interface RandomAccess {
}
源码中RandomAccess接口中什么都没有定义。所以,RandomAccess接口只是一个标识,表示实现这个接口的类具有随机访问的功能。
在binarySearch()
方法中,他判断传入的list是否RandomAccess实例,如果是,调用indexBinarySearch()
方法,如果不是,那么调用iteratorBinarySearch()
方法
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
2、Map
使用键值对存储(key-value)存储,key是无序的、不可重复的,value是无序的、可重复的,每个键最多映射到一个值。
2.1、equals()和hashcode()
在说Map和Set之前,需要先说一下这两个方法。map中的Key是不允许重复的,重复的就会覆盖。而Java中判断两个对象是否相同有两种==
和equal()
==是判断两个对象的地址是否相同,也就是判断是不是同一个对象。equal一般是判断两个对象内容是否相同。map中的key唯一,在map添加新元素时候,会把待加入的key值取hashcode值,如果hashcode值相同,再去判断equal是否相同,相同的话就覆盖。hashcode一样的对象,不一定相同,equal相同的对象,hashcode一定相同。hashcode这里是为了预先判断相等
,如果hashcode不相等的话,就没必要再使用费时间的equal()方法去比较了。所以我们重写一个类的equal方法时候,一定要重写hashcode。
2.2、HashMap
jdk1.8之前HashMap
由数组+链表组成,数组是hashmap的主体,链表主要是为了解决哈希冲突而存在的(拉链法 解决冲突)。jdk1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认8)(将链表转换为红黑树前会判断,如果当前数组的长度小于64,那么会选择先进行数组扩容,而不是转化为红黑树)时,链表将会转化为红黑树,以减少搜索时间。
红黑树避免了二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
HashMap的长度是2的幂次方。hash的值范围是-2147483648-2147483647,前后空间大概40亿,太大了,所以一般hash对数组长度取余就可以得到数组下标计算方法(n-1)&hash
(n代表数组长度)。这也就解释了HashMap的长度为啥什么是2的幂次方。取余首先我们想到的%取余操作,但是如果除数是2的幂次则等价hash和除数减一做与(&)操作,并且位操作比%操作快得多。
2.3、LinkedHashMap
LinkedHashMap继承自HashMap,所以他的底层仍然是基于拉链式散列表结构即有数组和链表或红黑树组成。另外,LinkedHashMap在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑
2.4、Hashtable
数组+链表组成,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。
HashMap和Hashtable的区别
Hashmap是非线程安全的,Hashtable是线程安全的,因为HashTable内部的方法基本都经过synchronized
修饰,(性能会有明显的下降,如果你要保证线程安全的话就使用ConcurrentHashMap把!)
效率没有HashMap高,因为需要考虑线程安全的问题。
HashMap可以存储null的key和value,但null作为键只能有一个,null作为值可以有多个;Hashtable不允许有null键和null值,否则会抛出NullPointerException。
初始化容量大小和每次扩充容量大小不同:
- Hashtable默认初始化大小为,之后每次扩充,容量变为原来的2n+1。HashMap默认初始化大小是16.之后每次扩充,容量变为原来的2倍。
- 创建时如果给定了容量初始值,那么Hashtable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方的大小
1.8以后HashMap引入了红黑树和阈值等扩容机制,Hashtable没有这样的机制。
2.5、TreeMap
红黑树(自平衡的排序二叉树)。TreeMap和HashMap都继承自AbstractMap
,但是需要注意的是TreeMap
他还实现了NavigableMap
接口和SortedMap
接口。
实现SortMap接口让TreeMap有了对集合内元素的搜索的能力。实现SortMap接口让TreeMap有了对集合根据键排序的能力。默认是按key升序排序,不过我们也可以指定排序比较器。
综上,相对于HashMap来说TreeMap主要多了对集合中的元素根据排序的能力以及对集合内元素的搜索的能力。
2.6、ConcurrentHashMap
线程安全的Map集合,用于多线程的场景。
3、Set
存储的元素时无序的、不可重复的。
无序性不等于随机性,无序性是指存储的数据在底层数组中并非按照索引的顺序添加,而是根据数据的哈希决定的。
不可重复性,是根据equals()判断的,需要同时重写equals()方法和Hashcode()方法。
3.1、HashSet
HashSet是set的接口的主要实现类,HashSet的底层是HashMap,线程不安全的,可以存储null值;
HashSet底层就是基于HashMap实现的。HashSet中的值不允许重复,使用的HashMap的key不允许重复来实现的。
3.2、LinkedHashSet
LinkedHashSet是HashSet的子类,能够按照添加的顺序遍历;
3.3、TreeSet
TreeSet底层使用红黑树,能够按照添加元素顺序进行遍历,排序的方式由自然排序和定制排序。
3.4、comparable和Comparator
这两个接口都可以实现TreeSet的定制排序。
comparable
接口实际上是出自java.lang
包,它有一个comparaTo(Object obj)
方法用来排序comparator
接口实际上是出自java.util
包,他有一个compare(Object obj1,Object obj2)
方法用来排序
一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo
方法或者compare()
方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写comparaTo()
方法和使用自制的Comparator
方法或者以两个Comparater来实现歌名排序和歌手排序