Java集合系列

一、JCF集合框架概述

容器,就是可以容纳其他Java对象的对象。Java Collections Framework(JCF) 为Java开发者提供了通用的容器

Java集合主要划分为四个部分:

  Collection(List列表、Set集合)、Map映射、迭代器(Iterator、Enumeration)、工具类(Arrays、Collections)

背景

数组的优势:是一种简单的线性序列,可以快速地访问数组元素,效率高。如果从效率和类型检查的角度讲,数组是最好的。
数组的劣势:不灵活。容量需要事先定义好,不能随着需求的变化而扩容。
由于数组的这些劣势,我们需要一种更强大、更灵活、容量随时可扩大的集合(Collection)来存储对象。(扩容)

基本类型

Java容器里只能放对象,对于基本类型(int, long, float, double等),需要将其包装成对象类型后(Integer, Long, Float, Double等)才能放到容器里。很多时候拆包装和解包装能够自动完成。 这虽然会导致额外的性能和空间开销,但简化了设计和编程。

泛型

所有容器的内部存放的都是Object对象,泛型机制(语法糖,编译器)只是简化了编程,由编译器自动帮我们完成了强制类型转换而已。JDK 1.4以及之前版本不支持泛型,类型转换需要程序员显式完成。

ArrayList list = new ArrayList();//泛型,参数化类型

泛型的通配符(?)

上限限定:public void getFunc(List<? extends Animal> an),那么表示这里的参数可以传入Animal,或者 Animal的子类
下限限定:public void getFunc(Set<? super Animal> an ),那么表示这里的参数可以传入Animal,或者Animal的父类

使用泛型的注意点

  1. 泛型不支持基本数据类型
  2. 泛型不支持继承,必须保持前后一致(比如这样是错误的:List<Object> list = new ArrayList<String>();

内存

由于Java里对象都在堆上,且对象只能通过引用访问,容器里放的其实是对象的引用而不是对象本身

Java集合框架图



List链表

是Collection的子接口,里面的内容允许重复。
List的特点:线性。即:有序。元素放入的顺序和元素的存储顺序保持一致。
表象上:List最大的特点就是有下标。

  • ArrayList: 就是作为一个数组的封装出现的,底层就是数组。
  • LinkedList:底层封装的是一个双向链表。

1、查找、修改用的多的时候用ArrayList。
2、增加、删除用的多的时候(特别是往中间增加、删除)用LinkedList。
3、线程安全,数据量大的时候考虑用vector。
泛型<>:用来控制集合只能操作某一种数据类型。

遍历:1、普通for循环遍历。
   2、使用迭代器Iterator完成遍历。——没有下标从头到尾走一遍,不能操作数组。
   3、for-each循环语句,底层封装的就是迭代器,语法简单,还可以操作数组。推荐使用。

Set集

是collection的子接口,里面的内容不允许重复。
特点:不能放置重复元素,无序存放元素。
表象上:Set没有下标。

  • HashSet判断两元素不重复:
    1、调用equals方法比较两对象
    ​2、两元素的hashcode值保持一致
    只有这两个条件同时满足,java才认为是同一对象。
    所有重写了equals方法一般要重写hashcode方法,让equals返回true的时候,hashcode返回的值应该一样。

hashSet只有增、删、求长度和遍历等操作。
遍历:1、迭代器
​ 2、for-each

Map映射

是存放键值对的接口,所有的元素都以键和值的方式存储。
特点:键值对——键要求唯一,值可以重复
常用子类:HashMap、Properties(专用于操作属性文件)

  • HashMap
    常用操作:增、删、改、查、容、遍历
    遍历的时候:不能同时遍历所有的键和值,只能单独遍历键与值。
    键在遍历的时候用Set类型来接。
    值在遍历的时候用Collection来接。
    hashMap与hashTable比较:1、hashMap非线性安全,hashTable是线性安全的。
    ​ 2、hashMap允许null来做键/值,hashTable不允许。

  • Properties
    常用操作:增、删、查、改、容、存、取。
    存的时候,调用的是.store方法存文件,
    取的时候,调用.load方法取文件。
    文件类型:.properties——固定格式的文本文件,将是工作中使用率第二的配置文件。

  • Map.Entry
    Map.Entry的内部接口,每个Map.Entry对象都保存着一对key——value的内容,每个map接口中都保存有多个Map.Entry接口实例。

Iterator

集合的输出接口,从前到后输出指定集合中的内容。不能遍历Map,只用来遍历Collection。

  • ListIterator
    是Iterator的子接口,可以进行双向输出。专门用来便利List.

  • Enumeration
    是最早的输出接口,用于输出指定集合中的内容。

Collections

集合操作的算法类
包括排序、求最大、最小、反转、随机混排等操作
排序的时候,因为是对对象排序,所以应按一定的业务逻辑的自然顺序排序。

比较器

  • Comparable接口:内部比较器
    可对任意数组排序,java泛型技术,二叉树排序原理
    1、类实现comparable接口
    2、重写compareTo方法
    compareTo方法返回的是int类型数据:1表示大于,-1表示小于,0表示相等。

  • Comparator接口:外部比较器
    补救的做法
    此接口一样需要重写方法,但方法接收两个对象,返回值依然是1、-1、0;

  • SortedSet
    单值的排序接口,实现此接口的集合类,里面的内容可以使用比较器排序。

  • SortedMap
    存放一对值的排序接口,里面内容按照key排序,使用比较器排序。

Queue

队列接口,此接口的子类可以实现队列操作。

二、List基本操作

1. List

Java List重要观点

  • Java List接口是Java Collections Framework的成员。
  • List允许您添加重复元素。
  • List允许您拥有'null'元素。
  • List接口在Java 8中有许多默认方法,例如replaceAll,sort和spliterator。
  • 列表索引从0开始,就像数组一样。
  • List支持泛型(类型的参数化),我们应尽可能使用它。将Generics与List一起使用将在运行时避免ClassCastException。

Java列表类图

Java List接口继承了Collection接口。Collection接口 extends 了Iterable接口。
一些最常用的List实现类是ArrayList,LinkedList,Vector,Stack,CopyOnWriteArrayList。
AbstractList提供了List接口的骨干实现,以减少实现List的工作量。

Java List方法

  • int size():获取列表中元素的数量。
  • boolean isEmpty():检查列表是否为空。
  • boolean contains(Object o):如果此列表包含指定的元素,则返回true。
  • Iterator iterator():以适当的顺序返回此列表中元素的迭代器。
  • Object [] toArray():以适当的顺序返回包含此列表中所有元素的数组
  • boolean add(E e):将指定的元素追加到此列表的末尾。
  • boolean remove(Object o):从此列表中删除指定元素的第一个匹配项。
  • boolean retainAll(Collection <?> c):仅保留此列表中包含在指定集合中的元素。
  • void clear():从列表中删除所有元素。
  • E get(int index):返回列表中指定位置的元素。
  • E set(int index,E element):用指定的元素替换列表中指定位置的元素。
  • ListIterator listIterator():返回列表中元素的列表迭代器。
  • List subList(int fromIndex,int toIndex):返回指定fromIndex(包含)和toIndex(不包括)之间的此列表部分的视图。返回的列表由此列表支持,因此返回列表中的非结构更改将反映在此列表中,反之亦然。

在Java 8中添加到List的一些默认方法是;

  • default void replaceAll(UnaryOperator 运算符):将此列表的每个元素替换为将运算符应用于该元素的结果。
  • default void sort(Comparator c):根据指定的Comparator引发的顺序对此列表进行排序。
  • default Spliterator spliterator():在此列表中的元素上创建Spliterator。

2. ArrayList

ArrayList 结构图

ArrayList基于数组实现,是一个动态的数组链表。但是它和Java中的数组又不一样,它的容量可以自动增长,类似于C语言中动态申请内存,动态增长内存!
ArrayList继承了AbstractList,实现了RandomAccess、Cloneable和Serializable接口!

  • 实现了RandomAccess接口,提供了随机访问功能,实际上就是通过下标序号进行快速访问,因此查找效率高。
  • 实现了Cloneable接口,即覆盖了函数clone(),实现浅拷贝。
  • 实现了Serializable接口,支持序列化,也就意味了ArrayList能够通过序列化传输。

ArrayList 重要特点

  1. 本质实现:Object类型的动态的数组
  2. 线程安全:非同步的。
  3. 列表长度:ArrayList中元素个数用size记录。
  4. 扩展容量:初始化容量 = 10 ,最大容量不会超过 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8!【Integer.MAX_VALUE = 0x7fffffff,换算成二进制: 2^31 - 1,十进制就是 :2147483647,二十一亿多。一些虚拟器需要在数组前加个 头标签,所以减去 8 。 当想要分配比 MAX_ARRAY_SIZE 大的个数就会报 OutOfMemoryError。】当ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量:JDK1.6 int newCapacity = (oldCapacity * 3) /2 + 1; JDK1.8 int newCapacity = oldCapacity + (oldCapacity >> 1); 当容量不够时,调用ensureCapacity(int minCapacity)方法调整列表容量,每次增加元素,都要将原来的元素拷贝到一个新的数组中,使用Arrays.copyOf(elementData, newCapacity)拷贝 ,非常之耗时,也因此建议在事先能确定元素数量的情况下,才使用ArrayList,否则建议使用LinkedList。我们可以主动调用 ensureCapcity 来增加 ArrayList 对象的容量,这样就避免添加元素满了时扩容、挨个复制后移等消耗。
  5. 3种构造方法:1)构造一个默认初始容量为10的空列表; 2) 构造一个指定初始容量的空列表; 3) 构造一个包含指定collection的元素的列表,内部是Arrays.copyOf(elementData, size, Object[].class);。
  6. 5种存储方法:1)set(int index, E element)、2)add(E e)、3)add(int index, E element)、4)addAll(Collection<? extends E> c)、5)addAll(int index, Collection<? extends E> c) 其中3,4,5调用了 System.arraycopy()
  7. 2种删除方法:1) remove(int index) ;2)remove(Object o)
  8. 转换成数组:toArray()方法内部调用Arrays.copyOf(),toArray(T[] a)方法内部如果是部分转换用Arrays.copyOf(),全部转换用 System.arraycopy()
  9. 遍历方法:遍历时 get 的效率要 >= 迭代器。
  10. Fail-Fast机制:ArrayList 不是同步的,所以在并发访问时,如果在迭代器迭代的同时有其他线程修改了 ArrayList, fail-fast 的迭代器 Iterator/ListIterator 会报 ConcurrentModificationException 错。因此我们在并发环境下需要外部给 ArrayList 加个同步锁,或者直接在初始化时用 Collections.synchronizedList 方法进行包装。也可以使用concurrent并发包下的CopyOnWriteArrayList类。快速失败机制通过记录modCount参数来实现。迭代器的快速失败行为应该仅用于检测 bug。
  11. 序列化:ArrayList实现java.io.Serializable的方式。当写入到输出流时,先写入“容量”,再依次写入“每一个元素”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。那为什么ArrayList里面的elementData为什么要用transient来修饰?不是因为ArrayList不能序列化和反序列化,是因为elementData里面不是所有的元素都有数据,因为容量的问题,elementData里面有一些元素是空的,这种是没有必要序列化的。ArrayList的序列化和反序列化依赖writeObject和readObject方法来实现。可以避免序列化空的元素。序列化size大小的元素。
  12. 克隆: ArrayList的本质是维护了一个Object的数组,所以克隆也是通过数组的复制实现的,属于浅复制,调用的是Arrays.copyOf()。如果你想要修改克隆后的集合,那么克隆前的也会被修改。那么就需要使用深复制。通过实现对象类的clone方法。
  13. ArrayList的实现中大量地调用了Arrays.copyof()和System.arraycopy()方法。
  14. ArrayList基于数组实现,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低。
  15. 在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理,ArrayList中允许元素为null。
  16. indexOf和lastIndexOf 查找元素,若元素不存在,则返回-1!
  17. 适合读数据多的场合。

3. LinkedList

LinkedList 结构图

LinkedList是基于链表实现的,从源码可以看出是一个双向链表。除了当做链表使用外,它也可以被当作堆栈、队列或双端队列进行操作

LinkedList不是线程安全的,继承AbstractSequentialList实现List、Deque、Cloneable、Serializable。

  • LinkedList继承AbstractSequentialList,AbstractSequentialList 实现了get(int index)、set(int index, E element)、add(int index, E element) 和 remove(int index)这些函数。这些接口都是随机访问List的。
  • LinkedList 实现 List 接口,能对它进行队列操作。
  • LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
  • LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
  • LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

