第二章:Java核心数据结构


文章基于JDK1.8 , MacBookPro CPU 2.9GHz 双核Intel Core i5

List接口

List类族关系,3中主要实现:ArrayList、LinkedList、Vector
在这里插入图片描述
ArrayList和Vector内部使用数组实现,唯一区别是对多线程的支持。ArrayList没有对方法做同步,不是线程安全的,Vector中绝大部分方法都做了线程同步,是一种线程安全的实现。
LinkedList使用了循环双向链表数据结构。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;
    ……
    
  	private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
 }

ArrayList和LinkedList对比

增加元素到列表尾部

ArrayList.add(element)源码
 	/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
      /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
     /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;
 	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(minCapacity);
    }
     /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    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);
    }

当ArrayList对容量的需求超过当前数组大小时,需要进行扩容。扩容时,会进行大量的数组复制操作。

LinkedList.add(element)源码
 	/**
     * Appends the specified element to the end of this list.
     *
     * <p>This method is equivalent to {@link #addLast}.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
     /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

LinkedList使用了链表数据结构,不需要维护容量的大小。从这点上来说,比ArrayList有一定的优势,但是每次元素增加需要新建一个Node对象。

性能对比

-Xms500M -Xmx500M

	public static void main(String[] args) throws  Exception{
        testArrAdd();  //耗时287ms
//        testLinkAdd(); //耗时616ms
    }
    static void testArrAdd(){
        List arrList = new ArrayList();
        Object obj = new Object();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            arrList.add(obj);
        }
        System.out.println("ArrayList add, cost time:" + (System.currentTimeMillis() - start));
    }

    static void testLinkAdd(){
        List linkList = new LinkedList();

        Object obj = new Object();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            linkList.add(obj);
        }
        System.out.println("LinkedList add, cost time:" + (System.currentTimeMillis() - start));

    }

增加元素到列表任意位置

ArrayList是基于数组实现的,数组是一块连续的内存空间,如果在数组任意位置插入元素,必然导致在该位置后的所有元素都需要重新排列,因此效率会相对较低。

ArrayList.add(index,element)源码
	/**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

每次插入操作,都会进行一次数组复制。插入的位置在List中的位置越靠前,数组重组的开销也越大。

LinkedList.add(index,element)源码
	/**
     * Inserts the specified element at the specified position in this list.
     * Shifts the element currently at that position (if any) and any
     * subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
     /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
    /**
     * Inserts element e before non-null Node succ.
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

对于LinkedList来说,在List尾端插入数据何在任意位置插入数据是一样的。

性能对比
	public static void main(String[] args) throws  Exception{
//        testArrAdd(); //耗时785ms
        testLinkAdd(); //耗时10ms
    }
    static void testArrAdd(){
        List arrList = new ArrayList();
        Object obj = new Object();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            arrList.add(0,obj);
        }
        System.out.println("ArrayList add, cost time:" + (System.currentTimeMillis() - start));
    }

    static void testLinkAdd(){
        List linkList = new LinkedList();

        Object obj = new Object();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            linkList.add(0, obj);
        }
        System.out.println("LinkedList add, cost time:" + (System.currentTimeMillis() - start));
    }

删除任意位置元素

ArrayList.remove(index)源码
	/**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

在ArrayList每一次删除操作后,都需要进行数组的重组。删除的元素位置越靠前,数组重组的开销也越大。

LinkedList.remove(index)源码
	/**
     * Removes the element at the specified position in this list.  Shifts any
     * subsequent elements to the left (subtracts one from their indices).
     * Returns the element that was removed from the list.
     *
     * @param index the index of the element to be removed
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
     /**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
    /**
     * Unlinks non-null node x.
     */
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

在LinkedList的实现中,首先通过循环找到要删除的元素。如果要删除的位置处于List的前半段,则从前往后找,若其位置处于后半段,则从后往前找。删除靠前或者靠后比较高效,但是移除List中间元素几乎要遍历半个List。

性能对比
//头部删除
 while (list.size() > 0){
     list.remove(0);
 }
 //中间删除
  while (list.size() > 0){
     arrList.remove(list.size()>>1);
 }
 //尾部删除
 while (list.size() > 0){
   arrList.remove(list.size()-1);
 }
List类型/删除位置头部中间尾部
ArrayList8054145
LinkedList744196

initialCapacity

