Java Collection集合源码详解 --- Map

283 阅读6分钟

Java Collection集合源码详解 --- Map

作者:shiwyang

Java版本:1.8

  • 深入了解集合源码与数据结构的关系,了解集合内部不同子类的共性与区别
  • 一共分为三个部分:Collection Map ConcurrentHashMap
  • 这是第二个部分,详细的描述了Map,还介绍了两个工具类,最后是各种类型集合的比较

Map 概述

  • Map(用 key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),"x" 代表 key,"y" 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
  • Map接口的常用实现类:HashMap、Hashtable 、Properties
  • Map中的Key 不允许重复,key相同时,value替换
  • key和value都可以为null
  • Key-Value可以是任何类型,常用String类型作为key

Entry< K - V >: Map的原理就是存放K-V键值对,因此Map提供了一个这个接口,用于存放Map中的每一个键值对元素。

EntrySet: 为了方便遍历(提供了 getKey() getValue()),使用这个EntrySet集合,该集合存放的元素的类型Entry。在EntrySet中,定义的类型是Map.Entry 。

HashMap

  • 线程不安全

HashMap源码

**entrySet方法:**在HashMap中Set<Map.Entry<K,V>>的Entry对象实际上存放的是HashMap&Node,因为Node 是HashMap中Entry的实现子类(impl)(面向对象中多态的概念

在执行entrySet后,本质上Set集合还是一个空的,但是在执行 迭代器 的时候,使用了HashMap内的 nextNode() 方法,这个方法可以从 table表 中获取Node元素,而Node元素本身是Entry的子类,可以向上转型;在执行entrySet中的 forEach() 中,也是从table表中获取Node对象。

  • 第一步:
public Set<Map.Entry<K,V>> entrySet() {
    Set<Map.Entry<K,V>> es;
  	// 返回空对象
    return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
  • forEach方法
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
    public final int size()                 { return size; }
    public final void clear()               { HashMap.this.clear(); }
  	// 迭代器
    public final Iterator<Map.Entry<K,V>> iterator() {
        return new EntryIterator();
    }
    public final boolean contains(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>) o;
        Object key = e.getKey();
        Node<K,V> candidate = getNode(hash(key), key);
        return candidate != null && candidate.equals(e);
    }
    public final boolean remove(Object o) {
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;
            Object key = e.getKey();
            Object value = e.getValue();
            return removeNode(hash(key), key, value, true, true) != null;
        }
        return false;
    }
    public final Spliterator<Map.Entry<K,V>> spliterator() {
        return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
  	// forEach方法
    public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
        Node<K,V>[] tab;
        if (action == null)
            throw new NullPointerException();
        if (size > 0 && (tab = table) != null) {
          	// 也是从table里面遍历
            int mc = modCount;
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)
                    action.accept(e);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }
}
  • 执行迭代器:
final class EntryIterator extends HashIterator
    implements Iterator<Map.Entry<K,V>> {
    public final Map.Entry<K,V> next() { return nextNode(); }
}

final Node<K,V> nextNode() {
    Node<K,V>[] t;
    Node<K,V> e = next;
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    if (e == null)
        throw new NoSuchElementException();
  	// 都是从table返回的Node对象
    if ((next = (current = e).next) == null && (t = table) != null) {
        do {} while (index < t.length && (next = t[index++]) == null);
     }
    return e;
}

其他的HashMap的源码在前面HashSet里面已经写过了。

HashTable

  • HashTable 和HashMap使用方法基本是一样的
  • HashTable是线程安全的
  • HashTable 的默认大小是 11 扩容阈值也是 0.75 扩容机制是 ( 原大小 * 2 + 1)
  • HashTable的key - Value 都不能为null (会抛出异常)
  • 常用的实现子类有 Properties 用于获取配置文件

TreeSet&TreeMap

  • TreeSet 底层是TreeMap
  • TreeSet的保证数据唯一的方法跟HashSet一样,都是利用了Map中的key有不可重复的性质,将元素存入Value中
  • 可以通过构造器方法,往TreeSet里面传入比较器,使得TreeSet内的数据按照比较器的方法进行排序。

TreeSet&TreeMap源码

Entry结构

static final class Entry<K,V> implements Map.Entry<K,V> {
    K key;
    V value;
    Entry<K,V> left;
    Entry<K,V> right;
    Entry<K,V> parent;
    boolean color = BLACK;
}

构造函数: 传入比较器的构造函数

public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}

public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}

put函数: TreeSet的结构跟HashSet很像,关键的不同就是在添加之前,需要使用比较器,确定元素需要添加的位置。