LinkedList 重要特点

  1. 本质实现:底层使用一个Node数据结构,有前后两个指针,双向链表实现。
  2. 线程安全:非同步的。
  3. 列表长度:LinkedList中元素个数用size记录。
  4. 列表容量:LinkedList是基于链表实现的,因此不存在容量不足的问题,所以这里没有扩容的方法。
  5. 内存:需要更多的内存,LinkedList 每个节点中需要多存储前后节点的信息,占用空间更多些(previous element next)。
  6. 元素允许为null。
  7. Fail-Fast机制:同ArrayList相同。
  8. 遍历方法:所有指定位置的操作都是从头开始遍历进行的。LinkedList是基于链表实现的,因此插入删除效率高,查找效率低(虽然有一个加速动作)。源码中先将index与长度size的一半比较,如果index<size/2,就只从位置0往后遍历到位置index处,而如果index>size/2,就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历,从而提高一定的效率(实际上效率还是很低)。
  9. 它适合删除,插入较多的场景。

4. Vector

Vector 结构图

Vector 类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector 的大小可以根据需要增大或缩小,以适应创建 Vector 后进行添加或移除项的操作。Vector 是同步的,可用于多线程。

  • Vector 继承了AbstractList,实现了List;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能。
  • Vector实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在Vector中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。
  • Vector 实现了Cloneable接口,即实现clone()函数。它能被克隆。
  • Vector 实现Serializable接口,支持序列化。

Vector 重要特点

  1. 本质实现:可增长的对象数组。
  2. 线程安全:同步的,很多方法都加入了synchronized同步语句,来保证线程安全。
  3. 列表长度:elementCount表示实际元素的数量。(Vector 通过 capacity (容量) 和 capacityIncrement (增长数量) 来尽量少的占用空间)
  4. 列表容量:Vector初始化容量是10,扩容默认2倍。capacityIncrement 容量增长系数(向量的大小大于其容量时,容量自动增加的量),ensureCapacity(int minCapacity)调用ensureCapacityHelper(int minCapacity)如果此向量的当前容量小于 minCapacity,则通过将其内部数据数组(保存在字段 elementData 中)替换为一个较大的数组来增加其容量。新数据数组的大小将为原来的大小加上 capacityIncrement,如果容量的增量小于等于零,则每次需要增大容量时,向量的容量将增大一倍(编程原来的两倍),不过,如果此大小仍然小于 minCapacity,则新容量将为 minCapacity
  5. Fail-Fast机制:Vector 的 iterator 和 listIterator 方法所返回的迭代器是快速失败的:如果在迭代器创建后的任意时间从结构上修改了向量(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。
  6. Vector 的 elements 方法返回的 Enumeration 不是 快速失败的。
  7. Vector 主要用在事先不知道数组的大小,或者只是需要一个可以改变大小的数组的情况。
  8. 最好在插入大量元素前增加 vector 容量,那样可以减少重新申请内存的次数。

Array vs Vector

共同点:

  • 都是基于数组
  • 都支持随机访问
  • 默认容量都是 10
  • 都有扩容机制

区别:

  • Vector 出生的比较早,JDK 1.0 就出生了,ArrayList JDK 1.2 才出来
  • Vector 比 ArrayList 多一种迭代器 Enumeration
  • Vector 是线程安全的,ArrayList 不是
  • Vector 默认扩容 2 倍,ArrayList 是 1.5

如果没有线程安全的需求,一般推荐使用 ArrayList,而不是 Vector,因为每次都要获取锁,效率太低。

5. Stack

Stack 结构图

Stack 类表示后进先出(LIFO)的对象堆栈。它通过五个操作对类 Vector 进行了扩展 ,允许将向量视为堆栈。它提供了通常的 push 和 pop 操作,以及取堆栈顶点的 peek 方法、测试堆栈是否为空的 empty 方法、在堆栈中查找项并确定到堆栈顶距离的 search 方法。
因为它继承自Vector,那么它的实现原理是以数组实现堆栈的。如果要以链表方式实现堆栈可以使用LinkedList!(因为)

Stack 重要特点

  1. Stack是栈。它的特性是:先进后出(FILO, First In Last Out)。
  2. Stack实际上也是通过数组去实现的。实际调用的实现方法都是Vector中的方法!
  3. push时(即,将元素推入栈中),是通过将元素追加的数组的末尾中。
  4. peek时(即,取出栈顶元素,不执行删除),是返回数组末尾的元素。
  5. pop时(即,取出栈顶元素,并将该元素从栈中删除),是取出数组末尾的元素,然后将该元素从数组中删除。
  6. 栈最大的长度取决于vector里面数组能有多长。这里vector里面最大能取到Integer.MAX_VALUE。

6.CopyOnWriteArrayList(JUC)

CopyOnWriteArrayList 结构图

CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。添加的时候是需要加锁的.

CopyOnWriteArrayList重要特点

  1. 增删改都需要获得锁,并且锁只有一把,而读操作不需要获得锁,支持并发。(而Vector读也需要加锁,性能差)
  2. 读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。
  3. 应用:CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景。
  4. 缺点:即内存占用问题(写时复制机制->GC->应用响应时间长)和数据一致性问题(只能保证数据的最终一致性,不能保证数据的实时一致性)。
  5. CopyOnWriteArrayList 是一个线程安全的 ArrayList,通过内部的 volatile 数组和显式锁 ReentrantLock 来实现线程安全。
  6. CopyOnWriteArrayList 实现非常简单。内部使用了一个 volatile 数组(array)来存储数据,保证了多线程环境下的可见性。在更新数据时,都会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给 array。正由于这个原因,涉及到数据更新的操作效率很低。

三、Set基本操作

1. Java Set

Java Set 重要观点

  • Java Set接口是Java Collections Framework的成员。
  • Set不允许出现重复元素-----------无重复
  • Set不保证集合中元素的顺序---------无序
  • Set允许包含值为null的元素,但最多只能有一个null元素。
  • Set支持泛型(类型的参数化),我们应尽可能使用它。将Generics与List一起使用将在运行时避免ClassCastException。
  • 先去看Map,Set的实现类都是基于Map来实现的(如,HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的,LinkedHashSet是通过LinkedHashMap来实现的)。

Java Set类图

Java Set接口扩展了Collection接口。Collection接口 extends Iterable接口。
一些最常用的Set实现类是HashSet,LinkedHashSet,TreeSet,SortedSet,CopyOnWriteArraySet。
AbstractSet提供了Set接口的骨干实现,以减少实现List的工作量。

Java Set 方法

 boolean    add(E e) //如果 set 中尚未存在指定的元素,则添加此元素(可选操作)。
 boolean    addAll(Collection<? extends E> c) //如果 set 中没有指定 collection 中的所有元素,则将其添加到此 set 中(可选操作)。
 void      clear() //移除此 set 中的所有元素(可选操作)。
 boolean    contains(Object o) //如果 set 包含指定的元素,则返回 true。
 boolean    containsAll(Collection<?> c) //如果此 set 包含指定 collection 的所有元素,则返回 true。
 boolean    equals(Object o) //比较指定对象与此 set 的相等性。
 int       hashCode() //返回 set 的哈希码值。
 boolean    isEmpty() //如果 set 不包含元素,则返回 true。
 Iterator<E>    iterator() //返回在此 set 中的元素上进行迭代的迭代器。
 boolean    remove(Object o) //如果 set 中存在指定的元素,则将其移除(可选操作)。
 boolean    removeAll(Collection<?> c) //移除 set 中那些包含在指定 collection 中的元素(可选操作)。
 boolean    retainAll(Collection<?> c) //仅保留 set 中那些包含在指定 collection 中的元素(可选操作)。
 int       size() //返回 set 中的元素数(其容量)。
 Object[]   toArray() //返回一个包含 set 中所有元素的数组。
<T> T[]    toArray(T[] a) //返回一个包含此 set 中所有元素的数组;返回数组的运行时类型是指定数组的类型。

2. HashSet

HashSet 结构图

HashSet,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null元素。!
HashSet继承了AbstractSet,实现了Cloneable和Serializable接口!

  • 实现了Cloneable接口,即覆盖了函数clone(),实现浅拷贝。
  • 实现了Serializable接口,支持序列化,能够通过序列化传输。

HashSet 重要特点

  1. 依赖于哈希表(实际上是一个 HashMap 实例)(哈希表+链表+红黑树),不可以存储相同元素(排重)
  2. 底层实现是一个HashMap(保存数据),实现Set接口。(HashSet中含有一个”HashMap类型的成员变量”map,HashSet的操作函数,实际上都是通过map实现的。)
  3. 非同步,线程不安全,存取速度快(同步封装Set s = Collections.synchronizedSet(new HashSet(...));)
  4. 默认初始容量为16。
  5. 加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容
  6. 扩容增量:原容量的 1 倍,如 HashSet的容量为16,一次扩容后是容量为32
  7. 重写hashCode():HashSet集合排重时,需要判断两个对象是否相同,对象相同的判断可以通过hashCode值判断,所以需要重写hashCode()方法
  8. 重写equals():equals()方法是Object类中的方法,表示比较两个对象是否相等,若不重写相当于比较对象的地址, 所以我们可以尝试重写equals方法,检查是否排重。
  9. 会根据hashcode和equals来庞端是否是同一个对象,如果hashcode一样,并且equals返回true,则是同一个对象,不能重复存放。
  10. fail-fast机制:HashSet通过iterator()返回的迭代器是fail-fast的。
  11. 两种遍历方法:Iterator【iterator.next()】,forEach【set.toArray();】
Set<String> set = new HashSet<String>();
set.add("first");
set.add("second");
set.add("three");

// foreach
for (String string : set) {
    System.out.println(string);
}

// iterator
Iterator<String> setIterator = set.iterator();
while (setIterator.hasNext()) {
    String string = (String) setIterator.next();
    System.out.println(string);
}

3. TreeSet

TreeSet 结构图

基于 TreeMap 的 NavigableSet 实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator进行排序,具体取决于使用的构造方法。
  TreeSet也不能存放重复对象,但是TreeSet会自动排序,如果存放的对象不能排序则会报错,所以存放的对象必须指定排序规则。排序规则包括自然排序和客户排序。
  ①自然排序:TreeSet要添加哪个对象就在哪个对象类上面实现java.lang.Comparable接口,并且重写comparaTo()方法,返回0则表示是同一个对象,否则为不同对象。
  ②客户排序:建立一个第三方类并实现java.util.Comparator接口。并重写方法。定义集合形式为TreeSet ts = new TreeSet(new 第三方类());

TreeSet继承了AbstractSet,实现了NavigableSet、Cloneable和Serializable接口!

  • 继承于AbstractSet,AbstractSet实现了equals和hashcode方法。
  • 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。
  • 实现了Cloneable接口,即覆盖了函数clone(),实现浅拷贝。
  • 实现了Serializable接口,支持序列化,能够通过序列化传输。
  • TreeSet是SortedSet接口的实现类

TreeSet 重要特点

  1. 依赖于TreeMap,TreeSet是基于TreeMap实现的。(红黑树)复杂度为O(log (n))
  2. 不可以存储相同元素(排重),自动排序。(有序集合)
  3. TreeSet中不允许使用null元素!在添加的时候如果添加null,则会抛出NullPointerException异常。
  4. TreeSet是非同步的方法【SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));】。
  5. 它的iterator 方法返回的迭代器是fail-fast的。
  6. TreeSet不支持快速随机遍历,只能通过迭代器进行遍历! 两种遍历方法:Iterator【iterator.next()】,forEach【set.toArray();】
  7. TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。
  8. 自然排序,重写compareTo方法:元素所属的类需要实现java.lang.Comparable接口,并重写compareTo方法。 compareTo方法除了可以进行排序外,还有排重的功能,但是必须在compareTo方法中对类中所有的属性值都进行判断,否则不比较那个属性,排重就会忽略哪个属性
  9. 定制排序,重写compare方法:元素需要通过java.util.Comparator接口(比较器)中的compare方法进行比较大小,并排序。 compare方法除了可以进行排序外,还有排重的功能,但是必须在compare方法中对类中所有的属性值都进行判断,否则不比较那个属性,排重就会忽略哪个属性

  ps:Comparable中的compareTo()一个参数, Comparator中compare()两个参数,返回值都是int类型,如果返回0,表示两个比较元素相同,如果大于0 ,前面大于后面,如果小于0,前面小于后面。

HashSet vs TreeSet

  1. HashSet是一个无序的集合,基于HashMap实现;TreeSet是一个有序的集合,基于TreeMap实现。
  2. HashSet集合中允许有null元素,TreeSet集合中不允许有null元素。
  3. HashSet和TreeSet都是非同步!在使用Iterator进行迭代的时候要注意fail-fast。

