集合


java集合类是java数据结构的实现,它允许以各种方式将元素分组,并定义了各种使用这些元素更容易操作的方法。集合类有一个共同特点,就是它们只容纳对象(实际上是对象名,即指向地址的指针)。这一点和数组不同,数组可以容纳对象和简单数据。java中的集合类可以分为两大类: 一类是实现Collection接口;另一类是实现Map接口

Java基本集合类如下图所示:

Collection接口

Collection<E>接口是java集合类的基本接口,扩展了Iterable<E>接口,for each循环可与任何实现了Iterable接口的对象一起工作。Collection接口有两个基本方法:

public interface Collection<E>
{
    boolean add (E element);
    Iterator<E> iterator;
    ......
}

iterator方法用于返回一个实现了Iterator的对象,可以使用这个迭代器对象依次访问集合中的元素。Iterator接口包含3个方法:

public interface Iterator<E>
{
    E next();
    boolean hasNext();
    void remove();
 }

注: next()方法和remove()方法的调用具有依赖性,如果调用remove之前没有调用next将会抛出IllegalStateException异常。

List集合

List<E>接口继承自Collection<E>接口,描述的是一个有序的集合。元素可以添加到容器中某个特定的位置。将对象放置在某个位置上可以采用两种方式:使用整数索引或使用列表迭代器ListIterator。可以通过调用listIterator()方法产生一个指向List开始处的ListIterator, 还可以调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。List接口定义了几个用于随机访问的方法:

void add(int index, E element)
E get(int index)
E remove(int index)
E set(int index, E element)
ListIterator<E> listIterator()
ListIterator<E> listIterator(int index)

ListIterator是一个功能更加强大的, 它继承于Iterator接口,只能用于各种List类型的访问。ListIterator能向List集合中添加对象,还可以逆向遍历,通过nextIndex()previousIndex()定位当前的索引位置,还可以用set()实现对象的修改。

public interface ListIterator<E> extends Iterator<E>
{
    void add(E element)  
    void set(E element)          
    boolean hasPrevious()
    E previous()
    int nextIndex()
    int previousIndex()
}

ArrayList

ArrayList继承自AbstractList,实现了List,RandomAccess,Cloneable,java.io.Serializable这些接口。

  • ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
  • ArrayList 实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆。
  • ArrayList 实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。
  • ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList。

**ArrayList常用方法:**有实现List接口的带有索引的增删改查方法,也有size()isEmpty()等方法。

扩容机制

ArrayList的底层是数组队列,相当于动态数组。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 默认初始容量大小
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空数组(用于空实例)。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

     //用于默认大小空实例的共享空数组实例。
      //我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 保存ArrayList数据的数组
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList 所包含的元素个数
     */
    private int size;

在无参构造中,ArrayList创建对象的时候其实就是创建了一个空数组,长度为0。

     /**
     *默认构造函数,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

在有参构造中,传入的参数是正整数就按照传入的参数来确定创建数组的大小,否则抛出IllegalArgumentException异常。

     /**
     * 带初始容量参数的构造函数。(用户自己指定容量)
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //创建initialCapacity大小的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //创建空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

通过add(E e)方法的调用可以达到扩容的目的,如果在添加的时候原数组是空的,就直接初始化数组长度为10,否则就加1。当需要的长度大于原来数组长度的时候就需要扩容,扩容后,新数组长度为原数组的1.5倍。

    //下面是ArrayList的扩容机制
    //ArrayList的扩容机制提高了性能,如果每次只扩充一个,
    //那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。
    /**
     * 如有必要,增加此ArrayList实例的容量,以确保它至少能容纳元素的数量
     * @param   minCapacity   所需的最小容量
     */
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
   //得到最小扩容量
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              // 获取默认的容量和传入参数的较大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
  //判断是否需要扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            //调用grow方法进行扩容,调用此方法代表已经开始扩容了
            grow(minCapacity);
    }
    /**
     * ArrayList扩容的核心方法。
     */
    private void grow(int minCapacity) {
        // oldCapacity为旧容量,newCapacity为新容量
        int oldCapacity = elementData.length;
        //将oldCapacity 右移一位,其效果相当于oldCapacity /2,
        //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //再检查新容量是否超出了ArrayList所定义的最大容量,
        //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,
        //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。
        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);
    }
    //比较minCapacity和 MAX_ARRAY_SIZE
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

