Java 基础——集合与 Map 讲解

一、集合

在这里插入图片描述

① Queue 接口与 List、Set 同一级别,都是继承了Collection 接口。
② LinkedList 既可以实现 Queue 接口,也可以实现 List 接口。Queue 接口窄化了对 LinkedList 的方法的访问权限(即在方法中的参数类型如果是 Queue 时,就完全只能访问 Queue 接口所定义的方法 了,而不能直接访问 LinkedLis t的非 Queue 的方法),以使得只有恰当的方法才可以使用。
③ SortedSet 是个接口,它里面的(只有 TreeSet 这一个实现可用)中的元素一定是有序的。

1、List 接口

集合 List 是接口 Collection 的子接口,List 进行了元素排序,且允许存放相同的元素,即有序,可重复。List 接口的子类有:ArrayList、LinkedList 和 vector 等。

(1)ArrayList 类

1)源码

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

    private static final long serialVersionUID = 8683452581122892189L;

    // Default initial capacity.
    private static final int DEFAULT_CAPACITY = 10;
	// Shared empty array instance used for empty instances.
    private static final Object[] EMPTY_ELEMENTDATA = {};
    /**
     * 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 = {};
    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access
    // The size of the ArrayList (the number of elements it contains).
    private int size;
}

2)构造函数

	// 1、使用初始容量
	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);
        }
    }

    // 2、不提供初始容量,使用默认空数组
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    // 3、使用集合创建 ArrayList
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

3)扩容机制

	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 扩容后变为原来的 1.5 倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //  把原来的数组复制到另一个内存空间更大的数组中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
	private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

ArrayList 的扩容可分为两种情况:
第一种情况,当 ArrayList 的容量为0时,此时添加元素的话,需要扩容,三种构造方法创建的 ArrayList 在扩容时略有不同:

1)以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组,当真正对数组进行添加元素操作时,才真正分配容量,即向数组中添加第一个元素时,数组容量扩为10。 此后若需要扩容,则正常扩容。
2)传容量构造,当参数为 0 时,创建 ArrayList 后容量为 0,添加第一个元素后,容量为 1,此时 ArrayList 是满的,下次添加元素时需正常扩容。
3)传列表构造,当列表为空时,创建 ArrayList 后容量为 0,添加第一个元素后,容量为 1,此时 ArrayList 是满的,下次添加元素时需正常扩容。

第二种情况,当 ArrayList 的容量大于 0,并且 ArrayList 是满的时,此时添加元素的话,进行正常扩容,每次扩容到原来的 1.5 倍
扩容后 把原来的数组复制到另一个内存空间更大的数组中。

4)常用 API

在这里插入图片描述 在这里插入图片描述

5)优缺点

优点:底层数据结构是数组可以为 null 值,可以允许重复元素;有序;查询快,增删慢异步,线程不安全效率高

(2)LinkedList 类

1)源码

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;
    }

2)构造函数

由于使用链表实现列表,所以构造时不能指定列表初始大小;也不存在扩容的说法,容量不够时创建新的节点即可

	// 空列表
    public LinkedList() {
    }

    // 使用一个集合构造列表
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

3)常用 API

在这里插入图片描述
在这里插入图片描述

4)优缺点

底层数据结构是双链表查询慢,增删快线程不安全效率高

(3)vector 类

1)源码

public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    /**
     * The array buffer into which the components of the vector are
     * stored. The capacity of the vector is the length of this array buffer,
     * and is at least large enough to contain all the vector's elements.
     *
     * <p>Any array elements following the last element in the Vector are null.
     *
     * @serial
     */
    protected Object[] elementData;

    // 元素个数
    protected int elementCount;

    // 扩容时的增量
    protected int capacityIncrement;

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -2767605614048989439L;

2)构造函数

   // 指定初始容量和扩容时的增量
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

    // 指定初始容量,扩容时增量默认为0
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }

    // 无参构造,初始容量默认为10,扩容时增量默认为0
    public Vector() {
        this(10);
    }

    // 使用集合创建vector
    public Vector(Collection<? extends E> c) {
        elementData = c.toArray();
        elementCount = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
    }

3)扩容机制

	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	
	private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         (oldCapacity + capacityIncrement) : (oldCapacity * 2);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
	private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