4. LinkedHashSet

LinkedHashSet 结构图

LinkedHashSet类:LinkedHashSet正好介于HashSet和TreeSet之间,它也是一个hash表,但它同时维护了一个双链表来记录插入的顺序,基本方法的复杂度为O(1)。

当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。

LinkedHashSet 重要特点

  1. 继承自HashSet,与HashSet唯一的区别是LinkedHashSet内部使用的是LinkHashMap((哈希表+链表+红黑树)+双向链表)。
  2. LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
  3. 非同步,线程不安全,存取速度快(同步封装 Set s = Collections.synchronizedSet(new LinkedHashSet(...));)
  4. 其他同HashSet
  5. 维护插入顺序,LinkedHashSet使用LinkedHashMap对象来存储它的元素,插入到LinkedHashSet中的元素实际上是被当作LinkedHashMap的键保存起来的
  6. LinkedHashMap的每一个键值对都是通过内部的静态类Entry<K, V>实例化的。这个 Entry<K, V>类继承了HashMap.Entry类。这个静态类增加了两个成员变量,before和after来维护LinkedHasMap元素的插入顺序。这两个成员变量分别指向前一个和后一个元素,这让LinkedHashMap也有类似双向链表的表现。

5. ConcurrentSkipListSet

ConcurrentSkipListSet 结构图

ConcurrentSkipListSet 重要特点

  1. 一个基于 ConcurrentSkipListMap 的可缩放并发 NavigableSet 实现。set 的元素可以根据它们的自然顺序进行排序,也可以根据创建 set 时所提供的 Comparator 进行排序,具体取决于使用的构造方法。
  2. 此实现为 containsaddremove 操作及其变体提供预期平均 log(n) 时间开销。多个线程可以安全地并发执行插入、移除和访问操作。迭代器是弱一致 的,返回的元素将反映迭代器创建时或创建后某一时刻的 set 状态。它们 抛出 ConcurrentModificationException,可以并发处理其他操作。升序排序视图及其迭代器比降序排序视图及其迭代器更快。
  3. 请注意,与在大多数 collection 中不同,这里的 size 方法不是 一个固定时间 (constant-time) 操作。由于这些 set 的异步特性,确定元素的当前数目需要遍历元素。此外,批量操作 addAllremoveAllretainAllcontainsAll 并不 保证能以原子方式 (atomically) 执行。例如,与 addAll 操作一起并发操作的迭代器只能查看某些附加元素。
  4. 此类不允许使用 null 元素,因为无法可靠地将 null 参数及返回值与不存在的元素区分开来。

6. CopyOnWriteArraySet (JUC)

CopyOnWriteArraySet 结构图

CopyOnWriteArraySet 重要特点

  1. CopyOnWriteArraySet 是线程安全的 Set,它是由 CopyOnWriteArrayList 实现,内部持有一个 CopyOnWriteArrayList 引用,所有的操作都是由 CopyOnWriteArrayList 来实现的,区别就是 CopyOnWriteArraySet 是无序的,并且不允许存放重复值。由于是一个Set,所以也不支持随机索引元素。
  2. 适合元素比较少,并且读取操作高于更新(add/set/remove)操作的场景
  3. 由于每次更新需要复制内部数组,所以更新操作(addsetremove 等等)开销比较大。
  4. 内部的迭代器 iterator 使用了不变的“快照”技术,存储了内部数组快照, 所以它的 iterator 不支持可变remove、set、add操作,但是通过迭代器进行并发读取时效率很高。
  5. 它是线程安全的。

四、Queue基本操作

1. Java Queue

Java Queue 重要观点

  • Java Queue接口是Java Collections Framework的成员。
  • Queue 实现通常不允许插入 null 元素
  • 队列通常(但并非一定)以 FIFO(先进先出)的方式排序各个元素。不过优先级队列和 LIFO 队列(或堆栈)例外,前者根据提供的比较器或元素的自然顺序对元素进行排序,后者按 LIFO(后进先出)的方式对元素进行排序。无论使用哪种排序方式,队列的 都是调用 remove()poll() 所移除的元素。在 FIFO 队列中,所有的新元素都插入队列的末尾。其他种类的队列可能使用不同的元素放置规则。每个 Queue 实现必须指定其顺序属性。
  • 在处理元素前用于保存元素的 collection。除了基本的 Collection 操作外,队列还提供其他的插入、提取和检查操作。每个方法都存在两种形式:一种抛出异常(操作失败时),另一种返回一个特殊值(nullfalse,具体取决于操作)。
  • Queue 接口并未定义阻塞队列的方法,而这在并发编程中是很常见的。BlockingQueue 接口定义了那些等待元素出现或等待队列中有可用空间的方法,这些方法扩展了此接口。
  •  基本操作说明
操作 抛出异常 返回特殊值 说明
插入 add(e) offer(e) 插入一个元素
移除 remove() poll() 移除和返回队列的头
检查 element() peek() 返回但不移除队列的头
  • JDK中并发队列提供了两种实现,一种是高性能队列ConcurrentLinkedQueue,一种是阻塞队列BlockingQueue(7种阻塞队列),两种都继承自Queue。
  • JDK中队列有两大类,一类是双端队列,一类是单端队列。

Java Queue类图

此接口是 Java Collections Framework 的成员。
Java Queue接口扩展了Collection接口。Collection接口 extends Iterable接口。
子接口:BlockingQueue, Deque, BlobkingDequeue
一些最常用的Queue实现类是LinkedList,ArrayBlickingQueue, LinkedBlockingQueue,PriorityQueue, PriorityBlockingQueue。

Java Queue 方法

boolean  add(E e) //将指定的元素插入此队列(如果立即可行且不会违反容量限制),在成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException。
boolean  offer(E e) //将指定的元素插入此队列(如果立即可行且不会违反容量限制),当使用有容量限制的队列时
E        remove() //获取并移除此队列的头。
E        poll() //获取并移除此队列的头,如果此队列为空,则返回 null。
E        element() //获取,但是不移除此队列的头。
E        peek() //获取但不移除此队列的头;如果此队列为空,则返回 null。

2. ConcurrentLinkedQueue接口(JUC)

ConcurrentLinkedQueue 结构图


一个基于链接节点的无界线程安全队列。

ConcurrentLinkedQueue重要特点

  1. 一个基于链接节点的无界线程安全队列。此队列按照 FIFO(先进先出)原则对元素进行排序。队列的头部 是队列中时间最长的元素。队列的尾部是队列中时间最短的元素。新的元素插入到队列的尾部,队列获取操作从队列头部获得元素。当多个线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。
  2. 此队列不允许使用 null 元素。
  3. 此实现采用了有效的“无等待 (wait-free)”算法,该算法基于 Maged M. Michael 和 Michael L. Scott 合著的 Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms 中描述的算法。
  4. 需要小心的是,与大多数 collection 不同,size 方法不是 一个固定时间操作。由于这些队列的异步特性,确定当前元素的数量需要遍历这些元素。
  5. 内存一致性效果:当存在其他并发 collection 时,将对象放入 ConcurrentLinkedQueue 之前的线程中的操作 happen-before 随后通过另一线程从 ConcurrentLinkedQueue 访问或移除该元素的操作。
  6. ConcurrentLinkedQueue中有两个volatile类型的Node节点分别用来存在列表的首尾节点,其中head节点存放链表第一个item为null的节点,tail则并不是总指向最后一个节点。Node节点内部则维护一个变量item用来存放节点的值,next用来存放下一个节点,从而链接为一个单向无界列表。
  7. 开源框架中使用:Tomcat中NioEndPoint中的每个poller里面就维护一个ConcurrentLinkedQueue用来作为缓冲存放任务。
  8. ConcurrentLinkedQueue使用CAS非阻塞算法实现使用CAS解决了当前节点与next节点之间的安全链接和对当前节点值的赋值。【CAS的基本思想是认为当前环境中的并发并没有那么高,比较乐观的看待整个并发,只需要在更新某个值时先检查下该值有没有发生变化,如果没有发生变化则更新,否则放弃更新。CAS的操作其底层是通过调用sun.misc.Unsafe类中的CompareAndSwap的方法保证线程安全的。】由于使用CAS没有使用锁,所以获取size的时候有可能进行offer,poll或者remove操作,导致获取的元素个数不精确,所以在并发情况下size函数不是很有用。另外第一次peek或者first时候会把head指向第一个真正的队列元素。
  9. 线程安全的,volatile + CAS 可知入队出队函数都是操作volatile变量:head,tail。所以要保证队列线程安全只需要保证对这两个Node操作的可见性和原子性,由于volatile本身保证可见性,所以只需要看下多线程下如果保证对着两个变量操作的原子性。对于offer操作是在tail后面添加元素,也就是调用tail.casNext方法,而这个方法是使用的CAS操作,只有一个线程会成功,然后失败的线程会循环一下,重新获取tail,然后执行casNext方法。对于poll也是这样的。

3. Deque接口

Deque结构图

  一个线性 collection,支持在两端插入和移除元素。名称 deque 是“double ended queue(双端队列)”的缩写,通常读为“deck”。大多数 Deque 实现对于它们能够包含的元素数没有固定限制,但此接口既支持有容量限制的双端队列,也支持没有固定大小限制的双端队列。
  队列:此接口扩展了 Queue 接口。在将双端队列用作队列时,将得到 FIFO(先进先出)行为。将元素添加到双端队列的末尾,从双端队列的开头移除元素。
  堆栈:双端队列也可用作 LIFO(后进先出)堆栈。应优先使用此接口而不是遗留 Stack 类。在将双端队列用作堆栈时,元素被推入双端队列的开头并从双端队列开头弹出。堆栈方法完全等效于 Deque 方法,

4. LinkedList

LinkedList 结构图

LinkedList是基于链表实现的,从源码可以看出是一个双向链表。除了当做链表使用外,它也可以被当作堆栈、队列或双端队列进行操作

LinkedList不是线程安全的,继承AbstractSequentialList实现List、Deque、Cloneable、Serializable。

  • LinkedList继承AbstractSequentialList,AbstractSequentialList 实现了get(int index)、set(int index, E element)、add(int index, E element) 和 remove(int index)这些函数。这些接口都是随机访问List的。
  • LinkedList 实现 List 接口,能对它进行队列操作。
  • LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
  • LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
  • LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

LinkedList 重要特点

  1. 本质实现:底层使用一个Node数据结构,有前后两个指针,双向链表实现。
  2. 线程安全:非同步的。
  3. 列表长度:LinkedList中元素个数用size记录。
  4. 列表容量:LinkedList是基于链表实现的,因此不存在容量不足的问题,所以这里没有扩容的方法。
  5. 内存:需要更多的内存,LinkedList 每个节点中需要多存储前后节点的信息,占用空间更多些(previous element next)。
  6. 元素允许为null。
  7. Fail-Fast机制:同ArrayList相同。
  8. 遍历方法:所有指定位置的操作都是从头开始遍历进行的。LinkedList是基于链表实现的,因此插入删除效率高,查找效率低(虽然有一个加速动作)。源码中先将index与长度size的一半比较,如果index<size/2,就只从位置0往后遍历到位置index处,而如果index>size/2,就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历,从而提高一定的效率(实际上效率还是很低)。Arrays.copyOf() 方法:
  9. 它适合删除,插入较多的场景。

5. ConcurrentLinkedDeque接口(JUC)

ConcurrentLinkedDeque 结构图

并发队列ConcurrentLinkedDeque,这是一个非阻塞,无锁,无界 ,线程安全双端操作的队列。简单说就是ConcurrentLinkedQueue的升级版,在JDK7之后才提供。该队列也不允许空元素,而且size方法并不是常量时间,其需要遍历链表,此时并发修改链表会造成统计size不正确。同样,bulk操作和equal以及toArray方法不保证原子性。

ConcurrentLinkedDeque重要特点

  1. ConcurrentLinkedDeque 是双向链表结构的无界并发队列。从JDK 7开始加入到J.U.C的行列中。
  2. 使用CAS实现并发安全,与 ConcurrentLinkedQueue 的区别是该阻塞队列同时支持FIFOFILO两种操作方式,即可以从队列的头和尾同时操作(插入/删除)。
  3. 适合“多生产,多消费”的场景。
  4. 内存一致性遵循对 ConcurrentLinkedDeque 的插入操作先行发生于(happen-before)访问或移除操作

6. BlockingQueue接口(JUC)

BlockingQueue 结构图

