黑马程序员技术交流社区

标题: 【上海校区】Java核心(四)你不知道的数据集合 [打印本页]

作者: 不二晨    时间: 2018-11-30 10:37
标题: 【上海校区】Java核心(四)你不知道的数据集合

导读:Map竟然不属于Java集合框架的子集?队列也和List一样属于集合的三大子集之一?更有队列的正确使用姿势,一起来看吧!
Java中的集合通常指的是Collection下的三个集合框架List、Set、Queue和Map集合,Map并不属于Collection的子集,而是和它平行的顶级接口。Collection下的子集的关系如文章开头图片所示。
本文的重点将会围绕: 集合的使用、性能、线程安全、差异性、源码解读等几个方面进行介绍。
本文涉及的知识点,分为两部分:
第一部分,Collection所有子集:
第二部分,Map => Hashtable、HashMap、TreeMap、ConcurrentHashMap。
一、List我们先来看List、Vector、ArrayList、LinkedList,它们之间的继承关系图,如下图:


可以看出Vector、ArrayList、LinkedList,这三者都是实现集合框架中的List,也就是所谓的有序集合,因此具体功能也比较近似,比如都提供按照位置进行定位、添加或者删除的操作,都提供迭代器以遍历其内容等。但因为具体的设计区别,在行为、性能、线程安全等方面,表现又有很大不同。
来看它们的主要方法,如下图:


常用方法:
1.1 VectorVector是Java早期提供的 线程安全的动态数组, 如果不需要线程安全,并不建议选择,毕竟同步是有额外开销的。Vector 内部是使用对象数组来保存数据,可以根据需要自动的增加容量,当数组已满时,会创建新的数组,并拷贝原有数组数据。
看源代码可以知道,我们Vector是通过 synchronized 实现线程安全的:
public synchronized boolean add(E e) {    modCount++;    ensureCapacityHelper(elementCount + 1);    elementData[elementCount++] = e;    return true;}复制代码Vector动态增加容量,源码查看:
private void grow(int minCapacity) {    // overflow-conscious code    int oldCapacity = elementData.length;    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?                                     capacityIncrement : oldCapacity);    if (newCapacity - minCapacity < 0)        newCapacity = minCapacity;    if (newCapacity - MAX_ARRAY_SIZE > 0)        newCapacity = hugeCapacity(minCapacity);    elementData = Arrays.copyOf(elementData, newCapacity);}复制代码capacityIncrement变量是what?答案如下:
public Vector(int initialCapacity, int capacityIncrement) {    super();    if (initialCapacity < 0)        throw new IllegalArgumentException("Illegal Capacity: "+                                           initialCapacity);    this.elementData = new Object[initialCapacity];    this.capacityIncrement = capacityIncrement;}复制代码Vector动态增加容量总结: 由上面的源码可知,如果初始化Vector的时候指定了动态容量扩展大小,就增加指定的动态大小,如果未指定,则扩展一倍的容量。
1.2 ArrayListArrayList 是应用更加广泛的动态数组,它本身不是线程安全的,所以性能要好很多。
ArrayList的使用与Vector类似,但有着不同的动态扩容机制,如下源码:
private void grow(int minCapacity) {    // overflow-conscious code    int oldCapacity = elementData.length;    int newCapacity = oldCapacity + (oldCapacity >> 1);    if (newCapacity - minCapacity < 0)        newCapacity = minCapacity;    if (newCapacity - MAX_ARRAY_SIZE > 0)        newCapacity = hugeCapacity(minCapacity);    // minCapacity is usually close to size, so this is a win:    elementData = Arrays.copyOf(elementData, newCapacity);}复制代码其中“>> 1”是位运算相当于除2,所有ArrayList扩容是动态扩展50%.
1.3 LinkedListLinkedList 顾名思义是 Java 提供的双向链表,所以它不需要像上面两种那样调整容量,它也不是线程安全的,它包含一个非常重要的内部类:Entry。Entry是双向链表节点所对应的数据结构,它包括的属性有:当前节点所包含的值,上一个节点,下一个节点。
1.4 Vector、ArrayList、LinkedList区别Vector、ArrayList、LinkedList的区别,可以从以下几个维度进行对比:
1.4.1 底层实现的区别Vector、ArrayList 内部使用数组进行实现,LinkedList 内部使用双向链表实现。
1.4.2 读写性能方面的区别ArrayList 对元素 非末位 的增加和删除都会引起内存分配空间的动态变化,因此非末位的操作速度较慢,但检索速度很快。
LinkedList 基于链表方式存放数据,增加和删除元素的速度较快,但是检索速度较慢。
1.4.3 线程安全方面的区别Vector 使用了synchronized 修饰了操作方法是线程安全的,而 ArrayList、LinkedList 是非线程安全的。
如果需要使用线程安全的List可以使用CopyOnWriteArrayList类。
二、MapHashtable、HashMap、TreeMap 都是最常见的一些 Map 实现,是以键值对的形式存储和操作数据的容器类型。
它们之间的关系,如下图:


HashMap 的性能表现非常依赖于哈希码的有效性,请务必掌握 hashCode 和 equals 的一些基本约定,比如:
线程安全: Hashtable是线程安全的,HashMap和TreeMap是非线程安全的。HashMap可以使用ConcurrentHashMap来保证线程安全。
三、SetSet有两个比较常用的子集:HashSet、TreeSet.
HashSet内部使用的是HashMap实现的,看源代码可知:
public HashSet() {    map = new HashMap<>();}复制代码HashSet也并不是线程安全的,HashSet用于存储无序(存入和取出的顺序不一定相同)元素,值也不能重复。
HashSet可以去除重复的值,如下代码:
public static void main(String[] args) {        Set set = new HashSet();        set.add("orange");        set.add("apple");        set.add("banana");        set.add("grape");        set.add("banana");        System.out.println(set);}复制代码编译器不会报错,执行的结果为:[orange, banana, apple, grape],去掉了重复的“banana”选项。但排序是无序的,如果要实现有序的存储就要使用TreeSet了。
public static void main(String[] args) {    Set set = new TreeSet();    set.add("orange");    set.add("apple");    set.add("banana");    set.add("grape");    set.add("banana");    System.out.println(set);}复制代码输出的结果是:[apple, banana, grape, orange]
同样,我们查看源码发现,TreeSet的底层实现是TreeMap,源码如下:
public TreeSet() {    this(new TreeMap<E,Object>());}复制代码TreeSet也是非线程安全的。
四、QueueQueue(队列)与栈是相对的一种数据结构。只允许在一端进行插入操作,而在另一端进行删除操作的线性表。栈的特点是后进先出,而队列的特点是先进先出。队列的用处很大,但大多都是在其他的数据结构中,比如,树的按层遍历,图的广度优先搜索等都需要使用队列做为辅助数据结构。
Queue的直接子集,如下图:


其中最常用的就是线程安全类:BlockingQueue.
4.1 Queue方法注意:
4.2 Queue使用Queue<String> queue =  new LinkedList<String>();queue.offer("a");queue.offer("b");queue.offer("c");queue.offer("d");System.out.println(queue);queue.poll();System.out.println(queue);queue.poll();queue.poll();queue.poll();System.out.println(queue.peek());// System.out.println(queue.element()); // element 查询失败会抛出异常System.out.println(queue);复制代码4.3 其他队列ArrayBlockingQueue 底层是数组,有界队列,如果我们要使用生产者-消费者模式,这是非常好的选择。
LinkedBlockingQueue 底层是链表,可以当做无界和有界队列来使用,所以大家不要以为它就是无界队列。
SynchronousQueue 本身不带有空间来存储任何元素,使用上可以选择公平模式和非公平模式。
PriorityBlockingQueue 是无界队列,基于数组,数据结构为二叉堆,数组第一个也是树的根节点总是最小值。
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列
五、扩展:String的线程安全关于String、StringBuffer、StringBuilder的线程安全
String是典型的Immutable(不可变)类,被声明为final所有属性也都是final,所有它是不可变的,所有拼加、截取等动作等会产生新的String对象。
StringBuffer是为了解决上面的问题,而诞生的,提供了append方法实现了对字符串的拼加,append方法使用了synchronized实现了线程安全。
StringBuilder是JDK 1.5 新出的特性,作为StringBuffer的性能补充,StringBuffer的append方法使用了synchronized实现了线程的安全,但同时也带来了性能开销,在没有线程安全的情况下可以优先使用StringBuilder。
六、总结List 也就是我们前面介绍最多的有序集合,它提供了方便的访问、插入、删除等操作。
Set 是不允许重复元素的,这是和 List 最明显的区别,也就是不存在两个对象 equals 返回 true。我们在日常开发中有很多需要保证元素唯一性的场合。
Queue/Deque 则是 Java 提供的标准队列结构的实现,除了集合的基本功能,它还支持类似先入先出(FIFO, First-in-First-Out)或者后入先出(LIFO,Last-In-First-Out)等特定行为。这里不包括 BlockingQueue,因为通常是并发编程场合,所以被放置在并发包里。
Map 是广义 Java 集合框架中的另外一部分,Map 接口存储一组键值对象,提供key(键)到value(值)的映射。


【转载】
作者:王磊的博客
链接:https://juejin.im/post/5bfdf39ce51d4538e36240ee




作者: 不二晨    时间: 2018-12-5 16:28

作者: 梦缠绕的时候    时间: 2018-12-5 16:29





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2