Vector 当扩容容量增量大于 0 时,新数组长度为原数组长度+扩容容量增量,如果没有传入扩容容量增量,扩容后的新数组就是当前数组的 2 倍
触发扩容时,如果新数组的大小比所需最小容量小,则新数组的大小改为所需最小容量小;如果新数组的大小比数组的最大容量 MAX_ARRAY_SIZE 还要大,则比较 minCapacity 和 MAX_ARRAY_SIZE,如果 minCapacity 大于最大容量,则新容量则为 Integer.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8。

4)常用 API

这里是引用
在这里插入图片描述

5)优缺点

底层数据结构是数组查询快,增删慢线程安全效率低

(4)Stack 栈(类)

栈是 Vector 的一个子类,它实现了一个标准的后进先出的栈。

1)源码

public class Stack<E> extends Vector<E> {
    
    // 创建一个空栈
    public Stack() {
    }

    // 入栈
    public E push(E item) {
        addElement(item);
        return item;
    }

    // 出栈,线程安全
    public synchronized E pop() {
        E obj;
        int len = size();
        obj = peek();
        removeElementAt(len - 1);
        return obj;
    }

    public synchronized E peek() {
        int len = size();
        if (len == 0)
            throw new EmptyStackException();
        return elementAt(len - 1);
    }

    // 判空
    public boolean empty() {
        return size() == 0;
    }

    public synchronized int search(Object o) {
        int i = lastIndexOf(o);

        if (i >= 0) {
            return size() - i;
        }
        return -1;
    }

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = 1224463164541339165L;
}

2)构造函数

堆栈只定义了默认构造函数,用来创建一个空栈。

public Stack() {
    }

3)方法

堆栈除了包括由 Vector 定义的所有方法,也定义了自己的一些方法。

这里是引用

(5)Queue 队列(接口)

Queue 是 java 中实现队列的接口,队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。
Queue 的实现类有 LinkedList 和 PriorityQueue,最常用的实现类是 LinkedList,因此我们可以把 LinkedList 当成 Queue 来用

1)源码

public interface Queue<E> extends Collection<E> {
    
    boolean add(E e);

    boolean offer(E e);

    E remove();

    E poll();
    
    E element();

    E peek();
}

2)方法

1)压入元素(添加):add()、offer()

相同:未超出容量,从队尾压入元素,返回压入的那个元素。
区别:在超出容量时,add()方法会对抛出异常,offer()返回false

2)弹出元素(删除):remove()、poll()

相同:容量大于0的时候,删除并返回队头被删除的那个元素。
区别:在容量为0的时候,remove()会抛出异常,poll()返回false

3)获取队头元素(不删除):element()、peek()

相同:容量大于0的时候,都返回队头元素。但是不删除。
区别:容量为0的时候,element()会抛出异常,peek()返回null。

队列除了基本的 Collection 操作外,还提供特有的插入、提取和检查操作(如上)。每个方法都存在两种形式:一种抛出异常(操作失败时),另一种返回一个特殊值(null 或 false,具体取决于操作)。插入操作的后一种形式是用于专门为有容量限制的 Queue 实现设计的;在大多数实现中,插入操作不会失败。
在这里插入图片描述
3)示例

public class QueueTest {
    public static void main(String[] args) {
    
        Queue<String> queue = new LinkedList();
        queue.offer("元素A");
        queue.offer("元素B");
        queue.offer("元素C");
        queue.offer("元素D");
        queue.offer("元素E");
        while (queue.size() > 0) {
            String element = queue.poll();
            System.out.println(element);
        }
    }
}

结果:
在这里插入图片描述

2、Set 接口

在这里插入图片描述

Set 集合用于存储不重复的元素集合,主要的子类有 HashSet、LinkedHashSet 和 TreeSet。

TreeSet 的主要功能用于排序;
LinkedHashSet 的主要功能用于保证 FIFO 即有序的集合(先进先出);
HashSet 只是通用的存储数据的集合;
三者都不是线程安全的,如果要使用线程安全可以 Collections.synchronizedSet();
HashSet 插入数据最快,其次 LinkHashSet,最慢的是 TreeSet 因为内部实现排序

(1)HashSet

HashSet 底层数据结构是哈希表。 HashSet 实现了Set接口,并没有实现 SortedSet 接口,因此是无序的允许存放 null 值

1、HashSet 的源码

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable{

    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
  }

HashSet 实现 Set 接口,底层由 HashMap 来实现,为哈希表结构,新增元素相当于 HashMap 的 keyvalue 默认为一个固定的 Object。HashSet 相当于一个阉割版的 HashMap。