JDK7提供了7个阻塞队列。分别是

  • ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue :一个由链表结构组成的可选有界阻塞队列。如果未指定容量,那么容量将等于 Integer.MAX_VALUE。 2 31-1
  • PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
  • DelayQueue:一个使用优先级队列实现的无界阻塞队列,,只有在延迟期满时才能从中提取元素。
  • SynchronousQueue:一个不存储元素、没有内部容量的阻塞队列。
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞TransferQueue队列。
  • LinkedBlockingDeque:一个由链表结构组成的可选范围双向阻塞队列。如果未指定容量,那么容量将等于 Integer.MAX_VALUE。 2 31-1

BlockingQueue重要特点

  1. 支持两个附加操作的 Queue,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用(阻塞)。
  2. BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(nullfalse,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:
方法、处理方式 抛出异常 返回特殊值 一直阻塞 超时退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
检查方法 element() peek() 不可用 不可用
  1. BlockingQueue 不接受 null 元素。试图 addputoffer 一个 null 元素时,某些实现会抛出 NullPointerExceptionnull 被用作指示 poll 操作失败的警戒值。
  2. BlockingQueue 可以是限定容量的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法无阻塞地 put 附加元素。没有任何内部容量约束的 BlockingQueue 总是报告 Integer.MAX_VALUE 的剩余容量。
  3. BlockingQueue 实现主要用于生产者-使用者队列,BlockingQueue 可以安全地与多个生产者和多个使用者一起使用。但它另外还支持 Collection 接口。因此,举例来说,使用 remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常 会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。
  4. BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。然而,大量的 Collection 操作(addAllcontainsAllretainAllremoveAll没有 必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了 c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。
  5. BlockingQueue 实质上 支持使用任何一种“close”或“shutdown”操作来指示不再添加任何项。这种功能的需求和使用有依赖于实现的倾向。例如,一种常用的策略是:对于生产者,插入特殊的 end-of-streampoison 对象,并根据使用者获取这些对象的时间来对它们进行解释。
  6. 内存一致性效果:当存在其他并发 collection 时,将对象放入 BlockingQueue 之前的线程中的操作 happen-before 随后通过另一线程从 BlockingQueue中访问或移除该元素的操作。

7. ArrayBlockingQueue(JUC)

ArrayBlockingQueue 结构图


ArrayBlockingQueue 重要特点

  1. 一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。队列的头部 是在队列中存在时间最长的元素。队列的尾部 是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。
  2. 这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。
  3. 此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性”。
  4. ArrayBlockingQueue内部有个循环数组items用来存放队列元素,putIndex下标标示入队元素下标,takeIndex是出队下标,count统计队列元素个数,从定义可知道并没有使用volatile修饰,这是因为访问这些变量使用都是在锁块内,并不存在可见性问题。
  5. 有个独占锁lock用来对出入队操作加锁,这导致同时只有一个线程可以访问入队出队,
  6. notEmpty,notFull条件变量用来进行出入队的同步。另外构造函数必须传入队列大小参数,所以为有界队列,默认是Lock为非公平锁。
  7. offer操作:在队尾插入元素,如果队列满则返回false,否者入队返回true。由于在操作共享变量前加了锁【final ReentrantLock lock = this.lock; 获取独占锁 lock.lock();】,所以不存在内存不可见问题,加过锁后获取的共享变量都是从主内存获取的,而不是在CPU缓存或者寄存器里面的值,释放锁后修改的共享变量值会刷新会主内存中。另外这个队列是使用循环数组实现,所以计算下一个元素存放下标时候有些特殊。【i=putIndex; return(++i == items.length) ? 0 : i;】另外insert后调用 notEmpty.signal();是为了激活调用notEmpty.await()阻塞后放入notEmpty条件队列中的线程。
  8. put操作:在队列尾部添加元素,如果队列满则等待队列有空位置插入后返回。【final ReentrantLock lock = this.lock; 获取可被中断锁 lock.lockInterruptibly();】如果队列满了那么当前线程会阻塞,直到出队操作调用了notFull.signal方法激活该线程。
  9. size操作:获取队列元素个数,非常精确,因为计算size时候加了独占锁,其他线程不能入队或者出队或者删除元素
  10. ArrayBlockingQueue通过使用全局独占锁实现同时只能有一个线程进行入队或者出队操作,这个锁的粒度比较大,有点类似在方法上添加synchronized的意味。其中offer,poll操作通过简单的加锁进行入队出队操作,而put,take则使用了条件变量实现如果队列满则等待,如果队列空则等待,然后分别在出队和入队操作中发送信号激活等待线程实现同步。另外相比LinkedBlockingQueue,ArrayBlockingQueue的size操作的结果是精确的,因为计算前加了全局锁。

8. LinkedBlockingQueue(JUC)

LinkedBlockingQueue 结构图


一个由链接节点支持的可选有界队列。

LinkedBlockingQueue 重要特点

  1. 一个基于已链接节点的、范围任意的 blocking queue。此队列按 FIFO(先进先出)排序元素。队列的头部 是在队列中时间最长的元素。队列的尾部 是在队列中时间最短的元素。新元素插入到队列的尾部,并且队列获取操作会获得位于队列头部的元素。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。
  2. 可选的容量范围构造方法参数作为防止队列过度扩展的一种方法。如果未指定容量,则它等于 Integer.MAX_VALUE。除非插入节点会使队列超出容量,否则每次插入后会动态地创建链接节点。
  3. LinkedBlockingQueue中也有两个Node分别用来存放首尾节点,并且里面有个初始值为0的原子变量count用来记录队列元素个数,另外里面有两个ReentrantLock的独占锁,分别用来控制元素入队和出队加锁,其中takeLock用来控制同时只有一个线程可以从队列获取元素,其他线程必须等待,putLock控制同时只能有一个线程可以获取锁去添加元素,其他线程必须等待。另外notEmpty和notFull用来实现入队和出队的同步。 另外由于出入队是两个非公平独占锁,所以可以同时又一个线程入队和一个线程出队,其实这个是个生产者-消费者模型。

9. PriorityBlockingQueue(JUC)

PriorityBlockingQueue 结构图


一个基于优先级堆的无界优先级阻塞队列。

PriorityBlockingQueue 重要特点

  1. 一个基于优先级堆的无界阻塞队列。,它使用与类 PriorityQueue 相同的顺序规则,并且提供了阻塞获取操作。虽然此队列逻辑上是无界的,但是资源被耗尽时试图执行 add 操作也将失败(导致 OutOfMemoryError)。
  2. 此类不允许使用 null 元素。依赖自然顺序的优先级队列也不允许插入不可比较的对象(这样做会导致抛出 ClassCastException)。
  3. 此类及其迭代器可以实现 CollectionIterator 接口的所有可选 方法。iterator() 方法中提供的迭代器并 保证以特定的顺序遍历 PriorityBlockingQueue 的元素。如果需要有序地进行遍历,则应考虑使用 Arrays.sort(pq.toArray())。此外,可以使用方法 drainTo 按优先级顺序移除 全部或部分元素,并将它们放在另一个 collection 中。
  4. 在此类上进行的操作不保证具有同等优先级的元素的顺序。如果需要实施某一排序,那么可以定义自定义类或者比较器,比较器可使用修改键断开主优先级值之间的联系。例如,以下是应用先进先出 (first-in-first-out) 规则断开可比较元素之间联系的一个类。要使用该类,则需要插入一个新的 FIFOEntry(anEntry) 来替换普通的条目对象。
  5. PriorityBlockingQueue内部有个数组queue用来存放队列元素,size用来存放队列元素个数,allocationSpinLockOffset是用来在扩容队列时候做cas的,目的是保证只有一个线程可以进行扩容。
  6. 由于这是一个优先级队列所以有个比较器comparator用来比较元素大小。lock独占锁对象用来控制同时只能有一个线程可以进行入队出队操作。notEmpty条件变量用来实现take方法阻塞模式。这里没有notFull 条件变量是因为这里的put操作是非阻塞的,为啥要设计为非阻塞的是因为这是无界队列。
  7. 最后PriorityQueue q用来搞序列化的。如下构造函数,默认队列容量为11,默认比较器为null;

10. DelayQueue(JUC)

DelayQueue 结构图


一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
DelayQueue队列中每个元素都有个过期时间,并且队列是个优先级队列,当从队列获取元素时候,只有过期元素才会出队列。

DelayQueue 重要特点

  1. Delayed 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部 是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且 poll 将返回 null
  2. 当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于等于 0 的值时,将发生到期。即使无法使用 takepoll 移除未到期的元素,也不会将这些元素作为正常元素对待。例如,size 方法同时返回到期和未到期元素的计数。
  3. 此队列不允许使用 null 元素。
  4. DelayQueue中内部使用的是PriorityQueue存放数据,使用ReentrantLock实现线程同步,可知是阻塞队列。另外队列里面的元素要实现Delayed接口,一个是获取当前剩余时间的接口,一个是元素比较的接口,因为这个是有优先级的队列。

11. SychronousQueue(JUC)

SychronousQueue 结构图

一个不存储元素、没有内部容量的阻塞同步队列。
SynchronousQueue的吞吐量高于LinkedBlockingQueue 和 ArrayBlockingQueue。

SychronousQueue 重要特点

  1. 一个不存储元素、没有内部容量的阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。不能在同步队列上进行 peek,因为仅在试图要移除元素时,该元素才存在;除非另一个线程试图移除某个元素,否则也不能(使用任何方法)插入元素;也不能迭代队列,因为其中没有元素可用于迭代。队列的 是尝试添加到队列中的首个已排队插入线程的元素;如果没有这样的已排队线程,则没有可用于移除的元素并且 poll() 将会返回 null。对于其他 Collection 方法(例如 contains),SynchronousQueue 作为一个空 collection。
  2. 此队列不允许 null 元素。
  3. 同步队列类似于 CSP 和 Ada 中使用的 简单聚集(rendezvous)机制信道。它非常适合于传递性设计,在这种设计中,在一个线程中运行的对象要将某些信息、事件或任务传递给在另一个线程中运行的对象,它就必须与该对象同步。
  4. 对于正在等待的生产者和使用者线程而言,此类支持可选的公平排序策略。默认情况下不保证这种排序。但是,使用公平设置为 true 所构造的队列可保证线程以 FIFO 的顺序进行访问。
  5. SynchronousQueue的吞吐量高于LinkedBlockingQueue 和 ArrayBlockingQueue。

12. LinkedTransferQueue(JUC)

LinkedTransferQueue 结构图

LinkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。

LinkedTransferQueue 重要特点

  1. LinkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。
  2. 相对于其他阻塞队列LinkedTransferQueue多了tryTransfer和transfer方法。
  3. transfer方法。如果当前有消费者正在等待接收元素(消费者使用take()方法或带时间限制的poll()方法时),transfer方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。
  4. tryTransfer方法。则是用来试探下生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回false。和transfer方法的区别是tryTransfer方法无论消费者是否接收,方法立即返回。而transfer方法是必须等到消费者消费了才返回。
  5. 对于带有时间限制的tryTransfer(E e, long timeout, TimeUnit unit)方法,则是试图把生产者传入的元素直接传给消费者,但是如果没有消费者消费该元素则等待指定的时间再返回,如果超时还没消费元素,则返回false,如果在超时时间内消费了元素,则返回true。

13. LinkedBlockingDeque(JUC)

LinkedBlockingDeque 结构图

一个基于已链接节点的、任选范围的阻塞双端队列。

LinkedBlockingDeque 重要特点

  1. 一个基于已链接节点的、任选范围的阻塞双端队列。
  2. 可选的容量范围构造方法参数是一种防止过度膨胀的方式。如果未指定容量,那么容量将等于 Integer.MAX_VALUE。只要插入元素不会使双端队列超出容量,每次插入后都将动态地创建链接节点。
  3. 大多数操作都以固定时间运行(不计阻塞消耗的时间)。异常包括 removeremoveFirstOccurrenceremoveLastOccurrencecontainsiterator.remove() 以及批量操作,它们均以线性时间运行。

五、Map基本操作

1. Java Map

Java Map 重要观点

  • Java Map接口是Java Collections Framework的成员。但是它不是Collection
  • 将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。(不同的键对应的值可以相等)
  • Map 接口提供三种collection 视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。
  • Map中某些映射实现可明确保证其自然顺序和定制顺序,如 TreeMap 类;另一些映射实现则不保证任何顺序,如 HashMap 类;还有些类保证添加顺序。
  • 某些映射实现对可能包含的键和值有所限制。例如,某些实现禁止 null 键和值,另一些则对其键的类型有限制。

Java Map类图

一些最常用的Map实现类是HashMap,LinkedHashMap,TreeMap,SortedMap,HashTable,WeakedHashMap。
Set的实现类都是基于Map来实现的(如,HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的,LinkedHashSet是通过LinkedHashMap来实现的)。

Java Map 方法

 void                   clear() //从此映射中移除所有映射关系(可选操作)。
 boolean                containsKey(Object key) //如果此映射包含指定键的映射关系,则返回 true。
 boolean                containsValue(Object value) //如果此映射将一个或多个键映射到指定值,则返回 true。
 Set<Map.Entry<K,V>>    entrySet() //返回此映射中包含的映射关系的 Set 视图。
 boolean                equals(Object o) //比较指定的对象与此映射是否相等。
 V                      get(Object key) //返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。
 int                    hashCode() //返回此映射的哈希码值。
 boolean                isEmpty() //如果此映射未包含键-值映射关系,则返回 true。
 Set<K>                 keySet() //返回此映射中包含的键的 Set 视图。
 V                      put(K key, V value) //将指定的值与此映射中的指定键关联(可选操作)。
 void                   putAll(Map<? extends K,? extends V> m) //从指定映射中将所有映射关系复制到此映射中(可选操作)。
 V                      remove(Object key) //如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
 int                    size() //返回此映射中的键-值映射关系数。
 Collection<V>          values() //返回此映射中包含的值的 Collection 视图。

2. HashMap

HashMap 结构图



在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突, 同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

HashMap 继承了AbstractMap,实现了Map<K,V>、Cloneable和Serializable接口!

  • 实现了Cloneable接口,即覆盖了函数clone(),实现浅拷贝。
  • 实现了Serializable接口,支持序列化,能够通过序列化传输。

HashMap 重要特点

  1. HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。可以存入null键,null值
  2. 底层实现即 (数组 + 单链表 + 红黑树),HashMap的底层是个Node数组(Node<K,V>[] table),在数组的具体索引位置,如果存在多个节点,则可能是以链表或红黑树的形式存在。Node实现了Map.Entry接口,本质上是一个映射(k-v)
  3. DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认初始容量为16,0000 0001 左移4位 0001 0000为16,主干数组的初始容量为16,而且这个数组必须是2的倍数。
  4. MAXIMUM_CAPACITY = 1 << 30;//最大容量为int的最大值除2
  5. DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子为0.75,负载因子可以大于1,即当元素个数超过容量长度的0.75倍时,进行扩容。通过调节负载因子,可使 HashMap 时间和空间复杂度上有不同的表现。当我们调低负载因子时,HashMap 所能容纳的键值对数量变少。扩容时,重新将键值对存储新的桶数组里,键的键之间产生的碰撞会下降,链表长度变短。此时,HashMap 的增删改查等操作的效率将会变高,这里是典型的拿空间换时间。相反,如果增加负载因子(负载因子可以大于1),HashMap 所能容纳的键值对数量变多,空间利用率高,但碰撞率也高。这意味着链表长度变长,效率也随之降低,这种情况是拿时间换空间。
  6. TREEIFY_THRESHOLD = 8; //阈值,在jdk8中,HashMap处理“碰撞”增加了红黑树这种数据结构,如果主干数组上的链表的长度大于8,链表转化为红黑树 。
  7. UNTREEIFY_THRESHOLD = 6; //hash表扩容后,如果发现某一个红黑树的长度小于6,则会重新退化为链表
  8. MIN_TREEIFY_CAPACITY = 64; //当hashmap容量大于64时,链表才能转成红黑树
  9. threshold;//即触发扩容的阈值,临界值=主干数组容量*负载因子
  10. 解决冲突,链地址法(也叫拉链法)。jdk1.7中,当冲突时,在冲突的地址上生成一个链表,将冲突的元素的key,通过equals进行比较,相同即覆盖,不同则添加到链表上,此时如果链表过长,效率就会大大降低,查找和添加操作的时间复杂度都为O(n);但是在jdk1.8中如果链表长度大于8,链表就会转化为红黑树,时间复杂度也降为了O(logn),性能得到了很大的优化。
  11. 非同步,线程不安全,存取速度快(同步封装Map m = Collections.synchronizedMap(new HashMap(...));),在并发场景下使用ConcurrentHashMap来代替。
  12. 扩容增量:原容量的 1 倍,阈值会变为原来的2倍,如 HashSet的容量为16,一次扩容后是容量为32
  13. 扩容机制:1. 计算新桶数组的容量 newCap 和新阈值 newThr;2.根据计算出的 newCap 创建新的桶数组,桶数组 table 也是在这里进行初始化的;3.将键值对节点重新映射到新的桶数组里。如果节点是 TreeNode 类型,则需要拆分红黑树。如果是普通节点,则节点按原顺序进行分组。【重新映射红黑树的逻辑和重新映射链表的逻辑基本一致。不同的地方在于,重新映射后,会将红黑树拆分成两条由 TreeNode 组成的链表。如果链表长度小于 UNTREEIFY_THRESHOLD,则将链表转换成普通链表。否则根据条件重新将 TreeNode 链表树化。红黑树中仍然保留了原链表节点顺序。有了这个前提,再将红黑树转成链表就简单多了,仅需将 TreeNode 链表转成 Node 类型的链表即可。】
  14. HashMap在触发扩容后,阈值会变为原来的2倍,并且会进行重hash,重hash后索引位置index的节点的新分布位置最多只有两个:原索引位置或原索引+oldCap位置。例如capacity为16,索引位置5的节点扩容后,只可能分布在新报索引位置5和索引位置21(5+16)。导致HashMap扩容后,同一个索引位置的节点重hash最多分布在两个位置的根本原因是:1)table的长度始终为2的n次方;2)索引位置的计算方法为“(table.length - 1) & hash”。HashMap扩容是一个比较耗时的操作,定义HashMap时尽量给个接近的初始容量值。【首次put元素需要进行扩容为默认容量16,阈值16*0.75=12,以后扩容后的table大小变为原来的两倍,接下来就是进行扩容后table的调整:假设扩容前的table大小为2的N次方,有上述put方法解析可知,元素的table索引为其hash值的后N位确定那么扩容后的table大小即为2的N+1次方,则其中元素的table索引为其hash值的后N+1位确定,比原来多了一位因此,table中的元素只有两种情况:元素hash值第N+1位为0:不需要进行位置调整;元素hash值第N+1位为1:调整至原索引的两倍位置;扩容或初始化完成后,resize方法返回新的table。】
  15. HashMap在JDK1.8之后不再有死循环的问题,JDK1.8之前存在死循环的根本原因是在扩容后同一索引位置的节点顺序会反掉。JDK1.8 重新映射后,两条链表中的节点顺序并未发生变化,还是保持了扩容前的顺序。
  16. get(key):1.判断表或key是否是null,如果是直接返回null;2.获取key的hash值,计算hash&(table.length-1)得到在链表数组中的位置first=tab[hash&(table.length -1)],判断索引处第一个key与传入key是否相等,如果相等直接返回;3.如果不相等,判断链表是否是红黑二叉树,如果是,直接从树中取值;4.如果不是树,就遍历链表查找。
  17. put(key,value)的过程:1. 当桶数组 table 为空或者null时,否则以默认大小resize();2.根据键值key计算hash值得到插入的数组索引i,如果tab[i]==null,直接新建节点添加,否则判断当前数组中处理hash冲突的方式为链表还是红黑树(check第一个节点类型即可),分别处理;3. 查找要插入的键值对已经存在,存在的话根据条件判断是否用新值替换旧值;4.如果不存在,则将键值对链入链表中,并根据链表长度决定是否将链表转为红黑树;5.判断键值对数量是否大于阈值,大于的话则进行扩容操作
  18. HasMap的扩容机制resize():构造hash表时,如果不指明初始大小,默认大小为16(即Node数组大小16),如果Node[]数组中的元素达到(填充比*Node.length)重新调整HashMap大小 变为原来2倍大小,扩容很耗时
  19. HashMap有threshold属性和loadFactor属性,但是没有capacity属性。初始化时,如果传了初始化容量值,该值是存在threshold变量,并且Node数组是在第一次put时才会进行初始化,初始化时会将此时的threshold值作为新表的capacity值,然后用capacity和loadFactor计算新表的真正threshold值。
  20. 重写计算hash是通过key的hashCode的高16位和低16位异或后和桶的数量取模得到索引位置,即key.hashcode()^(hashcode>>>16)%length,;好处:1.让高位数据与低位数据进行异或,以此加大低位信息的随机性,变相的让高位数据参与到计算中。2. 可以增加 hash 的复杂度,进而影响 hash 的分布性。这也就是为什么 HashMap 不直接使用键对象原始 hash 的原因了。在 Java 中,hashCode 方法产生的 hash 是 int 类型,32 位宽。前16位为高位,后16位为低位,所以要右移16位。
  21. 当同一个索引位置的节点在增加后达到9个时,并且此时数组的长度大于等于64,则会触发链表节点(Node)转红黑树节点(TreeNode,间接继承Node),转成红黑树节点后,其实链表的结构还存在,通过next属性维持。链表节点转红黑树节点的具体方法为源码中的treeifyBin(Node<K,V>[] tab, int hash)方法。而如果数组长度小于64,则不会触发链表转红黑树,而是会进行扩容。
  22. 当同一个索引位置的节点在移除后达到6个时,并且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点。红黑树节点转链表节点的具体方法为源码中的untreeify(HashMap<K,V> map)方法。
  23. 保证键的唯一性,需要覆盖hashCode方法,和equals方法。先写hashCode再写equals 1、如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同;2、如果两个对象的hashCode相同,它们并不一定相同(即用equals比较返回false) 【因为equals()方法只比较两个对象是否相同,相当于==,而不同的对象hashCode()肯定是不同,所以如果我们不是看对象,而只看对象的属性,则要重写这两个方法,如Integer和String他们的equals()方法都是重写过了,都只是比较对象里的内容。使用HashMap,如果key是自定义的类,默认的equal函数的行为可能不能符合我们的要求,就必须重写hashcode()和equals()。】
  24. 序列化:桶数组 table 被申明为 transient。HashMap 并没有使用默认的序列化机制,而是通过实现readObject/writeObject两个方法自定义了序列化的内容。【序列化 talbe 存在着两个问题:1.transient 是表明该数据不参与序列化。因为 HashMap 中的存储数据的数组数据成员中,数组还有很多的空间没有被使用,没有被使用到的空间被序列化没有意义,浪费空间。所以需要手动使用 writeObject() 方法,只序列化实际存储元素的数组。;2.同一个键值对在不同 JVM 下,所处的桶位置可能是不同的,在不同的 JVM 下反序列化 table 可能会发生错误。(HashMap 的get/put/remove等方法第一步就是根据 hash 找到键所在的桶位置,但如果键没有覆写 hashCode 方法,计算 hash 时最终调用 Object 中的 hashCode 方法。但 Object 中的 hashCode 方法是 native 型的,不同的 JVM 下,可能会有不同的实现,产生的 hash 可能也是不一样的。也就是说同一个键在不同平台下可能会产生不同的 hash,此时再对在同一个 table 继续操作,就会出现问题。)】
  25. fail-fast机制:HashSet通过iterator()返回的迭代器是fail-fast的。
  26. 四种遍历方法:map.keySet().iterator();map.entrySet().iterator(); foreach map.keySet(); foreach map.entrySet()
  27. 注意containsKey方法和containsValue方法。前者直接可以通过key的哈希值将搜索范围定位到指定索引对应的链表,而后者要对哈希数组的每个链表进行搜索。

