点击查看JDK源码剖析系列
目录
前言
Java集合分为Collection
和Map
两种,在实际开发中使用频率极高,其实现细节和用法在面试中几乎是必问的知识点。在特定的需求环境下选用合适的子类可以提高代码质量,并使业务实现更加轻松。
Java集合体系结构
- Java中的集合主要分为
Collection
和Map
两类,并且所有集合都继承或依赖于Collection
- Collection分为
List
、Set
和Queue
- List分为
LinkedList
、ArrayList
和Vector
,Vector有子类Stack
- Set分为
HashSet
和TreeSet
- Map分为
TreeMap
、HashMap
、WeakHashMap
和Hashtable
体系结构图如下:
其中
Set
放的对象是无序,不能重复的,集合中的对象不按特定的方式排序,只是简单地把对象加入集合中,非线程安全Map
存储的是键值对,键不能重复,值可以重复。根据键得到值,对map
集合遍历时先得到键的set
集合,对set集合进行遍历,得到相应的值。可以接受空字串“”
,或者null
作为键值,非线程安全ArrayList
有序,可以包含重复的元素,通过索引访问。查询效率高,增删元素效率低,非线程安全LinkedList
有序,可以包含重复的元素,通过索引访问。查询效率低,增删元素效率高,非线程安全Vector
基本类似ArrayList
,但是线程安全Stack
继承自Vector
,实现一个后进先出的栈,线程安全Hashtable
继承Map
接口,实现一个key-value映射的哈希表。键值对不接受null
。get与put操作时间开销为常数,线程安全HashMap
和Hashtable
类似,键值对可接受null
,但是将HashMap视为Collection时,其迭代子操作时间开销和HashMap
的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。非线程安全WeakHashMap
一种改进的HashMap
,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。非线程安全TreeMap
基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的Comparator
进行排序,基本操作containsKey、get、put和remove的时间复杂度是 log(n) 。非线程安全
集合源代码剖析
一、Collection接口
public interface Collection<E> extends Iterable<E> {
int size();//返回集合中元素的个数
boolean isEmpty();//如果集合中没有元素,返回true
boolean contains(Object o);//如果集合中包含特定对象,返回true
Iterator<E> iterator();//返回该集合对象的迭代器
Object[] toArray();//返回包含该集合对象所有元素的Object类型数组
<T> T[] toArray(T[] a);// 如果集合与指定数组长度和类型一致,则将集合的元素放在数组里返回。否则,将分配一个新数组,数组的大小和这个集合的大小相同。如果指定数组长度小于集合的长度,则将集合的元素放在数组里后,用null填充后再返回该数组。
boolean add(E e);//添加元素,成功返回true
boolean remove(Object o);//删除元素,成功返回true
boolean containsAll(Collection<?> c);//如果集合包含传入集合的所有元素,返回true
boolean addAll(Collection<? extends E> c);//将传入集合的所有元素追加至该集合的末尾
boolean removeAll(Collection<?> c);//将集合中与传入集合中所有相同的元素移除
/**
* 1.8加入的模板方法,按条件过滤集合中的元素
*/
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
boolean retainAll(Collection<?> c);//如果存在相同元素,这个集合中仅保留相同的元素并返回true。如果不存在相同元素,这个会变为空并返回true。P.S.如果集合相同,则返回false且什么都不做
void clear();//清空集合
boolean equals(Object o);//判断两集合是否相等
int hashCode();//计算并返回集合的hash值
@Override
default Spliterator<E> spliterator() {//JDK1.8新加入的模板方法,获得可并行处理的迭代器
return Spliterators.spliterator(this, 0);
}
default Stream<E> stream() {//JDK1.8新加入的模板方法,获得集合的stream对象,对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。像一个高级版本的Iterator。
return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {//JDK1.8新加入的模板方法,获得并行执行的流.它通过默认的ForkJoinPool,可能提高你的多线程任务的速度。
return StreamSupport.stream(spliterator(), true);
}
}
Collection接口定义了一组有关集合的常规操作,不做赘述。
值得关注的是JDK1.8之后新加入的几个模板方法:
- removeIf
- retainAll
- spliterator
- stream
- parallelStream
二、List
public interface List<E> extends Collection<E> {
...
//省略Collection中出现过的函数
...
boolean addAll(int index, Collection<? extends E> c);//将集合c中的所有元素插入到此集合中,插入位置位index
E get(int index);//按下标获得集合中元素
E set(int index, E element);//按下标设置集合中元素
void add(int index, E element);//按下标向集合添加一个元素
E remove(int index);//按下标移除一个元素
int indexOf(Object o);//获得某对象的下标值,若集合有多个该元素,则返回第一个相同元素的下标
int lastIndexOf(Object o);//获得某对象在集合中最后一次出现的下标值
ListIterator<E> listIterator();//获得list迭代器,和普通迭代器相比多了add,set,remove,向前迭代等操作
ListIterator<E> listIterator(int index);//获得list迭代器,保留下标为index以及之后的元素
List<E> subList(int fromIndex, int toIndex);//返回原来list的从fromIndex到toIndex(左闭右开)之间这一部分的视图,慎用
//JDK1.8加入的模板方法,根据UnaryOperator的apply方法返回的结果,替换集合中所有的元素
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
//JDK1.8加入的模板方法,通过实现compare方法,按一定规则对集合中的对象进行排序
@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
}
在Collection的基础上加入了get、set、indexOf、lastIndexOf等针对元素操作的方法,还增加了针对整个集合进行操作的方法如listIterator、subList、replaceAll、sort等。对于JDK1.8新加入的几个模板方法我们可以特别留意一下:
- replaceAll
- sort
三、Set接口
public interface Set<E> extends Collection<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean retainAll(Collection<?> c);
boolean removeAll(Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT);
}
}
Set
中的方法在Collection
中都有,不做赘述。
四、Queue
public interface Queue<E> extends Collection<E> {
boolean add(E e);//向队列末尾添加一个元素,超出队列界限会抛异常
boolean offer(E e);//向队列末尾添加一个元素,超出队列界限不会抛异常(返回false)
E remove();//移除并返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException异常
E poll();//移除并返问队列头部的元素,如果队列为空,则返回null
E element();//返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException异常
E peek();//返回队列头部的元素,如果队列为空,则返回null
}
Queue
队列在数据结构中是一种特殊的线性表,元素只能添加到末尾,只能获取第一个元素,即先进先出(FIFO)。
五、Map
public interface Map<K,V> {
int size();//返回Map的元素个数
boolean isEmpty();//元素个数是否为0
boolean containsKey(Object key);//是否拥有某个key
boolean containsValue(Object value);//是否拥有某个值
V get(Object key);//根据key获得value
V put(K key, V value);//放入一个元素
V remove(Object key);//根据key移除value
void putAll(Map<? extends K, ? extends V> m);//可以合并两个MAP,如果有相同的key那么用后面的覆盖前面的
void clear();//清空map
Set<K> keySet();//返回Map中所有key的set对象
Collection<V> values();//返回map中所有value的Collection
Set<Map.Entry<K, V>> entrySet();//返回一个表示映射项(里面有Key和Value)的Set
/**
*Map.Entry 是Map中的一个接口,他的用途是表示一个映射项(里面有Key和Value),而
*Set<Map.Entry<K,V>>表示一个映射项的Set。Map.Entry里有相应的getKey和getValue方法,
*即JavaBean,让我们能够从一个项中取出Key和Value。
*/
interface Entry<K,V> {
K getKey();//返回这个entry的key
V getValue(); //返回这个entry的value
V setValue(V value);//设置该entry的value
boolean equals(Object o);//如果给出的Object也是entry并且代表相同的映射,则返回true
int hashCode();//返回Hash码
//返回关于key的Comparator对象
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
//返回关于value的Comparator对象
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getValue().compareTo(c2.getValue());
}
//用已有的Comparator返回关于key的Comparator对象
public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
}
//用已有的Comparator返回关于value的Comparator对象
public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
}
}
boolean equals(Object o);//比较两个map是否相同
int hashCode();//返回map的Hash码
//根据key获取value,若不存在返回默认值
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
//JDK1.8新加入模板方法,函数式编程,可以在遍历的同时进行操作
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
//JDK1.8新加入,对每个元素执行operator指定的操作,并用操作结果来替换原来的元素。其中UnaryOperator是一个函数接口,里面只有一个待实现函数T apply(T t)。
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
// ise thrown from function is not a cme.
v = function.apply(k, v);
try {
entry.setValue(v);
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
}
}
//尝试添加一个元素,若该key已被占用,则不添加
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
//如果key对应的value和传入的value相等,则移除该key对应的元素
default boolean remove(Object key, Object value) {
Object curValue = get(key);
if (!Objects.equals(curValue, value) ||
(curValue == null && !containsKey(key))) {
return false;
}
remove(key);
return true;
}
//替换元素,如果oldvalue和key对应的value不一致,则不替换并返回false
default boolean replace(K key, V oldValue, V newValue) {
Object curValue = get(key);
if (!Objects.equals(curValue, oldValue) ||
(curValue == null && !containsKey(key))) {
return false;
}
put(key, newValue);
return true;
}
//如果存在key,则将key对应的value替换为新值
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
/**
* 当前Map中不存在key值的映射或映射值为null时,才调用mappingFunction,
* 并在mappingFunction执行结果非null时,将结果跟key关联,
* Function是一个函数接口,里面有一个待实现方法R apply(T t)。
* computeIfAbsent()常用来对Map的某个key值建立初始化映射.
* 比如我们要实现一个多值映射,Map的定义可能是Map<K,Set<V>>,要向Map中放入新值
*/
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
/**
* 作用跟computeIfAbsent()相反,即,只有在当前Map中存在key值的映射且非null时,
* 才调用remappingFunction,如果remappingFunction执行结果为null,则删除key的映射,
* 否则使用该结果替换key原来的映射.
*/
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
if ((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
put(key, newValue);
return newValue;
} else {
remove(key);
return null;
}
} else {
return null;
}
}
/**
* 作用是把remappingFunction的计算结果关联到key上,
* 如果计算结果为null,则在Map中删除key的映射.
*/
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
if (oldValue != null || containsKey(key)) {
remove(key);
return null;
} else {
return null;
}
} else {
put(key, newValue);
return newValue;
}
/**
* 如果Map中key对应的映射不存在或者为null,则将value(不能是null)关联到key上;
* 否则执行remappingFunction,如果执行结果非null则用该结果跟key关联,
* 否则在Map中删除key的映射.
*/
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
}
对于JDK1.8新加入的几个模板方法我们可以特别留意一下:
- comparingByKey
- comparingByValue
- forEach
- replaceAll
- putIfAbsent
- computeIfAbsent
- computeIfPresent
- compute
- merge