2、构造函数

	// 默认构造函数将构建一个容量为16,加载因子为0.75的HashMap
	public HashSet() {
        map = new HashMap<>();
    }

    // 使用集合创建 hashmap
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

    // 指定容量和负载因子
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

    // 指定容量
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

	// 构造一个新的空的linkedhashset.(这个包的私有构造函数只被LinkedHashSet使用)。支持的HashMap实例是一个LinkedHashMap,具有指定的初始容量和指定的负载因子。dummy参数可以忽略,只是为了区分与其他构造函数的区别,并没有实际意义。
	HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

从构造函数可以看出,HashSet 内部封装使用了 HashMap,其构造函数都是通过调用 HashMap 构造函数来实现的

3、重写 equals 和 hashCode 方法

当我们使用 HashSet 存储自定义类时,需要在自定义类中重写 equals 和 hashCode 方法,主要原因是集合内不允许有重复的数据元素,在集合校验元素的有效性时(数据元素不可重复),需要调用 equals 和 hashCode 验证。

4、元素的唯一性与查找

HashSet 中的元素是唯一的,保证唯一性的原理:HashSet 用 hashMap 的 key 键进行存储元素,所以 HashSet 也不允许出现重复值。判断元素唯一性的依据是 hashCode 和 equals() 方法比较的结果,因此向 HashSet 集合存储元素时,集合先判断元素的 hashCode ,如果元素的 hashCode 相同,接着调用元素的 equals() 方法,如果返回值为真,集合就会判定为元素是同一个元素。
在这里插入图片描述
哈希算法:先计算得到 key 的 Hash 值,然后通过 Key 高16位与低16位相异或(高16位不变),然后与数组大小-1相与,得到了该元素在数组中的位置,流程:
在这里插入图片描述

5、HashSet 与 HashMap

(1)HashSet 实现了 Set 接口,仅存储对象;HashMap 实现了 Map 接口,存储的是键值对。
(2)HashSet 是披着 Set 外壳的 HashMap:HashSet 底层其实是用 HashMap 实现存储的,HashSet 封装了一系列 HashMap 的方法,依靠利用 hashMap 的 key 键进行存储,而 value 值默认为 Object 对象。所以 HashSet 也不允许出现重复值,判断标准和 HashMap 判断标准相同,两个元素的 hashCode 相等并且通过 equals() 方法返回 true。

(2)LinkedHashSet

底层数据结构是链表和哈希表。由链表保证元素有序;由哈希表保证元素唯一。(FIFO插入有序,唯一)

源码

// 父类
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable{
 	// dummy:dummy参数可以忽略,只是为了区分与其他构造函数的区别,并没有实际意义。
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }


// 继承 HashSet
public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, java.io.Serializable {

    private static final long serialVersionUID = -2851667679971038690L;

    
    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }

    
    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }

    
    public LinkedHashSet() {
        super(16, .75f, true);
    }

    
    public LinkedHashSet(Collection<? extends E> c) {
        super(Math.max(2*c.size(), 11), .75f, true);
        addAll(c);
    }
 }

(3)TreeSet

底层数据结构是红黑树。(唯一,有序)
保证元素排序的: 自然排序、比较器排序
保证元素唯一性的:根据比较的返回值是否是0来决定

二、Map

Map 容器中存储的是键值对,既是一个键信息和一个值信息的映射关系。Map 容器中可以有成千上万的键值对信息,每一个键值对都使用 Map.Entry<K , V> 的定义进行存储——也就是说一个 Map 容器中可以有成千上万个 Map.Entry 接口的实例化对象。
在这里插入图片描述
Map 集合类属于映射式容器,用于存储元素对(称作“键”和“值”),其中每个键映射到一个值。Map 这种键值(key-value)映射表的数据结构,作用就是通过 key 能够高效、快速查找 value。
在这里插入图片描述

Map 常用子类有 HashMap、LinkedHashMap、 TreeMap和Hashtable。

(1)TreeMap 是有序的,HashMap 和 HashTable 是无序的
(2)HashMap 的方法不是同步的,不是线程安全的,效率较高Hashtable 的方法是同步的,是线程安全的,效率较低
(3)Hashtable 不允许 null 值,HashMap 允许 null 值(key 和 value 都允许)。
(4)父类不同:Hashtable 的父类是 Dictionary,HashMap 的父类是 AbstractMap。
如果对同步性或与遗留代码的兼容性没有任何要求,建议使用 HashMap。
查看 Hashtable 的源代码就可以发现,除构造函数外,Hashtable 的所有 public 方法声明中都有 synchronized 关键字,而 HashMap 的源码中则没有。