3. TreeMap

TreeMap 结构图

基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
  此实现为 containsKeygetputremove 操作提供受保证的 log(n) 时间开销。  
  TreeMap会自动排序,如果存放的对象不能排序则会报错,所以存放的对象必须指定排序规则。排序规则包括自然排序和客户排序。
  ①自然排序:TreeMap要添加哪个对象就在哪个对象类上面实现java.lang.Comparable接口,并且重写comparaTo()方法,返回0则表示是同一个对象,否则为不同对象。
  ②客户排序:建立一个第三方类并实现java.util.Comparator接口。并重写方法。定义集合形式为TreeMap tm = new TreeMap(new 第三方类());

TreeMap继承了AbstractMap,实现了NavigableMap、Cloneable和Serializable接口!

  • 继承于AbstractMap,AbstractMap实现了equals和hashcode方法。
  • 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。
  • 实现了Cloneable接口,即覆盖了函数clone(),实现浅拷贝。
  • 实现了Serializable接口,支持序列化,能够通过序列化传输。
  • TreeMap是SortedMap接口的实现类

TreeMap 重要特点

  1. 自平衡红黑二叉树,复杂度为O(log (n))
  2. key支持2种排序方式:1 key 要实现Comparable接口 ,2 定制比较器 Comparator
  3. TreeMap是非同步的方法【SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));】。
  4. 它的iterator 方法返回的迭代器是fail-fast的。
  5. TreeMap的查询、插入、删除效率均没有HashMap高,一般只有要对key排序时才使用TreeMap。
  6. TreeMap的key不能为null,而HashMap的key可以为null。
  7. Entry是红黑数的节点,它包含了红黑数的6个基本组成成分:key(键)、value(值)、left(左孩子)、right(右孩子)、parent(父节点)、color(颜色)。
  8. TreeSet不支持快速随机遍历,只能通过迭代器进行遍历! 两种遍历方法:Iterator【map.entrySet().iterator(); map.keySet().iterator();map.values();】,forEach

