Java集合框架概述

说明

集合、数组都是对多个数据进行存储操作的结构,简称Java容器。都是属于内存存储。

数组的特点

一旦初始化后,长度就确定

初始化后类型就确定了

image.png

image.png
image.png

image.png

image.png

Collection接口方法

add(E e)

addAll(Collection<? extends E> c)

size() 最大值 nteger.MAX_VALUE

clear();

isEmpty();

contains();

如果此集合包含指定的元素,则返回true 。 更正式地说,当且仅当此集合包含至少一个元素e使得(o==null ? e==null : o.equals(e))时才返回tru

containsAll(Collection<?> c);

remove(Object o);

retainAll(Collection<?> c);

从该集合中移除所有未包含在指定集合中的元素。

hashCode();

toArray()

iterator()

Iterator迭代器接口

JDK5.0增强for遍历集合

Collection子接口:List

说明

  1. 鉴于Java数组的局限性,通常使用 list代替数组
  2. List 集合类中元素有序、且可重复,集合中的每个元素都有对应的顺序索引
  3. List容器中的元素都对应一个整数型的序号记载在容器中的位置,可以根据序号的存取容器中的元素,
  4. JDK API 中List 接口实现类常用的有:ArrayList、LinkedList 和 Vector

三者ArrayList、LinkedList 和 Vector异同

同:实现了List接口,存储数据的特点相同:存储有序的,可以重复的数据

异:

  1. 出现的版本不同ArrayList,线程不安全,效率高; 底层使用transient Object[] elementData 数组
  2. Vector 线程安全,效率低; 底层使用 protected Object[] elementData 数组;
  3. LinkedList 对于频繁的插入、删除操作,使用此类效率比ArrayList高; 底层使用双向链表存储

ArrayList 源码分析

JDK7 源码

说明:

  1. 当超过底层数组长度(默认是10)则扩容,扩容为原来的1.5倍,同时需要将原有数组中的数据复制到新的数组中
  2. 所以在使用ArrayList时,建议使用带参的构造方法 new ArrayList<>(100); ```java // 创建一个为长度10的数组 public ArrayList() {

    1. this(10);

    }

    public boolean add(E e) {

     ensureCapacityInternal(size + 1);  // Increments modCount!!
     elementData[size++] = e;
     return true;
    

    }

private void ensureExplicitCapacity(int minCapacity) { modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

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

<a name="mqCeK"></a>
### JDK8源码
<a name="wHmk2"></a>
#### 说明

1. new ArrayList() 底层Object[] _DEFAULTCAPACITY_EMPTY_ELEMENTDATA _= {};并没有创建长度
1. 第一次调用add()方法时,底层才创建默认长度为10的数组,并将数据添加到数组elementData 中

```java
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}


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


    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

     private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return 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);
    }

LinkedList源码

双向链表结构

Collection子接口:Set

说明:Set接口:存储无序的、不可重复的数据

HashSet

说明: 线程不安全,可以存储null值

HashSet源码 存储使用的方式时 散列表以及 链表存储,