常用 API:
在这里插入图片描述
在这里插入图片描述

1、HashMap(数组 + 链表 + 红黑树)

它根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有高效的访问速度,但遍历顺序却是不确定的
HashMap最多只允许一条记录的键为null,允许多条记录的值为null
HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。
如果需要满足线程安全,可以用 Collections 的静态方法 synchronizedMap 方法使 HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap(分段加锁)

(1)HashMap 结构

HashMap 主要是数组 + 链表 + 红黑树(JDK1.8 增加红黑树部分)结构组成。
在这里插入图片描述
从上图可以看出,HashMap 底层就是一个数组结构(Entry<K,V>[] table),数组中的每一项又是一个链表
在这里插入图片描述

(2)HashMap 构造函数

// 1、无参构造器
// 负载因子为默认值 0.75f,容量为默认初始值 16
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }


// 2、把该初始容量和默认负载因子 0.75 f作为入参,调用 HashMap 的两个参数的构造方法
public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }


// 3、通过指定的初始容量和负载因子初始化一个空的 HashMap
public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)		// 如果初始容量小于0,抛出异常
            throw new IllegalArgumentException("Illegal initial capacity: " +        
        // 如果初始容量超过最大容量(1<<32), 则使用最大容量作为初始容量                                     
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))		//如果负载因子小于等于0或者不是数字,则抛出异常
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        // 调用 tableSizeFor 方法计算出不小于 initialCapacity 的最小的 2 的幂的结果,并赋给成员变量 threshold
        this.threshold = tableSizeFor(initialCapacity);
    }


// 4、使用 Map 作为构造参数
public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);	// 调用 PutMapEntries() 来完成 HashMap 的初始化赋值过程
    }

2、LinkedHashMap( 数组 + 链表 + 红黑树)

LinkedHashMap 是 HashMap 的一个子类,替 HashMap 完成了输入顺序的记录功能
它的底层是基于拉链式散列结构,该结构由数组+链表+红黑树组成, 在此基础上 LinkedHashMap 增加了一条双向链表通过维护一个运行于所有条目的双向链表保持遍历顺序和插入顺序一致的问题
链表只让挂7个元素,超过七个就会转成一个红黑树进行处理(最多是64,超多64 就会重新拆分)
key和value都允许为空key重复会覆盖,value可以重复;
LinkedHashMap是非线程安全
要想实现像输出同输入顺序一致,应该使用 LinkedHashMap。

(1)LinkedHashMap 结构

LinkedHashMap 和 HashMap 的区别在于它们的基本数据结构上,看一下 LinkedHashMap 的基本数据结构,也就是 Entry:

private static class Entry<K,V> extends HashMap.Entry<K,V> {
    // These fields comprise the doubly linked list used for iteration.
    Entry<K,V> before, after;

    Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
        super(hash, key, value, next);
    }
    ... 
  } 

在这里插入图片描述 在这里插入图片描述 第一张图为 LinkedHashMap 整体结构图,第二张图专门把循环双向链表抽取出来,直观一点,注意该循环双向链表的头部存放的是最久访问的节点或最先插入的节点,尾部为最近访问的或最近插入的节点,迭代器遍历方向是从链表的头部开始到链表尾部结束,在链表尾部有一个空的 header 节点,该节点不存放 key-value 内容,为 LinkedHashMap 类的成员属性,循环双向链表的入口。

(2)构造函数

// 构造一个具有默认初始容量 (16)和默认负载因子(0.75)的空 LinkedHashMap
public LinkedHashMap() {
        super();  // 调用HashMap对应的构造函数
        accessOrder = false;           // 迭代顺序的默认值
    }


// 构造一个指定初始容量和指定负载因子的空 LinkedHashMap
public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);      // 调用HashMap对应的构造函数
        accessOrder = false;            // 迭代顺序的默认值
    }


// 构造一个指定初始容量和默认负载因子 (0.75)的空 LinkedHashMap
public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);  // 调用HashMap对应的构造函数
        accessOrder = false;     // 迭代顺序的默认值
    }



// 构造一个与指定 Map 具有相同映射的 LinkedHashMap,其初始容量不小于 16 (具体依赖于指定Map的大小),负载因子是 0.75
public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super(m);       // 调用HashMap对应的构造函数
        accessOrder = false;    // 迭代顺序的默认值
    }



// 构造一个指定初始容量和指定负载因子的具有指定迭代顺序的LinkedHashMap
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor);   // 调用HashMap对应的构造函数
        this.accessOrder = accessOrder;    // 迭代顺序的默认值
    }