4. LinkedHashMap

LinkedHashMap 结构图

LinkedHashMap类:LinkedHashMap正好介于HashMap和TreeMap之间,它也是一个hash表,但它同时维护了一个双链表来记录插入的顺序,基本方法的复杂度为O(1)。
当遍历该集合时候,LinkedHashMap将会以元素的添加顺序访问集合的元素。

LinkedHashMap 重要特点

  1. 继承自HashMap,((数组+链表+红黑树)+双向链表)。
  2. LinkedHashMap在迭代访问Map中的全部元素时,性能比HashMapt好,但是插入时性能稍微逊色于HashMap。
  3. 非同步,线程不安全,存取速度快(同步封装Map m = Collections.synchronizedMap(new LinkedHashMap(...));)
  4. 维护插入顺序,从近期访问最少到近期访问最多的顺序(访问顺序)。这种映射很适合构建 LRU 缓存。
  5. 此实现可以让客户避免未指定的、由 HashMap(及 Hashtable)所提供的通常为杂乱无章的排序工作,同时无需增加与 TreeMap 相关的成本。使用它可以生成一个与原来顺序相同的映射副本,而与原映射的实现无关。【Map copy = new LinkedHashMap(m);】
  6. LinkedHashMap的每一个键值对都是通过内部的静态类Entry<K, V>实例化的。这个 Entry<K, V>类继承了HashMap.Entry类。这个静态类增加了两个成员变量,before和after来维护LinkedHasMap元素的插入顺序。这两个成员变量分别指向前一个和后一个元素,这让LinkedHashMap也有类似双向链表的表现。
  7. 它具有HashMap的所有特性,同样允许key和value为null。
  8. LinkedHashMap是如何实现LRU的。首先,当accessOrder为true时,才会开启按访问顺序排序的模式,才能用来实现LRU算法。我们可以看到,无论是put方法还是get方法,都会导致目标Entry成为最近访问的Entry,因此便把该Entry加入到了双向链表的末尾(get方法通过调用recordAccess方法来实现,put方法在覆盖已有key的情况下,也是通过调用recordAccess方法来实现,在插入新的Entry时,则是通过createEntry中的addBefore方法来实现),这样便把最近使用了的Entry放入到了双向链表的后面,多次操作后,双向链表前面的Entry便是最近没有使用的,这样当节点个数满的时候,删除的最前面的Entry(head后面的那个Entry)便是最近最少使用的Entry。

5. HashTable

HashTable 结构图

此类实现一个哈希表,该哈希表将键映射到相应的值。任何非 null 对象都可以用作键或值。
为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。

HashTable 重要特点

  1. 实现一个哈希表(数组+链表),该哈希表将键映射到相应的值。任何非 null 对象都可以用作键或值。初始时已经构建了数据结构是Entry类型的数组,Entry源码和hashmap基本元素用的node基本是一样的
  2. 为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。
  3. 默认加载因子(.75)在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查找某个条目的时间(在大多数 Hashtable 操作中,包括 getput 操作,都反映了这一点)。
  4. 初始容量主要控制空间消耗与执行 rehash 操作所需要的时间损耗之间的平衡。如果初始容量大于 Hashtable 所包含的最大条目数除以加载因子,则永远 不会发生 rehash 操作。但是,将初始容量设置太高可能会浪费空间。如果很多条目要存储在一个 Hashtable 中,那么与根据需要执行自动 rehashing 操作来增大表的容量的做法相比,使用足够大的初始容量创建哈希表或许可以更有效地插入条目。
  5. 由所有类的“collection 视图方法”返回的 collection 的 iterator 方法返回的迭代器都是快速失败
  6. 由 Hashtable 的键和元素方法返回的 Enumeration 是快速失败的。(保留是为了兼容)
  7. 同步的,线程安全的,
  8. HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。
  9. Hashtable中key和value都不允许为null,而HashMap中key和value都允许为null(key只能有一个为null,而value则可以有多个为null)。但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常,这是JDK的规范规定的。
  10. Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。【.关于2n+1的扩展,在hashtable选择用取模的方式来进行,那么尽量使用素数、奇数会让结果更加均匀一些,hashmap用2的幂,主要是其还有一个hash过程即二次hash,不是直接用key的hashcode,这个过程打散了数据总体就是一个减少hash冲突,并且找索引效率还要高,实现都是要考量这两因素的】
  11. Hashtable计算hash值,直接用key的hashCode(),而HashMap重新计算了key的hash值,Hashtable在求hash值对应的位置索引时,用取模运算,而HashMap在求位置索引时,则用与运算,且这里一般先用hash&0x7FFFFFFF后,再对length取模,&0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数,而&0x7FFFFFFF后,只有符号外改变,而后面的位都不变。
  12. hashtable已经算是废弃了,从实现上看,实际hashmap比hashtable改进良多,不管hash方案,还是结构上多红黑树,唯一缺点是非线程安全。但是hashtable的线程安全机制效率是非常差的,现在能找到非常多的替代方案,比如Collections.synchronizedMap,courrenthashmap等
  13. 遍历方法: table.entrySet().iterator();table.keySet().iterator();Enumeration enu = table.keys();

6. WeakedHashMap

WeakedHashMap 结构图

弱键 实现的基于哈希表的 Map。在 WeakHashMap 中,当某个键不再正常使用时,将自动移除其条目。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。丢弃某个键时,其条目从映射中有效地移除,因此,该类的行为与其他的 Map 实现有所不同。

WeakedHashMap 重要特点

  1. WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
  2. WeakHashMap和HashMap都是通过"拉链法"实现的散列表。
  3. modCount是用来实现fail-fast机制的
  4. queue保存的是“已被GC清除”的“弱引用的键”。
  5. WeakHashMap是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap。
  6. 垃圾回收机制通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。在每次get或者put的时候都会调用一个getTable的方法,而getTable里又调用了expungeStaleEntries,清空table中无用键值对。原理如下:新建WeakHashMap,将“键值对”添加到WeakHashMap中。当WeakHashMap中某个“弱引用的key”由于没有再被引用而被GC收回时,在GC回收该“弱键”时,这个“弱键”也同时会被添加到"ReferenceQueue(queue)"中。 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。当我们执行expungeStaleEntries时,就遍历"ReferenceQueue(queue)"中的所有key,然后就在“WeakReference的table”中删除与“ReferenceQueue(queue)中key”对应的键值对。
  7. tomcat在ConcurrentCache是使用ConcurrentHashMap和WeakHashMap做了分代的缓存。在put方法里,在插入一个k-v时,先检查eden缓存的容量是不是超了。没有超就直接放入eden缓存,如果超了则锁定longterm将eden中所有的k-v都放入longterm。再将eden清空并插入k-v。在get方法中,也是优先从eden中找对应的v,如果没有则进入longterm缓存中查找,找到后就加入eden缓存并返回。 经过这样的设计,相对常用的对象都能在eden缓存中找到,不常用(有可能被销毁的对象)的则进入longterm缓存。而longterm的key的实际对象没有其他引用指向它时,gc就会自动回收heap中该弱引用指向的实际对象,弱引用进入引用队列。longterm调用expungeStaleEntries()方法,遍历引用队列中的弱引用,并清除对应的Entry,不会造成内存空间的浪费。

7. EntrySet vs KeySet

遍历

遍历Map,并获取其 <Key, Value> 的方法有两种:
(1)KeySet
(2)EntrySet<KeyType, VlaueType>(性能更好)  
EntrySet速度比KeySet快了两倍多点;

  • hashmap.entryset,在set集合中存放的是entry对象。而在hashmap中的key 和 value 是存放在entry对象里面的;然后用迭代器,遍历set集合,就可以拿到每一个entry对象;得到entry对象就可以直接从entry拿到value了;
  • hashmap.keyset,只是把hashmap中key放到一个set集合中去,还是通过迭代器去遍历,然后再通过 hashmap.get(key)方法拿到value; hashmap.get(key)方法内部调用的是getEntry(key),得到entry,再从entry拿到value;
  • entry.getvalue可以直接拿到value,hashmap.get(key)是先得到Entry对象,再通过entry.getvalue去拿,直白点说就是hashmap.get(key)走了一个弯路,所以它慢一些;
  • keySet()的速度比entrySet()慢了很多,因为对于keySet其实是遍历了2次,一次是转为iterator,一次就从hashmap中取出key所对于的value。而entryset只是遍历了第一次,他把key和value都放到了entry中,所以就快了

  差别在哪里呢? 源码给我们答案了。

public V get(Object key) {
    if (key == null)
    return getForNullKey();
    Entry<K,V> entry = getEntry(key);
    return null == entry ? null : entry.getValue();
}

应用

(1)在需要同时获取Map的<Key, Value>时,EntrySet<KeyType, VlaueType>比KeySet方法要快很多。
(2)如果只需要获取Map的Key,建议使用KeySet方法,因为不需要像EntrySet<KeyType, VlaueType>一样开辟额外的空间存储value值。
(3)如果只需要获取Map的Value,建议使用map.values()方法获取values的集合(Collection)。
(4)由于操作系统内存管理的置换算法(LRU,Least Recently Used,近期最少使用算法),多次遍历速度会逐渐增加(直到寄存器被占满),因为常用数据会从主存被缓存到寄存器中。

底层原理

  keySet()方法返回一个引用,这个引用指向了HashMap的一个内部类KeySet类,此内部类继承了AbstractSet,此内部类初始化迭代器产生一个迭代器对象KeyIterator,它继承了HashIterator迭代器,HashIterator迭代器初始化拿到了next指向map中的第一个元素。当使用keySet集合遍历key时,其实是使用迭代器KeyIterator迭代每个节点的key。
  entrySet()方法同理。

其他

  • 对集合进行的for/in操作,最后会被编译器转化为Iterator操作。但是使用for/in时,Iterator是不可见的,所以如果需要调用Iterator.remove()方法,或其他一些操作, for/in循环就有些力不从心了。
  • Java中不存在foreach关键字,foreach是for/in的简称。
  • for循环比while循环节约内存空间,因为迭代器在for循环中,循环结束,迭代器属于局部变量,循环结束就消失了,while循环中迭代器对象虽然也是局部变量但是要等方法运行完毕才能在内存中消失
  • 当循环次数比较多时,while循环理论上要比for循环要高效,因为for循环比while多一条汇编语句
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class MapDemo {

    public static Map<Integer, String> map;
    static {
        map = new HashMap<Integer, String>();
        for(int i=0;i<1000000;i++) {
            map.put(3*i+1, "China");
            map.put(3*i+2, "America");
            map.put(3*i+3, "Japan");
        }
    }

    public static void main(String[] args) {
        System.out.println("map keySet ellipse " + MapKeySetMethod() + " ms");
        System.out.println("map entrySet ellipse " + MapEntrySetMethod() + " ms");
        //为了排除所谓的缓存带来的干扰,这里再多执行几次
        System.out.println("map keySet ellipse " + MapKeySetMethod() + " ms");
        System.out.println("map entrySet ellipse " + MapEntrySetMethod() + " ms");
        System.out.println("map keySet ellipse " + MapKeySetMethod() + " ms");
        System.out.println("map entrySet ellipse " + MapEntrySetMethod() + " ms");
        System.out.println("map keySet ellipse " + MapKeySetMethod() + " ms");
        System.out.println("map entrySet ellipse " + MapEntrySetMethod() + " ms");

        //当只要获取其value的时候可以这么用
        Collection<String> values = map.values();
        for(String str : values) {
            //System.out.println(str);
        }
        //当只要获取其key的时候可以这么用
        Collection<Integer> keys = map.keySet();
        for(Integer key : keys) {
            //System.out.println(key);
        }
    }

    public static long MapKeySetMethod() {
        long startTime = System.currentTimeMillis();
        Set<Integer> keySet =  map.keySet();
        Iterator<Integer> iterator = keySet.iterator();
        while(iterator.hasNext()) {
            Integer key = iterator.next();
            String value = map.get(key);
            //System.out.println(key + " = " + value);
        }
        long endTime = System.currentTimeMillis();
        return endTime-startTime;
    }

    public static long MapEntrySetMethod() {
        long startTime = System.currentTimeMillis();
        Set<Entry<Integer, String>> entrySet = map.entrySet();
        Iterator<Entry<Integer, String>> iterator = entrySet.iterator();
        while(iterator.hasNext()) {
            Entry<Integer, String> entry = iterator.next();
            Integer key = entry.getKey();
            String value = entry.getValue();
            //System.out.println(key + " = " + value);
        }
        long endTime = System.currentTimeMillis();
        return endTime-startTime;
    }
}