image.png

 /**
     源码分析
     * Implements Map.put and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    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)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            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) {
                        p.next = newNode(hash, key, value, null);
                        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;
    }
  @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

  public static int hash(Object... values) {
        return Arrays.hashCode(values);
    }


public static int hashCode(Object a[]) {
        if (a == null)
            return 0;

        int result = 1;

        for (Object element : a)
            result = 31 * result + (element == null ? 0 : element.hashCode());

        return result;
    }

image.png

Set要实现不可重复的要求

  1. 向Set中添加的数据,其所在的类一定要重写hashCode() 和 equals()
  2. 重写的hashCode() 和 equals()尽可能保持一致性:相等的对象必须具有相等的散列码

    LinkedHashSet

    说明:是HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历

    对于频繁遍历,LinkedHashSet 比 HashSet效率高

TreeSet(数据结构 :二叉树 - 红黑树 结构)

说明:可以按照添加对象的指定属性,进行排序

  1. 添加的数据需要同一个类型的数据
  2. 两种排序方式:自然排序 和 定制排序(Comparator)
  3. 自然排序中,比较两个对象是否相同的标准为:compareTo() 返回为0 ,不再是equals().
  4. 定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()

image.png

练习题



//  User 需要重写 hashCode() 和 equals() 方法
public static void main(String[] args) {
        HashSet set = new HashSet<>();
        User user = new User("1001", "aa");
        User user1 = new User("1002", "bb");

        set.add(user);
        set.add(user1);

        user.name = "cc";
        set.remove(user);

        System.out.println(set);
        set.add(new User("1001","cc"));
        System.out.println(set);
        set.add(new User("1001","aa"));
        System.out.println(set);

    }

Map接口

说明 key - value 对的数据

对Map 的理解

key:无序的、不可重复的,使用Set存储所有的key —>key所在的类要重写equals() 和hashCode() (以HashMap 为例)

value:无序的、可重复的,使用Cllection存储所有的value —>value所在的类要重写equals()

一个键值对:key-value 构成一个Entry对象

Map中的entry:无序的、不可重复的,使用Set存储所有的entry

HashMap

说明:作为Map的主要实现类、线程不安全,效率高,key和value 可以存储为null

底层:

jdk7 之前: 数组+链表
jdk8 : 数组 + 链表+ 红黑树

底层原理分析⭐️

  1. HashMap hashMap = new HashMap();
  2. 在实例化以后,底层创建了长度16的一堆数组 Entry[] table.
  3. hashMap.put(key1,value1);
  4. 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法以后,得到在entry 数组中的存放位置
  5. 如果位置的数据为空,此时的key1 - value1 添加成功
  6. 如果此位置上的数据不为空,(意味着此位置存在一个或多个数据(链表形式存在),比较key1 和已经存在一个或者多个的哈希值)
    1. 如果key1的哈希值与已经存在的数据的哈希值都不同,此时key1-value1添加成功。
    2. 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)
      1. 如果equals()返回false,此时key1 - value1 添加成功
      2. 如果equals()返回true ,使用value1 替换 value2
  7. 在不断的添加过程中,会涉及扩容问题,默认的扩容方式,扩容为原来的2倍,并将原有的数据复制
  8. jdk8 相较于 jdk7在底层实现方面的不同
    1. new HashMap(): 底层没有创建一个长度16的数组
    2. jdk8 底层的数组是 : Node[], 而非Entry[]
    3. 首次调用put()方法时,底层创建长度为16的数组
    4. jdk7 底层结构只有:数组+链表 jdk8 中底层结构:数组+链表+红黑树,当数组的某一索引位置上的元素以链表形式存在的数据个数 > 8 且数组的长度 > 64 时,此时此索引位置上的所有数据改为使用红黑树存储。

      内部名词解释

      DEFAULT_INITIAL_CAPACITY : 默认初始容量 (16)

      DEFAULT_LOAD_FACTOR :默认负载因子 (0.75)

      MIN_TREEIFY_CAPACITY : 可以将 b 树化的最小表容量,就是链表中长度的最小值, 默认是64

      threshold:扩容的临界值,= 容量 * 填充因子

      TREEIFY_THRESHOLD : Bucket 中链表长度大于改默认值,转化为红黑树:默认是 8

为什么提前扩容

  1. 为了提前让链表尽可能少

LinkedHashMap

Hashtable

说明:线程安全,key和value 不能存储为null

Properties

处理配置文件 key 和value 都是String 类型

SortedMap

TreeMap

说明:保证按照添加的key- value 对进行排序,实现排序遍历,此时考虑的自然排序或定制排序;底层使用红黑树。

面试题

  1. HashMap 的底层实现原理
  2. HashMap 与 Hashtable 的异同?
  3. CurrentHashMap 与 Hashtable 的异同?

    Collections工具类

    常用方法

    Collections.copy(dest, src);

     Collections.reverse(list);
    

    Collections.shuffle(list);

    Collections.sort(list);

    Collections.sort(list, c);

    Collections.swap(list, i, j);

    Collections.frequency(c, o);

    Collections.replaceAll(list, oldVal, newVal);