注:int newCapacity = oldCapacity + (oldCapacity >> 1);移位运算符就是在二进制的基础上对数字进行平移。按照平移的方向和填充数字的规则分为三种:<<(左移)、>>(带符号右移)和>>>(无符号右移)。作用:对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源。

ArrayList经典Demo

package list;
import java.util.ArrayList;
import java.util.Iterator;

public class ArrayListDemo {

    public static void main(String[] srgs){
         ArrayList<Integer> arrayList = new ArrayList<Integer>();

         System.out.printf("Before add:arrayList.size() = %d\n",arrayList.size());

         arrayList.add(1);
         arrayList.add(3);
         arrayList.add(5);
         arrayList.add(7);
         arrayList.add(9);
         System.out.printf("After add:arrayList.size() = %d\n",arrayList.size());

         System.out.println("Printing elements of arrayList");
         // 三种遍历方式打印元素
         // 第一种:通过迭代器遍历
         System.out.print("通过迭代器遍历:");
         Iterator<Integer> it = arrayList.iterator();
         while(it.hasNext()){
             System.out.print(it.next() + " ");
         }
         System.out.println();

         // 第二种:通过索引值遍历
         System.out.print("通过索引值遍历:");
         for(int i = 0; i < arrayList.size(); i++){
             System.out.print(arrayList.get(i) + " ");
         }
         System.out.println();

         // 第三种:for循环遍历
         System.out.print("for循环遍历:");
         for(Integer number : arrayList){
             System.out.print(number + " ");
         }

         // toArray用法
         // 第一种方式(最常用)
         Integer[] integer = arrayList.toArray(new Integer[0]);

         // 第二种方式(容易理解)
         Integer[] integer1 = new Integer[arrayList.size()];
         arrayList.toArray(integer1);

         // 抛出异常,java不支持向下转型
         //Integer[] integer2 = new Integer[arrayList.size()];
         //integer2 = arrayList.toArray();
         System.out.println();

         // 在指定位置添加元素
         arrayList.add(2,2);
         // 删除指定位置上的元素
         arrayList.remove(2);    
         // 删除指定元素
         arrayList.remove((Object)3);
         // 判断arrayList是否包含5
         System.out.println("ArrayList contains 5 is: " + arrayList.contains(5));

         // 清空ArrayList
         arrayList.clear();
         // 判断ArrayList是否为空
         System.out.println("ArrayList is empty: " + arrayList.isEmpty());
    }
}

LinkedList

LinkedList是一个实现了List接口和Deque接口的双端链表。 LinkedList底层的链表结构使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有队列的特性; LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchronizedList方法:

List list=Collections.synchronizedList(new LinkedList(...));

LinkedList常用方法:

package list;

import java.util.Iterator;
import java.util.LinkedList;

