Java 的容器是 Java 语言中很重要的一部分,平常写代码会大量用到各类容器。Java 中的容器有一个庞大的体系,纠缠于细节很难全面掌握。这篇文章就总览一下 Java 的容器,而后再深刻到细节中学习。java
Java 中的容器主要分为两部分,Collection 和 Map 两种。Collection 主要用于存储单个的元素。而 Map 则主要是存储键值对。算法
本文基于 JDK1.8编程
上图中圆圈表明接口, 长方形表明类,包括抽象类和普通类。绿色表明线程安全,黄色表明不是线程安全。上面的类图中只包括了 java.util
下的类,java.util.concurrent
下面的容器类从功能的角度上来讲并无太大不一样,可是这个包下的类都是线程安全的。数组
从类图中能够看到 Collection 继承了 Iterator 接口,说明全部的 Collection 均可以经过迭代器来进行访问。安全
Collection 接口有三个子接口,List
、Set
和 Queue
。List 会按照元素的插入顺序保存元素,Set 中的元素都不能重复。Collection 中定义了一些公共的方法,都是一些基础的工具方法,好比获取容器的大小、判断容器时候为空、清空容器、迭代容器元素等方法。在 JDK1.8 之后,在 Collection 接口中加入了 default 方法,这些方法都是用于支持 Java8 的函数式编程。微信
interface Collection<E> extends Iterable<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
default <T> T[] toArray(IntFunction<T[]> generator) {
return toArray(generator.apply(0));
}
boolean add(E e);
boolean remove(Object o);
boolean containsAll(java.util.Collection<?> c);
boolean addAll(java.util.Collection<? extends E> c);
boolean removeAll(java.util.Collection<?> c);
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(java.util.Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
}
复制代码
List
接口下的 ArrayList 平常写代码使用的不少。ArrayList 的部分代码以下。从代码中能够看到,ArrayList 底层的数据结构就是一个数组,并且 ArrayList 实现了 RandomAccess 来支持随机访问。数据结构
class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
transient Object[] elementData;
}
复制代码
ArrayList 与数组的功能很像,可是提供了更多便利的操做。Vector 与 ArrayList 的功能基本一致,可是是线程安全的,Vector 的子类 Stack 一样也是线程安全的,可是这些类基本都不推荐再使用了。若是要使用线程安全的类,java.util.concurrent 中的 CopyOnWriteArrayList
是一种更好的选择。多线程
LinkedList 与 ArrayList 功能也比较相近,从功能的角度上来讲,它们之间最大的区别在于 ArrayList 支持随机访问,而 LinkedList 则不支持。LinkedList 部分代码以下,能够看到 LinkedList 底层使用的是双向链表的数据结构。并且还实现了 Deque 接口,因此除了能够做为列表容器来使用以外,还能够做为队列或者双端队列来使用。并发
class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
}
复制代码
LinkedList 一样在 java.util.concurrent 中提供 LinkedBlockingQueue 和 LinkedBlockingDeque 来实现一样的功能,除了在多线程环境比 LinkedList 更有优点外,功能方面基本没有差异。app
各种 Set
的共同点在于 set 的元素是不重复的,这一特性在一些状况下很是有用,HashSet 是用的最多的 Set 类。如下是 HashSet 的部分代码,比较有意思的是 HashSet 底层是使用 HashMap 实现的,全部的值都存着在 HashMap 的 Key 中,Value 的位置就放一个固定的对象 PRESENT。
class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
private transient HashMap<E, Object> map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
}
复制代码
HashSet 里面的元素是无序的,若是须要让 set 中元素有序,那么就可使用 LinkedHashSet,LinkedHashSet 中经过构造一个双向链表来记录插入顺序。而 TreeSet 则是经过底层的红黑树结构提供了排序顺序的访问方式,具体用哪一种能够看具体的需求。一样 Set 也有线程安全的版本 CopyOnWriteArraySet
。
Queue/Deque
是 Java 中的提供的 队列接口。ArrayQueue 是具体可使用的队列类,能够做为普通队列或则双端队列来使用。可是队列在并发状况使用的更多一点,使用 LinkedBlockingQueue 或者 LinkedBlockingDeque 会是更好的选择。有时候除了顺序队列以外,可能还须要经过优先级来调度的队列,PriorityQueue 就是为这个需求而生的,在并发状况下与之对应的就是 PriorityBlockingQueue。
Map 的类图结构相对来讲就简单不少。全部的 Map 类都继承了 Map 接口。HashMap 是使用的最多的 Map 类,HashMap 也是无序的,和 Set 相似,LinkedHashMap 和 TreeMap 也从不一样的方面保证顺序,LinkedHashMap 经过双向链表来记录插入顺序。TreeMap 则是对其中的元素进行排序,能够按照排序的顺序进行访问。
做为 Map 的典型实现,HashMap 代码结构就复杂的多,HashMap 号称是有着 的访问速度(只是近似,在极端状况下可能退化成
)。这么快速的关键在于哈希函数的实现,哈希函数好的实现能够帮助键值对均匀的分布,从而有
的访问速度,如下是 HashMap 的哈希函数的实现,并且 HashMap 的扩容和处理哈希碰撞等问题的处理也很复杂。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
复制代码
与 Collection 中的结构相似,HashTable 也与 HashMap 功能相似,可是 HashTable 是线程安全的。一样由于 HashTable 实现的方式不如 java.util.concurrent 中提供的性能好,因此不推荐使用 HashTable。在并发状况下推荐使用 ConcurrentHashMap,ConcurrentHashMap 经过分段锁的机制,在并发状况下也能有较好的性能。若是在并发状况下也须要保证 Map 的顺序,那就使用 ConcurrentNavigableMap。
在 java.util 包下有一个 Collections 类,这是一个工具类,里面全部的方法都是静态的,并且类不能被实例化。里面提供了各类方法,能够用来更有效率的操做各种容器对象。
好比对 List 排序:
ArrayList<Integer> list = new ArrayList();
list.add(1);
list.add(4);
list.add(6);
list.add(2);
list.add(8);
Collections.sort(list);
复制代码
固然还能够自定义排序的规则,本身实现一个 Comparator
而后做为参数传入就行了。
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 > o2 ? 1 : 0;
}
});
复制代码
还有开箱即用的二分查找算法:
Collections.binarySearch(list, 2);
复制代码
还能够直接把 list 进行反转:
Collections.reverse(list);
复制代码
还能够把 list 使用洗牌算法打乱:
Collections.shuffle(list);
复制代码
以上只是其中的一部分方法,还有能够交换 list 中的元素,找出 list 中的最小、最大值等方法。
由于 java.util 包下的容器大部分都不是线程安全的,Collections 有一类方法能够把 普通的容器对象转成线程安全的对象:
Collections.synchronizedList(list);
复制代码
对于 Map 和 Set 也有相似的工具方法。
在并发环境下,还能够把一个普通容器对象转化成一个不可变的容器对象,这样在并发环境下也是线程安全的:
Collections.unmodifiableList(list);
复制代码
(完)
关注微信公众号,聊点其余的