8. ConcurrentSkipListMap(JUC)

ConcurrentSkipListMap 结构图

ConcurrentSkipListMap 重要特点

  1. 可缩放的并发 ConcurrentNavigableMap 实现。映射可以根据键的自然顺序进行排序,也可以根据创建映射时所提供的 Comparator 进行排序,具体取决于使用的构造方法。
  2. 此类实现 SkipLists 的并发变体,为 containsKeygetputremove 操作及其变体提供预期平均 log(n) 时间开销。多个线程可以安全地并发执行插入、移除、更新和访问操作。迭代器是弱一致 的,返回的元素将反映迭代器创建时或创建后某一时刻的映射状态。它们 抛出 ConcurrentModificationException,可以并发处理其他操作。升序键排序视图及其迭代器比降序键排序视图及其迭代器更快。
  3. 此类及此类视图中的方法返回的所有 Map.Entry 对,表示他们产生时的映射关系快照。它们 支持 Entry.setValue 方法。(注意,根据所需效果,可以使用 putputIfAbsentreplace 更改关联映射中的映射关系。)
  4. 请注意,与在大多数 collection 中不同,这里的 size 方法不是 一个固定时间 (constant-time) 操作。因为这些映射的异步特性,确定元素的当前数目需要遍历元素。此外,批量操作 putAllequalsclear 并不 保证能以原子方式 (atomically) 执行。例如,与 putAll 操作一起并发操作的迭代器只能查看某些附加元素。

9. ConcurrentHashMap(JUC)

ConcurrentHashMap 结构图

ConcurrentHashMap 重要特点

  1. 支持获取的完全并发和更新的所期望可调整并发的哈希表。此类遵守与 Hashtable 相同的功能规范,并且包括对应于 Hashtable 的每个方法的方法版本。不过,尽管所有操作都是线程安全的,但获取操作 必锁定,并且 支持以某种防止所有访问的方式锁定整个表。此类可以通过程序完全与 Hashtable 进行互操作,这取决于其线程安全,而与其同步细节无关。
  2. 获取操作(包括 get)通常不会受阻塞,因此,可能与更新操作交迭(包括 putremove)。获取会影响最近完成的更新操作的结果。对于一些聚合操作,比如 putAllclear,并发获取可能只影响某些条目的插入和移除。类似地,在创建迭代器/枚举时或自此之后,Iterators 和 Enumerations 返回在某一时间点上影响哈希表状态的元素。它们不会抛出 ConcurrentModificationException。不过,迭代器被设计成每次仅由一个线程使用。
  3. 这允许通过可选的 concurrencyLevel 构造方法参数(默认值为 16)来引导更新操作之间的并发,该参数用作内部调整大小的一个提示。表是在内部进行分区的,试图允许指示无争用并发更新的数量。因为哈希表中的位置基本上是随意的,所以实际的并发将各不相同。理想情况下,应该选择一个尽可能多地容纳并发修改该表的线程的值。使用一个比所需要的值高很多的值可能会浪费空间和时间,而使用一个显然低很多的值可能导致线程争用。对数量级估计过高或估计过低通常都会带来非常显著的影响。当仅有一个线程将执行修改操作,而其他所有线程都只是执行读取操作时,才认为某个值是合适的。此外,重新调整此类或其他任何种类哈希表的大小都是一个相对较慢的操作,因此,在可能的时候,提供构造方法中期望表大小的估计值是一个好主意。

六、工具类Collections和Arrays基本操作

1. Collections

Collections类主要是完成了两个主要功能

  1. 提供了若干简单而又有用的算法,比如排序,二分查找,求最大最小值等等。
  2. 提供对集合进行包装的静态方法。比如把指定的集合包装成线程安全的集合、包装成不可修改的集合、包装成类型安全的集合等。
  3. sort内部调用的是Arrays.sort(a);
  4. Collections.copy( )本身用到了深拷贝
<T> boolean             addAll(Collection<? super T> c, T... elements) //将所有指定元素添加到指定 collection 中。
<T> Queue<T>            asLifoQueue(Deque<T> deque) //以后进先出 (Lifo) Queue 的形式返回某个 Deque 的视图。
<T> int                 binarySearch(List<? extends Comparable<? super T>> list, T key) //使用二分搜索法搜索指定列表,以获得指定对象。
<T> int                 binarySearch(List<? extends T> list, T key, Comparator<? super T> c) //使用二分搜索法搜索指定列表,以获得指定对象。
<E> Collection<E>       checkedCollection(Collection<E> c, Class<E> type) //返回指定 collection 的一个动态类型安全视图。
<E> List<E>             checkedList(List<E> list, Class<E> type) //返回指定列表的一个动态类型安全视图。
<K,V> Map<K,V>          checkedMap(Map<K,V> m, Class<K> keyType, Class<V> valueType) //返回指定映射的一个动态类型安全视图。
<E> Set<E>              checkedSet(Set<E> s, Class<E> type) //返回指定 set 的一个动态类型安全视图。
<K,V> SortedMap<K,V>    checkedSortedMap(SortedMap<K,V> m, Class<K> keyType, Class<V> valueType) //返回指定有序映射的一个动态类型安全视图。
<E> SortedSet<E>        checkedSortedSet(SortedSet<E> s, Class<E> type) //返回指定有序 set 的一个动态类型安全视图。
<T> void                copy(List<? super T> dest, List<? extends T> src) //将所有元素从一个列表复制到另一个列表。 boolean           disjoint(Collection<?> c1, Collection<?> c2) //如果两个指定 collection 中没有相同的元素,则返回 true。
<T> List<T>             emptyList() //返回空的列表(不可变的)。
<K,V> Map<K,V>          emptyMap() //返回空的映射(不可变的)。
<T> Set<T>              emptySet() //返回空的 set(不可变的)。
<T> Enumeration<T>      enumeration(Collection<T> c) //返回一个指定 collection 上的枚举。
<T> void                fill(List<? super T> list, T obj) //使用指定元素替换指定列表中的所有元素。 
int             frequency(Collection<?> c, Object o) //返回指定 collection 中等于指定对象的元素数。 
int              indexOfSubList(List<?> source, List<?> target) //返回指定源列表中第一次出现指定目标列表的起始位置;如果没有出现这样的列表,则返回 -1。 
int              lastIndexOfSubList(List<?> source, List<?> target) //返回指定源列表中最后一次出现指定目标列表的起始位置;如果没有出现这样的列表,则返回 -1。
<T> ArrayList<T>        list(Enumeration<T> e) //返回一个数组列表,它按返回顺序包含指定枚举返回的元素。
<T extends Object & Comparable<? super T>>  T    max(Collection<? extends T> coll) //根据元素的自然顺序,返回给定 collection 的最大元素。
<T> T                    max(Collection<? extends T> coll, Comparator<? super T> comp) //根据指定比较器产生的顺序,返回给定 collection 的最大元素。
<T extends Object & Comparable<? super T>>  T    min(Collection<? extends T> coll) //根据元素的自然顺序 返回给定 collection 的最小元素。
<T> T                    min(Collection<? extends T> coll, Comparator<? super T> comp) //根据指定比较器产生的顺序,返回给定 collection 的最小元素。
<T> List<T>              nCopies(int n, T o) //返回由指定对象的 n 个副本组成的不可变列表。
<E> Set<E>               newSetFromMap(Map<E,Boolean> map) //返回指定映射支持的 set。
<T> boolean              replaceAll(List<T> list, T oldVal, T newVal) //使用另一个值替换列表中出现的所有某一指定值。 void             reverse(List<?> list) //反转指定列表中元素的顺序。
<T> Comparator<T>        reverseOrder() //返回一个比较器,它强行逆转实现了 Comparable 接口的对象 collection 的自然顺序。
<T> Comparator<T>        reverseOrder(Comparator<T> cmp) //返回一个比较器,它强行逆转指定比较器的顺序。
 void              rotate(List<?> list, int distance) //根据指定的距离轮换指定列表中的元素。 
 void              shuffle(List<?> list) //使用默认随机源对指定列表进行置换。 
 void              shuffle(List<?> list, Random rnd) //使用指定的随机源对指定列表进行置换。
<T> Set<T>               singleton(T o) //返回一个只包含指定对象的不可变 set。
<T> List<T>              singletonList(T o) //返回一个只包含指定对象的不可变列表。
<K,V> Map<K,V>           singletonMap(K key, V value) //返回一个不可变的映射,它只将指定键映射到指定值。
<T extends Comparable<? super T>> void    sort(List<T> list) //根据元素的自然顺序 对指定列表按升序进行排序。
<T> void                sort(List<T> list, Comparator<? super T> c) //根据指定比较器产生的顺序对指定列表进行排序。 void              swap(List<?> list, int i, int j) //在指定列表的指定位置处交换元素。
<T> Collection<T>       synchronizedCollection(Collection<T> c) //返回指定 collection 支持的同步(线程安全的)collection。
<T> List<T>             synchronizedList(List<T> list) //返回指定列表支持的同步(线程安全的)列表。
<K,V> Map<K,V>          synchronizedMap(Map<K,V> m) //返回由指定映射支持的同步(线程安全的)映射。
<T> Set<T>              synchronizedSet(Set<T> s) //返回指定 set 支持的同步(线程安全的)set。
<K,V> SortedMap<K,V>    synchronizedSortedMap(SortedMap<K,V> m) //返回指定有序映射支持的同步(线程安全的)有序映射。
<T> SortedSet<T>        synchronizedSortedSet(SortedSet<T> s) //返回指定有序 set 支持的同步(线程安全的)有序 set。
<T> Collection<T>       unmodifiableCollection(Collection<? extends T> c) //返回指定 collection 的不可修改视图。
<T> List<T>             unmodifiableList(List<? extends T> list) //返回指定列表的不可修改视图。
<K,V> Map<K,V>          unmodifiableMap(Map<? extends K,? extends V> m) //返回指定映射的不可修改视图。
<T> Set<T>              unmodifiableSet(Set<? extends T> s) //返回指定 set 的不可修改视图。
<K,V> SortedMap<K,V>    unmodifiableSortedMap(SortedMap<K,? extends V> m) //返回指定有序映射的不可修改视图。
<T> SortedSet<T>        unmodifiableSortedSet(SortedSet<T> s) //返回指定有序 set 的不可修改视图。

2. Arrays

Arrays的源码大致分为三种:parallel开头的都是并行处理的,deep开头的都是用于数组嵌套相关的操作,另一种就是我们常用的简单操作;

sort方法

  Arrays提供了一系列重载的sort方法,默认都是升序排列的。大体上可以分为两种,
  一种是针对基本数据类型来进行排序,包括int,long,byte,float,double等类型,底层都是通过调用DualPivotQuicksort该类的sort方法来实现的。DualPivotQuicksort这个类是JAVA1.7之后专门提供给Java内部排序使用的专用类,被称为双枢轴快速排序,用来优化原先的快速排序,该算法一般能提供O(nlog(n))的时间复杂度,
  另一种是针对Object数组类型来进行排序sort(Object[] a)。该算法的实现又分为两种,一种通过归并排序算法来实现,另一种通过使用TimSort排序算法来实现。TimSort算法是一种插入与传统归并算法结合的一种算法,是对归并算法的一种优化。至于使用哪一种算法,需要设置系统变量:java.util.Arrays.useLegacyMergeSort,通过设置为true,来使用归并算法,否则使用TimSort算法。默认情况下我们是不会用到归并算法的,并且在JDK文档中有说明,在后续的版本中,legacyMergeSort归并算法会被移除掉。

parallelSort方法