(3)获取第一个和最后一个元素

// 获取LinkedHashMap中的头部元素(最早添加的元素):时间复杂度O(1)
linkedhashmap.entrySet().iterator().next();


// 获取LinkedHashMap中的末尾元素(最近添加的元素):时间复杂度O(n)
Iterator<K> iterator = linkedhashmap.keySet().iterator();
K tail = null;
while (iterator.hasNext()) {
	tail = iterator.next();
    }

3、TreeMap(数组 + 红黑树)

TreeMap 存储 K-V 键值对,通过红黑树(R-B tree)实现。
TreeMap 继承了 NavigableMap 接口,NavigableMap 接口继承了 SortedMap 接口,可支持一系列的导航定位以及导航操作的方法,当然只是提供了接口,需要 TreeMap 自己去实现;
TreeMap 实现了 Cloneable 接口,可被克隆实现了 Serializable 接口,可序列化,可通过 Hessian 协议进行传输
TreeMap 因为是通过红黑树实现,红黑树结构天然支持排序,默认情况下通过Key值的自然顺序进行排序;

(1)TreeMap 的结构

public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
    // TreeMap是可以自动排序的,默认情况下comparator为null,这个时候按照key的自然顺序进行排序,然而并不是所有情况下都可以直接使用key的自然顺序,有时候我们想让Map的自动排序按照我们自己的规则,这个时候你就需要传递Comparator的实现类
    private final Comparator<? super K> comparator;

    private transient Entry<K,V> root;

    /**
     * The number of entries in the tree
     */
    private transient int size = 0;

    /**
     * The number of structural modifications to the tree.
     */
    private transient int modCount = 0;

    //返回从fromKey到toKey的集合:含头不含尾
    java.util.SortedMap<K,V> subMap(K fromKey, K toKey);

    //返回从头到toKey的集合:不包含toKey
    java.util.SortedMap<K,V> headMap(K toKey);

    //返回从fromKey到结尾的集合:包含fromKey
    java.util.SortedMap<K,V> tailMap(K fromKey);
    
    //返回集合中的第一个元素:
    K firstKey();
   
    //返回集合中的最后一个元素:
    K lastKey();
    
    //返回集合中所有key的集合:
    Set<K> keySet();
    
    //返回集合中所有value的集合:
    Collection<V> values();
    
    //返回集合中的元素映射:
    Set<Map.Entry<K, V>> entrySet();
}

Entry 结构

static final class Entry<K,V> implements Map.Entry<K,V> {
    //key,val是存储的原始数据
    K key;
    V value;
    //定义了节点的左孩子
    Entry<K,V> left;
    //定义了节点的右孩子
    Entry<K,V> right;
    //通过该节点可以反过来往上找到自己的父亲
    Entry<K,V> parent;
    //默认情况下为黑色节点,可调整
    boolean color = BLACK;
 }

(3)TreeMap 的构造函数

// 1、默认构造函数构造TreeMap时,使用java的默认的比较器比较Key的大小,从而对TreeMap进行排序
public TreeMap() {
        comparator = null;
    }

// 2、带比较器的构造函数
public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

// 2、带Map的构造函数,Map会成为TreeMap的子集
public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }

// 2、带SortedMap的构造函数,SortedMap会成为TreeMap的子集
public TreeMap(SortedMap<K, ? extends V> m) {
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

(3)TreeMap 的排序

TreeMap 基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。

1)TreeMap 因为是通过红黑树实现,红黑树结构天然支持排序,默认情况下通过 Key 值的自然顺序进行排序;
2)在使用 TreeMap 时,可以自己定义比较机制:一种方式是key实现 java.lang.Comparable 接口,并实现其compareTo()方法。另一种方式是写一个类(如MyCompatator)去实现java.util.Comparator接口,并实现compare()方法,然后将MyCompatator类实例对象作为TreeMap的构造方法参数进行传参(当然也可以使用匿名内部类)。

(4)TreeMap 的插入

TreeMap是数组+红黑树的实现方式。TreeMap的底层结构就是一个数组,数组中每一个元素又是一个红黑树。当添加一个元素(key-value)的时候,根据key的hash值来确定插入到哪一个桶中(确定插入数组中的位置),当桶中有多个元素时,使用红黑树进行保存。

当一个桶中存放的数据过多,那么根据key查找的效率就会降低,就必须对其扩容。hash数组的默认大小是11,当hash数组的容量超过初始容量0.75时,增加的方式是old*2+1