public V put(K key, V value) {
    Entry<K,V> t = root;
    if (t == null) {
        compare(key, key); // type (and possibly null) check

        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
  	// 判断是如何比较的:一共有两种方法,一种是传入了comparator对象,一种是对象实现了comparable接口,两种比较的具体比较过程。
    // split comparator and comparable paths
  	// cpr 就是匿名内部类
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        do {
            parent = t;
          	// 动态绑定
            cmp = cpr.compare(key, t.key);
          	// 放置位置的逻辑
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else // 相等,不发生任何事情
                return t.setValue(value);
        } while (t != null);
    }
    else {
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t;
            cmp = k.compareTo(t.key);
          	// 放置位置的逻辑 
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}

技术选型

允许重复底层结构增删效率改查效率
ArrayListObject []
LinkedList双向链表
不允许重复底层结构读取顺序
HashSetHashMap无序
TreeSetTreeMap排序
LinkedHashSetHashMap + 双向链表插入取出顺序一致
键值对底层结构键属性
HashMap数组 + 单向链表 + 红黑树无序
TreeMap二叉树排序 compare
HashTableHashMap + 线程同步无序

集合工具类

Java.utils 包下提供了工具类,提供了很多工具方法,方便你对集合进行操作,例如:排序、过滤。

通过学习这个包,既可以丰富了自己对于集合快速操作的手段,也可以通过阅读源码,了解如何操作Java中的数据结构。

提供的工具类主要有两个

  • Collections 用于处理 Set、List 和 Map
  • Arrays 用于处理数组

Collections 常用方法

Java Collections类操作集合详解 (biancheng.net)

排序

  • reverse(List list)
  • 对指定 List 集合元素进行逆向排序
  • shuffle(List list)
    • 对 List 集合元素进行随机排序(可以用于抽奖,对一个Set集合(非重复集合)执行shuffle方法之后取抽奖位数的元素)
  • sort(List list)
    • 根据元素的自然顺序对指定 List 集合的元素按升序进行排序(快速排序)
  • sort(List list, Comparator c)
    • 根据指定 Comparator 产生的顺序对 List 集合元素进行排序
  • swap(List list, int i, int j)
    • 将指定 List 集合中的 i 处元素和 j 处元素进行交换
  • rotate(List list, int distance)
    • 当 distance 为 正数 时,将 list 集合的后 distance 个元素 “整体”移到前面 ;当 distance 为 负数 时,将 list 集合的前 distance 个元素 “整体”移到后面 。该方法不会改变集合的长度

查找、替换

  • binarySearch(List list, Object key)
    • 使用二分搜索法搜索指定的 List 集合,以获得指定对象在 List 集合中的索引。如果要使该方法可以正常工作,则必须保证 List 中的元素已经处于有序状态
  • max(Collection coll)
    • 根据元素的自然顺序,返回给定集合中的最大元素
  • max(Collection coll, Comparator comp)
    • 根据 Comparator 指定的顺序,返回给定集合中的最大元素
  • min(Collection coll)
  • 根据元素的自然顺序,返回给定集合中的最小元素
  • min(Collection coll, Comparator comp)
    • 根据 Comparator 指定的顺序,返回给定集合中的最小元素
  • fill(List list, Object obj)
    • 使用指定元素 obj 替换指定 List 集合中的所有元素
  • frequency(Collection c, Object o)
    • 返回指定集合中指定元素的出现次数
  • indexOfSubList(List source, List target)
    • 返回子 List 对象在父 List 对象中第一次出现的位置索引;
    • 如果父 List 中没有出现这样的子 List,则返回 -1
  • lastIndexOfSubList(List source, List target)
  • 返回子 List 对象在父 List 对象中最后一次出现的位置索引;
  • 如果父 List 中没有岀现这样的子 List,则返回 -1
  • replaceAll(List list, Object oldVal, Object newVal)
    • 使用一个新值 newVal 替换 List 对象的所有旧值 oldVal

复制

Arrays 常用方法

Java中Arrays详解 - 程序咖啡 - 博客园 (cnblogs.com)

sort()

  • 排序算法内都有传入比较器的重载方法,这里不重复展示

  • sort(Object[] array)

    • 对数组按照升序排序
  • sort(Object[] array, int from, int to)

    • 对数组元素指定范围进行排序(排序范围是从元素下标为from,到下标为to-1的元素进行排序)
  • parallelSort(Object[] array)

    • 数组元素的并行升序排序 (多线程)
  • parallelSort(Object[] array,int from, int to)

    • 指定范围的并行排序

binarySearch()

  • 搜索方法都可以指定起始位置 int fromIndex

  • binarySearch(Object[] a, Object key)

    • 二分搜索法来搜索指定数组

copyOf()

  • copyOf(T[] original, int newLength)
    • 将数组中指定长度的数组复制为新的数组
  • copyOf(U[] original, int newLength, Class<? extends T[]> newType)
    • 将数组中指定长度,指定类型的数组复制为新的数组
  • copyOfRange(T[] original, int from, int to)
    • 对一个已有的数组进行截取复制,复制出一个左闭右开区间的数组
  • copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType)
    • 对一个已有的数组进行截取复制,复制出一个左闭右开区间内指定数据类型的数组

fill()

  • fill(Object[] array,Object object)
    • 可以为数组元素填充相同的值
  • fill(Object[] array,int from,int to,Object object)
    • 对数组的部分元素填充一个值,从起始位置到结束位置,前闭后开区间

toString()

  • toString(Object[] array)
    • 返回数组的字符串形式
  • deepToString(Object[][] arrays)
    • 返回多维数组的字符串形式

spliterator()

  • spliterator(T[] array)
    • 返回一个并行迭代器
  • spliterator(T[] array, int startInclusive, int endExclusive)
    • 返回一个并行迭代器,可以指定迭代器中的数组范围