public class LinkedListDemo {
    public static void main(String[] srgs) {
        //创建存放int类型的linkedList
        LinkedList<Integer> linkedList = new LinkedList<>();
        /************************** linkedList的基本操作 ************************/
        linkedList.addFirst(0); // 添加元素到列表开头
        linkedList.add(1); // 在列表结尾添加元素
        linkedList.add(2, 2); // 在指定位置添加元素
        linkedList.addLast(3); // 添加元素到列表结尾

        System.out.println("LinkedList(直接输出的): " + linkedList);

        System.out.println("getFirst()获得第一个元素: " + linkedList.getFirst()); // 返回此列表的第一个元素
        System.out.println("getLast()获得第最后一个元素: " + linkedList.getLast()); // 返回此列表的最后一个元素
        System.out.println("removeFirst()删除第一个元素并返回: " + linkedList.removeFirst()); // 移除并返回此列表的第一个元素
        System.out.println("removeLast()删除最后一个元素并返回: " + linkedList.removeLast()); // 移除并返回此列表的最后一个元素
        System.out.println("After remove:" + linkedList);
        System.out.println("contains()方法判断列表是否包含1这个元素:" + linkedList.contains(1)); // 判断此列表包含指定元素,如果是,则返回true
        System.out.println("该linkedList的大小 : " + linkedList.size()); // 返回此列表的元素个数

        /************************** 位置访问操作 ************************/
        System.out.println("-----------------------------------------");
        linkedList.set(1, 3); // 将此列表中指定位置的元素替换为指定的元素
        System.out.println("After set(1, 3):" + linkedList);
        System.out.println("get(1)获得指定位置(这里为1)的元素: " + linkedList.get(1)); // 返回此列表中指定位置处的元素

        /************************** Search操作 ************************/
        System.out.println("-----------------------------------------");
        linkedList.add(3);
        System.out.println("indexOf(3): " + linkedList.indexOf(3)); // 返回此列表中首次出现的指定元素的索引
        System.out.println("lastIndexOf(3): " + linkedList.lastIndexOf(3));// 返回此列表中最后出现的指定元素的索引

        /************************** Queue操作 ************************/
        System.out.println("-----------------------------------------");
        System.out.println("peek(): " + linkedList.peek()); // 获取但不移除此列表的头
        System.out.println("element(): " + linkedList.element()); // 获取但不移除此列表的头
        linkedList.poll(); // 获取并移除此列表的头
        System.out.println("After poll():" + linkedList);
        linkedList.remove();
        System.out.println("After remove():" + linkedList); // 获取并移除此列表的头
        linkedList.offer(4);
        System.out.println("After offer(4):" + linkedList); // 将指定元素添加到此列表的末尾

        /************************** Deque操作 ************************/
        System.out.println("-----------------------------------------");
        linkedList.offerFirst(2); // 在此列表的开头插入指定的元素
        System.out.println("After offerFirst(2):" + linkedList);
        linkedList.offerLast(5); // 在此列表末尾插入指定的元素
        System.out.println("After offerLast(5):" + linkedList);
        System.out.println("peekFirst(): " + linkedList.peekFirst()); // 获取但不移除此列表的第一个元素
        System.out.println("peekLast(): " + linkedList.peekLast()); // 获取但不移除此列表的第一个元素
        linkedList.pollFirst(); // 获取并移除此列表的第一个元素
        System.out.println("After pollFirst():" + linkedList);
        linkedList.pollLast(); // 获取并移除此列表的最后一个元素
        System.out.println("After pollLast():" + linkedList);
        linkedList.push(2); // 将元素推入此列表所表示的堆栈(插入到列表的头)
        System.out.println("After push(2):" + linkedList);
        linkedList.pop(); // 从此列表所表示的堆栈处弹出一个元素(获取并移除列表第一个元素)
        System.out.println("After pop():" + linkedList);
        linkedList.add(3);
        linkedList.removeFirstOccurrence(3); // 从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表)
        System.out.println("After removeFirstOccurrence(3):" + linkedList);
        linkedList.removeLastOccurrence(3); // 从此列表中移除最后一次出现的指定元素(从尾部到头部遍历列表)
        System.out.println("After removeFirstOccurrence(3):" + linkedList);

        /************************** 遍历操作 ************************/
        System.out.println("-----------------------------------------");
        linkedList.clear();
        for (int i = 0; i < 100000; i++) {
            linkedList.add(i);
        }
        // 迭代器遍历
        long start = System.currentTimeMillis();
        Iterator<Integer> iterator = linkedList.iterator();
        while (iterator.hasNext()) {
            iterator.next();
        }
        long end = System.currentTimeMillis();
        System.out.println("Iterator:" + (end - start) + " ms");

        // 顺序遍历(随机遍历)
        start = System.currentTimeMillis();
        for (int i = 0; i < linkedList.size(); i++) {
            linkedList.get(i);
        }
        end = System.currentTimeMillis();
        System.out.println("for:" + (end - start) + " ms");

        // 另一种for循环遍历
        start = System.currentTimeMillis();
        for (Integer i : linkedList)
            ;
        end = System.currentTimeMillis();
        System.out.println("for2:" + (end - start) + " ms");

        // 通过pollFirst()或pollLast()来遍历LinkedList
        LinkedList<Integer> temp1 = new LinkedList<>();
        temp1.addAll(linkedList);
        start = System.currentTimeMillis();
        while (temp1.size() != 0) {
            temp1.pollFirst();
        }
        end = System.currentTimeMillis();
        System.out.println("pollFirst()或pollLast():" + (end - start) + " ms");

        // 通过removeFirst()或removeLast()来遍历LinkedList
        LinkedList<Integer> temp2 = new LinkedList<>();
        temp2.addAll(linkedList);
        start = System.currentTimeMillis();
        while (temp2.size() != 0) {
            temp2.removeFirst();
        }
        end = System.currentTimeMillis();
        System.out.println("removeFirst()或removeLast():" + (end - start) + " ms");
    }
}

