Java容器系列-Java容器总览

Java 的容器是 Java 语言中很重要的一部分,平常写代码会大量用到各类容器。Java 中的容器有一个庞大的体系,纠缠于细节很难全面掌握。这篇文章就总览一下 Java 的容器,而后再深刻到细节中学习。java

Java 中的容器主要分为两部分,CollectionMap 两种。Collection 主要用于存储单个的元素。而 Map 则主要是存储键值对。算法

本文基于 JDK1.8编程

Collection

上图中圆圈表明接口, 长方形表明,包括抽象类和普通类。绿色表明线程安全,黄色表明不是线程安全。上面的类图中只包括了 java.util 下的类,java.util.concurrent 下面的容器类从功能的角度上来讲并无太大不一样,可是这个包下的类都是线程安全的。数组

从类图中能够看到 Collection 继承了 Iterator 接口,说明全部的 Collection 均可以经过迭代器来进行访问。安全

Collection 接口有三个子接口,ListSetQueue。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

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 的共同点在于 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

Queue/Deque 是 Java 中的提供的 队列接口。ArrayQueue 是具体可使用的队列类,能够做为普通队列或则双端队列来使用。可是队列在并发状况使用的更多一点,使用 LinkedBlockingQueue 或者 LinkedBlockingDeque 会是更好的选择。有时候除了顺序队列以外,可能还须要经过优先级来调度的队列,PriorityQueue 就是为这个需求而生的,在并发状况下与之对应的就是 PriorityBlockingQueue。

Map

Map 的类图结构相对来讲就简单不少。全部的 Map 类都继承了 Map 接口。HashMap 是使用的最多的 Map 类,HashMap 也是无序的,和 Set 相似,LinkedHashMap 和 TreeMap 也从不一样的方面保证顺序,LinkedHashMap 经过双向链表来记录插入顺序。TreeMap 则是对其中的元素进行排序,能够按照排序的顺序进行访问。

做为 Map 的典型实现,HashMap 代码结构就复杂的多,HashMap 号称是有着 O(1) 的访问速度(只是近似,在极端状况下可能退化成 O(N))。这么快速的关键在于哈希函数的实现,哈希函数好的实现能够帮助键值对均匀的分布,从而有 O(1) 的访问速度,如下是 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。

Collections 工具类

在 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);
复制代码

(完)

原文

关注微信公众号,聊点其余的

相关文章
相关标签/搜索