4、Hashtable(数组 + 链表)

Hashtable继承 Dictionary 类,实现 Map 接口,很多映射的常用功能与 HashMap 类似, Hashtable 采用"拉链法"实现哈希表 ,不同的是它来自 Dictionary 类,并且是线程安全的,任一时间只有一个线程能写 Hashtable,但并发性不如 ConcurrentHashMap,因为ConcurrentHashMap 引入了分段锁。Hashtable 使用 synchronized 来保证线程安全,在线程竞争激烈的情况下 HashTable 的效率非常低下。
Hashtable 并不是像 ConcurrentHashMap 对数组的每个位置加锁,而是对操作加锁,性能较差

不需要线程安全的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换

5、Map 实现类间的对比

在这里插入图片描述

三、Java 中的集合与 map 的选择

1、单列还是双列:

单列就选 Collection 类型的,双列选 Map 类型的

2、选择单列后看元素是否唯一

(1) 是:选择Set集合

       ● 看元素是否排序:
                      是:TreeSet
                      否:HashSet

(2) 否: 选择List集合

     ● 安全线高低:            
             高:Vector
           低:ArrayList 或LinkedList '
   ● 增删多:LinkedList
  ●	查询多:ArrayList

在这里插入图片描述

四、常用集合与 map 的声明和初始化

1、数组

(1)动态初始化格式

数据类型 [ ] 数组名称 = new 数据类型[数组长度]

(2)静态初始化格式

数据类型 [ ] 数组名称 = new 数据类型[ ]{元素1,元素2,元素3…}

例如:

int[] arrayC = new int[]{30, 40, 50}

(3)静态初始化省略格式

数据类型 [ ] 数组名称 = {元素1,元素2,元素3…}

例如:

int[] arrayC = {30, 40, 50}

2、ArrayList

(1)使用集合

ArrayList<数据类型> 列表名称 = new ArrayList<>(集合c)

(2)使用 Arrays.asList

ArrayList<data_type> arrayListName = new ArrayList<data_type> ( Arrays.asList (Object o1, Object o2, …, Object on));

例:

ArrayList<Integer> arr = new ArrayList<>(Arrays.asList(1, 2, 3));

(3)使用匿名内部类方法

ArrayList <数据类型> arraylistName =new ArrayList <数据类型>(){{
add(对象o1); add(对象o2);… add(对象);
}};

例:

ArrayList<String> colors = new ArrayList<String>(){{
        add("Red");
        add("Blue");
        add("Purple");
    }};

(4)使用 Collection.nCopies 方法

此方法用于使用相同的值初始化 ArrayList。我们提供要初始化的元素数和该方法的初始值。

ArrayList <数据类型> arrayListName = new ArrayList <数据类型>(Collections.nCopies(count,element));

例:

ArrayList<Integer> intList = new ArrayList<Integer>(Collections.nCopies(10,10));

3、HashMap

1、static 块

Map<String, String> myMap = new HashMap<String, String>();
static {
    myMap.put("张三", "北京");
    myMap.put("李四", "上海");
  }

2、匿名内部类(双括号初始化)

HashMap<String, String> myMap = new HashMap<String, String>() {{
        put("张三", "北京");
        put("李四", "上海");
    }};

java8 新特性,双括号初始化。
第一层括弧实际是定义了一个匿名内部类 ,第二层括弧实际上是一个实例初始化块,这个块在内部匿名类构造时被执行
因为这种方式是匿名内部类的声明方式,所以引用中持有着外部类的引用。所以当串行化这个集合时,外部类也会被不知不觉的串行化,而当外部类没有实现Serialize接口时,就会报错。其二,在上面的例子中,其实是声明了一个继承自HashMap的子类,然而有些串行化方法,例如要通过Gson串行化为json,或者要串行化为xml时,类库中提供的方式,是无法串行化Hashset或者HashMap的子类的,也就导致了串行化失败。解决办法是重新初始化为一个HashMap对象【new HashMap(map);】,这样就可以正常进行初始化了。
另外要注意的是,这种使用双括号进行初始化的语法在执行效率上要比普通的初始化写法要稍低

3、对于 Java 9 或更高版本

// 最多存储10个元素
Map<String, String> test1 = Map.of(
    "a", "b",
    "c", "d"
);
 
// 没有限制
Map<String, String> test2 = Map.ofEntries(
    entry("a", "b"),
    entry("c", "d")
);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值