LinkedList使用带索引的get()方法获取某个特定元素时效率并不高,每次查找元素都要从链表的头部重新开始搜索。LinkedList对象根本不做任何缓存位置信息的操作。get 方法做了微小的优化:如果索引大于size( )/ 2就从列表尾端开始搜索元素 。

Vector

Vector集合其实现90%都和ArrayList一样,其内部都是通过一个容量能够动态增长的数组来实现的。不同点是Vector是线程安全的,因为其内部有很多同步代码块来保证线程安全。但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间,更推荐使用ArrayList。值得一提的是,Vector在进行扩容时新数组的长度为原来数组的2倍。

Set集合

Set<E>接口继承了Collection<E>接口,特点是不允许存储重复的元素,也无法使用普通for循环来遍历集合中的元素。Set集合使用hashCode()equals()方法来存储元素,而当存储自定义数据类型时这两个方法都要重写。

HashSet

HashSet是基于HashMap来实现的,操作很简单,更像是对HashMap做了一次“封装”,而且只使用了HashMap的key来实现各种特性。HashSet实际上是一个HashMap实例,都是一个存放链表的数组。它不保证存储元素的迭代顺序;此类允许使用null元素。HashSet不是线程安全的。
HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个固定对象:

private static final Object PRESENT = new Object();

HashSet常用方法:

boolean add(E e)
boolean remove(Object o)
boolean contains(Object o)
int size()

当向Set中添加对象时,首先调用此对象所在类的hashCode()方法,计算次对象的哈希值,此哈希值决定了此对象在Set中存放的位置;若此位置没有被存储对象则直接存储,若已有对象则通过对象所在类的equals()比较两个对象是否相同,相同则不能被添加。

LinkedHashSet

LinkedHashSet继承于HashSet,又是基于LinkedHashMap来实现的,具有可预测的迭代顺序,此类不是线程安全的。LinkedHashSet底层使用LinkedHashMap来保存所有元素,它继承与HashSet,其所有的方法操作上又与HashSet相同。

TreeSet

TreeSet是一个有序的集合,它的作用是提供有序的Set集合。它继承了AbstractSet抽象类,实现了NavigableSet,Cloneable,Serializable接口。TreeSet是基于TreeMap实现的,TreeSet的元素支持2种排序方式:自然排序或者根据提供的Comparator进行排序。
TreeSet通过实现Comparable接口并重写的compareTo()方法来判断元素是否重复、以及确定元素的顺序。

Map接口

Map<K,V>接口采用映射来存放键值对,将键映射到值的对象,一个映射不能包含重复的键,每个键最多只能映射一个值。Map集合是一个双列集合,数据结构针对键有效,跟值无关。
Map集合常用的方法:

V put(K key,V value)
V get(Object key)
void clear()
V remove(Object key)
boolean containsKey(Object key)
boolean containsValue(Object value)
boolean isEmpty()
int size()

处理映射时的一个难点就是更新映射项,当存放的键值对值为null时用get()方法取出并操作时会返回null,因此会出现一个NullPointerException异常。下面是Map集合为了解决此问题的一些更新映射项的方法:

返回指定键对应的值,否则以给定的值映射给指定的键
default V getOrDefault(Object key,V defaultValue)
如果指定的键尚未与某个值相关联(或映射到null)将其与给定值相关联并返回null,否则返回当前值
default V putIfAbsent(K key,V value)
如果键与一个非null值关联,将函数应用到值并将键与结果关联返回get(key),若结果为空则删除这个键值对。
default V merge ( K key , V value , BiFunction < ? super V , ? super V , ?
extends V > remappingFunctlon )

Map集合有3种映射视图:键集合、值集合以及键值对集。键和键值对可以构成一个集,因为映射中一个键只能有一个副本。

Set<K> keySet()
Collection<V> values()
Set<Map.Entry<K,V>> entrySet()