ArrayList和Vector基于数组的初始化大小。默认大小10,扩容时将数组大小设置为原大小的1.5倍。
ArrayList初始化指定数组大小:

	/**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

在能预估ArrayList数组大小的情况下,通过指定初始容量大小避免扩容对性能有较大提升。

	long start = System.currentTimeMillis();
	List arrList = new ArrayList(10000000);
    Object obj = new Object();
    for (int i = 0; i < 10000000; i++) {
        arrList.add(obj);
    }
    System.out.println("cost time:" + (System.currentTimeMillis() - start));

output:
	指定初始大小时耗时84ms,不指定时耗时293ms

遍历列表

foreach、迭代器、for循环

		List list = new LinkedList();
        Object obj = new Object();
        Object tmp;
        for (int i = 0; i < 10000000; i++) {
            list.add(obj);
        }
        long start = System.currentTimeMillis();
        for (Object o : list) {
            tmp = o;
        }
        System.out.println("foreach cost time:" + (System.currentTimeMillis() - start));

        start = System.currentTimeMillis();
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            tmp = iterator.next();
        }
        System.out.println("iterator cost time:" + (System.currentTimeMillis() - start));

        start = System.currentTimeMillis();
        for (int i = 0; i < list.size();i++){
            tmp = list.get(i);
        }
        System.out.println("fori cost time:" + (System.currentTimeMillis() - start));
Lis类型/遍历类型foreach迭代器for循环
ArrayList16116
LinkedList5854很长

foreach循环综合性能不如迭代器。使用for循环随机访问遍历列表时,ArrayList表现很好,LinkedList长时间未响应(等待时长超过2分钟,具体耗时未测出)

编译后的foreach与iterator几无差异:

  		List list = new LinkedList();
        Object obj = new Object();

        for(int i = 0; i < 10000000; ++i) {
            list.add(obj);
        }

        long start = System.currentTimeMillis();

        Iterator iterator;
        Object o;
        for(iterator = list.iterator(); iterator.hasNext(); o = iterator.next()) {
        }

        System.out.println("foreach cost time:" + (System.currentTimeMillis() - start));
        start = System.currentTimeMillis();

        Object var3;
        for(iterator = list.iterator(); iterator.hasNext(); var3 = iterator.next()) {
        }

        System.out.println("iterator cost time:" + (System.currentTimeMillis() - start));
        start = System.currentTimeMillis();

        for(int i = 0; i < list.size(); ++i) {
            list.get(i);
        }

        System.out.println("fori cost time:" + (System.currentTimeMillis() - start));

Map接口

关于HashMap的原理介绍主要基于JDK1.8。
在这里插入图片描述

HashMap和HashTable区别

  1. HashTable大部分方法做了同步,而HashMap没有,HashMap不是线程安全的。
  2. HashTable不允许key或者value使用null值,HashMap可以
  3. 对key的hash算法和hash值到内存索引的映射算法不同

HashMap实现原理

将key做hash算法,然后将hash值映射到内存地址,直接取得key所对应的数据。底层数据结构使用的是数组,内存地址即数组的下标索引。,数组里的元素可能为null,也有可能是单个对象,还有可能是单向链表或是红黑树(参考下面put源码)。

HashMap的高性能需保证以下几点:

  1. hash算法高效
  2. hash值到内存地址(数组索引)的算法是快速的
  3. 根据内存地址(数组索引)可以直接取得对应的值
 	static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

基本常量

 /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The bin count threshold for using a tree rather than list for a
     * bin.  Bins are converted to trees when adding an element to a
     * bin with at least this many nodes. The value must be greater
     * than 2 and should be at least 8 to mesh with assumptions in
     * tree removal about conversion back to plain bins upon
     * shrinkage.
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * The bin count threshold for untreeifying a (split) bin during a
     * resize operation. Should be less than TREEIFY_THRESHOLD, and at
     * most 6 to mesh with shrinkage detection under removal.
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * The smallest table capacity for which bins may be treeified.
     * (Otherwise the table is resized if too many nodes in a bin.)
     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
     * between resizing and treeification thresholds.
     */
    static final int MIN_TREEIFY_CAPACITY = 64;
    /**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     */
    transient Node<K,V>[] table;
    /**
     * Basic hash bin node, used for most entries.  (See below for
     * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
     */
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

put及hash冲突处理

	public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
        	//1. 放入第一个元素table为空,触发resize方法
            n = (tab = resize()).length;
        /**
        3. 继续放入元素
        i = (n - 1) & hash;//hash是传过来的,其中n是底层数组的长度,用&运算符计算出i的值 
		p = tab[i];//用计算出来的i的值作为下标从数组中元素
		if(p == null){//如果这个元素为null,用key,value构造一个Node对象放入数组下标为i的位置
		     tab[i] = newNode(hash, key, value, null);
		}
        */
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
        	/**
        	4. 放入元素时产生了hash冲突
        	*/
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                    	//5. new一个新的Node对象并把当前Node的next引用指向该对象,也就是说原来该位置上只有一个元素对象,现在转成了单向链表
                        p.next = newNode(hash, key, value, null);
                        //6. 当链表长度到8时,将链表转化为红黑树来处理
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    /**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     *
     * @return the table
     */
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
        	//2. 放入第一个元素触发下面代码,初始化一个长度为16的node数组
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

容量参数

HashMap提供了两个可以指定初始化大小的参数:

public HashMap(int initialCapacity, float loadFactor);
public HashMap(int initialCapacity)