Arrays同样提供了一系列重载的parallelSort方法,用于数字类型的并行排序,同样默认也是升序排列的。这一系列算法是JAVA1.8之后引入的,基于JAVA的并行处理框架fork/join框架,而fork/join框架,是Java1.7引入,目的是为了充分利用多核处理器,编写并行程序,提高程序性能的框架。
  程序里进行了判断,如果数组长度太小,小于并行排序的最小长度或者并行线程池的大小是1,这时候就不再使用并行处理,还是使用DualPivotQuicksort的sort方法来进行排序。

copy方法

  Array.copy() 底层是通过System的native方法arraycopy来实现的,这个方法在操作集合的时候也经常用到。
  System.CopyOf()

  1. 当数组为一维数组,且元素为基本类型或String类型时,属于深复制,即原数组与新数组的元素不会相互影响,String的特殊是因为它的不可变性
  2. 当数组为多维数组,或一维数组中的元素为引用类型时,属于浅复制,原数组与新数组的元素引用指向同一个对象
static <T> List<T>    asList(T... a) //返回一个受指定数组支持的固定大小的列表。
static int    binarySearch(byte[] a, byte key) //使用二分搜索法来搜索指定的 byte 型数组,以获得指定的值。
static int    binarySearch(byte[] a, int fromIndex, int toIndex, byte key) //使用二分搜索法来搜索指定的 byte 型数组的范围,以获得指定的值。
static int    binarySearch(char[] a, char key) //使用二分搜索法来搜索指定的 char 型数组,以获得指定的值。
static int    binarySearch(char[] a, int fromIndex, int toIndex, char key) //使用二分搜索法来搜索指定的 char 型数组的范围,以获得指定的值。
static int    binarySearch(double[] a, double key) //使用二分搜索法来搜索指定的 double 型数组,以获得指定的值。
static int    binarySearch(double[] a, int fromIndex, int toIndex, double key) //使用二分搜索法来搜索指定的 double 型数组的范围,以获得指定的值。
static int    binarySearch(float[] a, float key) //使用二分搜索法来搜索指定的 float 型数组,以获得指定的值。
static int    binarySearch(float[] a, int fromIndex, int toIndex, float key) //使用二分搜索法来搜索指定的 float 型数组的范围,以获得指定的值。
static int    binarySearch(int[] a, int key) //使用二分搜索法来搜索指定的 int 型数组,以获得指定的值。
static int    binarySearch(int[] a, int fromIndex, int toIndex, int key) //使用二分搜索法来搜索指定的 int 型数组的范围,以获得指定的值。
static int    binarySearch(long[] a, int fromIndex, int toIndex, long key) //使用二分搜索法来搜索指定的 long 型数组的范围,以获得指定的值。
static int    binarySearch(long[] a, long key) //使用二分搜索法来搜索指定的 long 型数组,以获得指定的值。
static int    binarySearch(Object[] a, int fromIndex, int toIndex, Object key) //使用二分搜索法来搜索指定数组的范围,以获得指定对象。
static int    binarySearch(Object[] a, Object key) //使用二分搜索法来搜索指定数组,以获得指定对象。
static int    binarySearch(short[] a, int fromIndex, int toIndex, short key) //使用二分搜索法来搜索指定的 short 型数组的范围,以获得指定的值。
static int    binarySearch(short[] a, short key) //使用二分搜索法来搜索指定的 short 型数组,以获得指定的值。
static <T> int    binarySearch(T[] a, int fromIndex, int toIndex, T key, Comparator<? super T> c) //使用二分搜索法来搜索指定数组的范围,以获得指定对象。
static <T> int    binarySearch(T[] a, T key, Comparator<? super T> c) //使用二分搜索法来搜索指定数组,以获得指定对象。
static boolean[]    copyOf(boolean[] original, int newLength) //复制指定的数组,截取或用 false 填充(如有必要),以使副本具有指定的长度。
static byte[]    copyOf(byte[] original, int newLength) //复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。
static char[]    copyOf(char[] original, int newLength) //复制指定的数组,截取或用 null 字符填充(如有必要),以使副本具有指定的长度。
static double[]    copyOf(double[] original, int newLength) //复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。
static float[]    copyOf(float[] original, int newLength) //复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。
static int[]    copyOf(int[] original, int newLength) //复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。
static long[]    copyOf(long[] original, int newLength) //复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。
static short[]    copyOf(short[] original, int newLength) //复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。
static <T> T[]    copyOf(T[] original, int newLength) //复制指定的数组,截取或用 null 填充(如有必要),以使副本具有指定的长度。
static <T,U> T[]    copyOf(U[] original, int newLength, Class<? extends T[]> newType) //复制指定的数组,截取或用 null 填充(如有必要),以使副本具有指定的长度。
static boolean[]    copyOfRange(boolean[] original, int from, int to) //将指定数组的指定范围复制到一个新数组。
static byte[]    copyOfRange(byte[] original, int from, int to) //将指定数组的指定范围复制到一个新数组。
static char[]    copyOfRange(char[] original, int from, int to) //将指定数组的指定范围复制到一个新数组。
static double[]    copyOfRange(double[] original, int from, int to) //将指定数组的指定范围复制到一个新数组。
static float[]    copyOfRange(float[] original, int from, int to) //将指定数组的指定范围复制到一个新数组。
static int[]    copyOfRange(int[] original, int from, int to) //将指定数组的指定范围复制到一个新数组。
static long[]    copyOfRange(long[] original, int from, int to) //将指定数组的指定范围复制到一个新数组。
static short[]    copyOfRange(short[] original, int from, int to) //将指定数组的指定范围复制到一个新数组。
static <T> T[]    copyOfRange(T[] original, int from, int to) //将指定数组的指定范围复制到一个新数组。
static <T,U> T[]    copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) //将指定数组的指定范围复制到一个新数组。
static boolean    deepEquals(Object[] a1, Object[] a2) //如果两个指定数组彼此是深层相等 的,则返回 true。
static int    deepHashCode(Object[] a) //基于指定数组的“深层内容”返回哈希码。
static String    deepToString(Object[] a) //返回指定数组“深层内容”的字符串表示形式。
static boolean    equals(boolean[] a, boolean[] a2) //如果两个指定的 boolean 型数组彼此相等,则返回 true。
static boolean    equals(byte[] a, byte[] a2) //如果两个指定的 byte 型数组彼此相等,则返回 true。
static boolean    equals(char[] a, char[] a2) //如果两个指定的 char 型数组彼此相等,则返回 true。
static boolean    equals(double[] a, double[] a2) //如果两个指定的 double 型数组彼此相等,则返回 true。
static boolean    equals(float[] a, float[] a2) //如果两个指定的 float 型数组彼此相等,则返回 true。
static boolean    equals(int[] a, int[] a2) //如果两个指定的 int 型数组彼此相等,则返回 true。
static boolean    equals(long[] a, long[] a2) //如果两个指定的 long 型数组彼此相等,则返回 true。
static boolean    equals(Object[] a, Object[] a2) //如果两个指定的 Objects 数组彼此相等,则返回 true。
static boolean    equals(short[] a, short[] a2) //如果两个指定的 short 型数组彼此相等,则返回 true。
static void    fill(boolean[] a, boolean val) //将指定的 boolean 值分配给指定 boolean 型数组的每个元素。
static void    fill(boolean[] a, int fromIndex, int toIndex, boolean val) //将指定的 boolean 值分配给指定 boolean 型数组指定范围中的每个元素。
static void    fill(byte[] a, byte val) //将指定的 byte 值分配给指定 byte 节型数组的每个元素。
static void    fill(byte[] a, int fromIndex, int toIndex, byte val) //将指定的 byte 值分配给指定 byte 型数组指定范围中的每个元素。
static void    fill(char[] a, char val) //将指定的 char 值分配给指定 char 型数组的每个元素。
static void    fill(char[] a, int fromIndex, int toIndex, char val) //将指定的 char 值分配给指定 char 型数组指定范围中的每个元素。
static void    fill(double[] a, double val) //将指定的 double 值分配给指定 double 型数组的每个元素。
static void    fill(double[] a, int fromIndex, int toIndex, double val) //将指定的 double 值分配给指定 double 型数组指定范围中的每个元素。
static void    fill(float[] a, float val) //将指定的 float 值分配给指定 float 型数组的每个元素。
static void    fill(float[] a, int fromIndex, int toIndex, float val) //将指定的 float 值分配给指定 float 型数组指定范围中的每个元素。
static void    fill(int[] a, int val) //将指定的 int 值分配给指定 int 型数组的每个元素。
static void    fill(int[] a, int fromIndex, int toIndex, int val) //将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。
static void    fill(long[] a, int fromIndex, int toIndex, long val) //将指定的 long 值分配给指定 long 型数组指定范围中的每个元素。
static void    fill(long[] a, long val) //将指定的 long 值分配给指定 long 型数组的每个元素。
static void    fill(Object[] a, int fromIndex, int toIndex, Object val) //将指定的 Object 引用分配给指定 Object 数组指定范围中的每个元素。
static void    fill(Object[] a, Object val) //将指定的 Object 引用分配给指定 Object 数组的每个元素。
static void    fill(short[] a, int fromIndex, int toIndex, short val) //将指定的 short 值分配给指定 short 型数组指定范围中的每个元素。
static void    fill(short[] a, short val) //将指定的 short 值分配给指定 short 型数组的每个元素。
static int    hashCode(boolean[] a) //基于指定数组的内容返回哈希码。
static int    hashCode(byte[] a) //基于指定数组的内容返回哈希码。
static int    hashCode(char[] a) //基于指定数组的内容返回哈希码。
static int    hashCode(double[] a) //基于指定数组的内容返回哈希码。
static int    hashCode(float[] a) //基于指定数组的内容返回哈希码。
static int    hashCode(int[] a) //基于指定数组的内容返回哈希码。
static int    hashCode(long[] a) //基于指定数组的内容返回哈希码。
static int    hashCode(Object[] a) //基于指定数组的内容返回哈希码。
static int    hashCode(short[] a) //基于指定数组的内容返回哈希码。
static void    sort(byte[] a) //对指定的 byte 型数组按数字升序进行排序。
static void    sort(byte[] a, int fromIndex, int toIndex) //对指定 byte 型数组的指定范围按数字升序进行排序。
static void    sort(char[] a) //对指定的 char 型数组按数字升序进行排序。
static void    sort(char[] a, int fromIndex, int toIndex) //对指定 char 型数组的指定范围按数字升序进行排序。
static void    sort(double[] a) //对指定的 double 型数组按数字升序进行排序。
static void    sort(double[] a, int fromIndex, int toIndex) //对指定 double 型数组的指定范围按数字升序进行排序。
static void    sort(float[] a) //对指定的 float 型数组按数字升序进行排序。
static void    sort(float[] a, int fromIndex, int toIndex) //对指定 float 型数组的指定范围按数字升序进行排序。
static void    sort(int[] a) //对指定的 int 型数组按数字升序进行排序。
static void    sort(int[] a, int fromIndex, int toIndex) //对指定 int 型数组的指定范围按数字升序进行排序。
static void    sort(long[] a) //对指定的 long 型数组按数字升序进行排序。
static void    sort(long[] a, int fromIndex, int toIndex) //对指定 long 型数组的指定范围按数字升序进行排序。
static void    sort(Object[] a) //根据元素的自然顺序对指定对象数组按升序进行排序。
static void    sort(Object[] a, int fromIndex, int toIndex) //根据元素的自然顺序对指定对象数组的指定范围按升序进行排序。
static void    sort(short[] a) //对指定的 short 型数组按数字升序进行排序。
static void    sort(short[] a, int fromIndex, int toIndex) //对指定 short 型数组的指定范围按数字升序进行排序。
static <T> void    sort(T[] a, Comparator<? super T> c) //根据指定比较器产生的顺序对指定对象数组进行排序。
static <T> void    sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c) //根据指定比较器产生的顺序对指定对象数组的指定范围进行排序。
static String    toString(boolean[] a) //返回指定数组内容的字符串表示形式。
static String    toString(byte[] a) //返回指定数组内容的字符串表示形式。
static String    toString(char[] a) //返回指定数组内容的字符串表示形式。
static String    toString(double[] a) //返回指定数组内容的字符串表示形式。
static String    toString(float[] a) //返回指定数组内容的字符串表示形式。
static String    toString(int[] a) //返回指定数组内容的字符串表示形式。
static String    toString(long[] a) //返回指定数组内容的字符串表示形式。
static String    toString(Object[] a) //返回指定数组内容的字符串表示形式。
static String    toString(short[] a) //返回指定数组内容的字符串表示形式。

转载原文

Java 集合系列之一:JCF集合框架概述

posted @ 2022-08-12 22:30  Saltery  阅读(151)  评论(0)    收藏  举报