注:keySet不是HashSet或TreeSet,而是实现了Set接口的另外某个类的对象。Set接口扩展了Collection接口。因此,可以像使用集合一样使用keySet。

HashMap

HashMap是基于哈希表实现了Map<K,V>接口的一个集合容器,并允许null值和null键。HashMap大致相当于Hashtable,除了它不是线程同步的,并允许null。jdk1.8之前HashMap由数组+链表组成,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在。jdk1.8之后在解决哈希冲突时,当链表长度大于阈值threshold(默认为8)时,将链表转化为红黑树(将链表转换成红黑树前会判断当前数组长度,小于64时选择先进行数组扩容,而不是转换为红黑树),以减少搜索时间。
HashMap的构造方法有4个:

    // 默认构造函数。
    public HashMap() 
     // 包含另一个“Map”的构造函数
     public HashMap(Map<? extends K, ? extends V> m)
     // 指定“容量大小”的构造函数
     public HashMap(int initialCapacity)
     // 指定“容量大小”和“加载因子”的构造函数
     public HashMap(int initialCapacity, float loadFactor)

loadFactor加载因子是控制数组存放数据的疏密程度,默认值为0.75。加载因子初始值大可以减少散列表再散列(扩容的次数),但同时会导致散列冲突的可能性变大(耗性能,遍历要操作红黑树),初始值小了,可以减小散列冲突的可能性,但同时扩容的次数可能就会变多。

常用方法:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

当用put()方法往集合中添加元素时,HashMap通过调用hash(key)将key的哈希值与高16位做异或运算,增加了随机性,减少了冲突碰撞的可能性。当集合存储容量超过阈值threshold=capacity*loadFactor且数组长度大于64时,HashMap调用resize()方法对集合进行扩容,扩容后为容量原来的两倍。此时会进行rehash,并且会遍历hash表中所有的元素复制到新的HashMap,是非常耗时的。集合初始化时也会调用resize()方法。

LinkedHashMap

LinkedHashMap继承于HashMap,实现了Map<K,V>接口,非线程同步。底层基于HashMap多了一个双向链表的维护,具有可预测的迭代次序。允许为null。可以设置两种遍历顺序:访问顺序(access-ordered)和插入顺序(insertion-ordered)。访问顺序是LRU(最近最少使用)算法的实现,可扩展成LRUMap使用,否则用处不大。
LinkedHashMap的构造方法有5个:

    // 默认构造函数。
    LinkedHashMap()
    // 指定“容量大小”的构造函数
    LinkedHashMap(int initialCapacity)
     // 指定“容量大小”和“加载因子”的构造函数
    LinkedHashMap(int initialCapacity, float loadFactor)
     // 指定“容量大小”、“加载因子”和访问模式的构造函数
    LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
     // 包含另一个“Map”的构造函数
    LinkedHashMap(Map<? extends K, ? extends V> m)

LinkedHashMap和HashMap的put()、get()方法大致是一样的,put方法在创建结点的时候,构建的是LinkedHashMap.Entry不再是Node。get方法中需判断是否为访问顺序,如果是访问顺序的话,把该结点放到链表最后面。在遍历时,LinkedHashMap重写了Set<Map.Entry<K,V>> entrySet()方法,从内部维护的双链表的表头开始循环输出,所以初始容量对遍历没有影响。

TreeMap

TreeMap基于Map<K,V>实现了NavigableMap接口,是一个有序集合,底层为红黑树。此类不是线程安全的,方法的时间复杂度为log(n),通过使用Comparator或者Comparable来比较key是否相等与排序的问题。
TreeMap的构造方法有4个:

    //使用键的自然顺序构造TreeMap
    TreeMap()
    //构造一个新的TreeMap,按照给定的比较器排序
    TreeMap(Comparator<? super K>)
    //包含一个“Map”的构造函数
    TreeMap(Map<? extends K,? extends V>)
    //构造一个包含使用指定排序的“Map”的TreeMap
    TreeMap(SortedMap<K,? extends V>)

在实例化一个TreeMap时,如果在构造方法中传递了Comparator对象,那么就会以Comparator对象的方法进行比较。否则,则使用Comparable的compareTo(T o)方法来比较。无论哪种比较方式,key值都不能为null。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值