initialCapacity指定了HashMap的初始容量,loadFactor指定了其负载因子。初始容量即数组的大小。HashMap会使用大于等于initialCapacity并且是2的指数幂的最小的整数作为内置数组的大小,负载因子又叫做填充比,是介于0和1之间的浮点数,决定HashMap在扩容之前,内部数组的填充度。默认initialCapacity=16,loadFactor=0.75.
实际使用中,负载因子也可以设置大于1,此时HashMap将产生大量冲突。
HashMap内部还维护了threshold变量,始终被定义为当前数组总容量和负载因子的乘机,表示HashMap的阈值。当HashMap的实际容量超过阈值时,HashMap便会进行扩容。因此,HashMap的实际填充率不会超过负载因子。

HashMap的扩容会操作遍历整个HashMap,设置合理的初始大小和负载因子,有效减少HashMap扩容次数。

LinkedHashMap

使用LinkedHashMap,可以保存元素输入时的顺序。

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
{
   static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
    ……
}

LinkedHashMap继承自HashMap,具备了HashMap的高性能,内部增加了一个链表,用于存放元素的顺序。
LinkedHashMap提供了两种类型的顺序:一是元素插入时的顺序,二是最近访问的顺序。

 /**
     * Constructs an empty <tt>LinkedHashMap</tt> instance with the
     * specified initial capacity, load factor and ordering mode.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @param  accessOrder     the ordering mode - <tt>true</tt> for
     *         access-order, <tt>false</tt> for insertion-order
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

accessOrder为true时,按照元素最后访问时间排序;accessOrder为false时,按照插入顺序排序,默认false。

TreeMap

TreeMap实现了SortedMap接口,可以对元素进行排序。
TreeMap提供的有关排序的接口如下:

    public SortedMap<K,V> subMap(K fromKey, K toKey) {
        return subMap(fromKey, true, toKey, false);
    }
    public SortedMap<K,V> headMap(K toKey) {
        return headMap(toKey, false);
    }
    public SortedMap<K,V> tailMap(K fromKey) {
        return tailMap(fromKey, true);
    }
    K firstKey();
    K lastKey();

TreeMap和LinkedHashMap排序方式对比

LinkedHashMap是基于元素进入集合的顺序或者元素被访问的顺序排序;TreeMap是基于元素的固有顺序(由Comparator或Comparable决定)

确定key排序算法

  1. 在TreeMap的构造函数中注入一个Comparator
 	public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
  1. 使用一个实现了Comparable接口的key

要正常使用TreeMap,一定要通过上述其中一种方式将排序规则传给TreeMap,否则在put操作时,会抛出java.lang.ClassCastException

TreeMap内部实现

内部基于红黑树,红黑树是一种平衡查找树,统计性能优于平衡二叉树。具有良好的最坏运行时间,可以在O(log n)时间内做查找、插入和删除,n表示树中元素的数目。

TreeMap实例

	public static void main(String[] args) throws  Exception{
        Map map = new TreeMap();
        Student s1 = new Student("tom", 65);
        Student s2 = new Student("jack", 80);
        Student s3 = new Student("lucy", 60);
        Student s4 = new Student("bill", 70);
        map.put(s1, new StudentDetailInfo(s1));
        map.put(s2, new StudentDetailInfo(s2));
        map.put(s3, new StudentDetailInfo(s3));
        map.put(s4, new StudentDetailInfo(s4));

        //筛选出成绩介于jack和bill之间的学生
        Map map1 = ((TreeMap) map).subMap(s4, s2);
        for (Iterator iterator = map1.keySet().iterator();iterator.hasNext();){
            Student key = (Student) iterator.next();
            System.out.println(key + "->" + map1.get(key));
        }
        System.out.println("subMap end");

        //找出成绩低于tom的学生
        map1 = ((TreeMap) map).headMap(s1);
        for (Iterator iterator = map1.keySet().iterator();iterator.hasNext();){
            Student key = (Student) iterator.next();
            System.out.println(key + "->" + map1.get(key));
        }
        System.out.println("headMap end");

        //找出成绩大于等于bill的学生
        map1 = ((TreeMap) map).tailMap(s4);
        for (Iterator iterator = map1.keySet().iterator();iterator.hasNext();){
            Student key = (Student) iterator.next();
            System.out.println(key + "->" + map1.get(key));
        }
        System.out.println("tailMap end");
    }

    public static class Student implements Comparable<Student>{
        String name;
        int score;
        public Student(String name, int score){
            this.name = name;
            this.score = score;
        }

        @Override
        public int compareTo(Student o) {
            if(o.score < this.score){
                return 1;
            } else if(o.score > this.score){
                return  -1;
            }
            return 0;
        }

        @Override
        public String toString() {
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append("name:").append(name).append(" ").append("score:").append(score);
            return stringBuffer.toString();
        }
    }

    public static class StudentDetailInfo{
        Student student;
        public StudentDetailInfo(Student student){
            this.student  = student;
        }

        @Override
        public String toString() {
            return student.name + "'s detail info";
        }
    }

Set接口

在这里插入图片描述
Set的实现都是关于Map的封装。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值