一起从源码学习java系列(一)(util)

怎么用IDEA查看源码

我从 util 包学习(按照结构模块来)【本文基于jdk8】


浅析Collection接口

public interface Collection<E> extends Iterable<E>

继承了Iterable父接口,那么该接口的实现都可以获得迭代器。

子接口

Set

以下的英文段落都是 jdk 文档定义:

A collection that contains no duplicate elements. More formally, sets contain no pair of elements e1 and e2 such that e1.equals(e2), and at most one null element. As implied by its name, this interface models the mathematical set abstraction.

是一个没有多重元素的集合,更准确的来说,集合不会存在一对元素 e1,e2 , 使得 e1.equals(e2) , 并且最多只有一个空元元素。顾名思义,这个接口是从数学中的集合中的抽象建模(抽象出来的模型)。

//TODO

AbstractCollection

//TODO

Queue

//TODO

List
  • AbstractList
  • AbstractSequentialList

This class provides a skeletal implementation of the List interface to minimize the effort required to implement this interface backed by a “sequential access” data store (such as a linked list). For random access data (such as an array), AbstractList should be used in preference to this class.

这个类提供了最简化实现“连续访问”的 List 接口(例如链表)的骨架。对于随机访问数据(例如数组),应该优先使用AbstractList。

Map接口


常用数据结构

线性表
链表

LinkedList


Doubly-linked list implementation of the List and Deque interfaces. >Implements all optional list operations, and permits all elements >(including null).

list接口实现类都可以添加多个 null //TODO

文档注释中提到了两点比较重要的:

  • 1.这个实现是不同步的,若有并发修改的操作,则必须在外部实现同步(例如: List list = Collections.synchronizedList(new LinkedList(…));)

  • 2.迭代器返回的 iterator and listIterator是 fail-fast 的:只要创建了迭代器后,若是对该对象做任何除了迭代器自身实现的 remove 或者 add 方法,那么迭代器将会抛出一个 ConcurrentModificationException.

    一个简单展示 fail-fast 的样例代码及分析

    因此,在面对并发修改时,迭代器会快速而干净地失败,而不是在将来某个不确定的时间冒着任意的、不确定的行为的风险。

    注意,不能保证迭代器的 fail-fast 机制,因为通常情况下,在存在不同步的并发修改的情况下,不可能做出任何硬保证。Fail-fast迭代器在尽最大努力的基础上抛出ConcurrentModificationException。因此,编写依赖于此异常的正确性的程序是错误的:迭代器的快速失败行为应该只用于检测错误。

    详细请看上面的样例。


public class LinkedList<E> extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

从他继承以及实现的接口上来看,这是一个包含了双向队列功能的连续链表(系列化、拷贝)

  • get(int index)【伪随机访问】
public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
}

我们可以发现一个 get(index) 方法,首先判断是否越界,然后返回 node.

  • node(int 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;
          }
      }

虽然有随机访问这个方法,但是实际上仍然是不断迭代获得元素

顺序表

ArrayList

这里思考了许久 ArrayList 为什么继承了 AbstractList 又实现 List 接口—>跳转链接

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

它是一个可变大小的 List 实现类,除了 List 接口中的操作外,还提供改变size的操作。

//初始容量10
private static final int DEFAULT_CAPACITY = 10;

//两种情况用到这个参数
//1.new ArrayList(0);
//2.new ArrayList(collection);且需要collection.toArray().length=0
private static final Object[] EMPTY_ELEMENTDATA = {};

//一种情况 new ArrayList();
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//实际存储所用数组
transient Object[] elementData; // non-private to simplify nested class access
//Arraylist长度
private int size;

扩容机制

add 方法

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

判断是不是空构造器创建,是的话更新 minCapacity

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//是否是默认的空数组
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }//默认初始化最小的容量10,超过10则更新
    ensureExplicitCapacity(minCapacity);
}

确保容量大小足够,否则扩容

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

扩容为1.5倍容量大小,然后判断是否达到要求,未达到要求再次更新,超过最大容量则置为最大

然后将原数组内容全部复制到新的数组中,并更新 elementData。

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 一样的特点是,线程不安全和 iterator fast-fail 机制

(This class is roughly equivalent to Vector, except that it is unsynchronized.)

Vector的不同的还有vector的扩容是2倍。

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

stack – 后进先出

实际上 stack 是继承了 Vector 然后实现了能体现栈操作的5个方法,和Vector本质上没有什么不同

那我们就直接看 Vector 和 ArrayList 有什么不同吧.

1.线程安全

2.扩容不同

  1. elements 方法是 ArrayList 没有的,elements 方法不是 fail-fast
public Enumeration<E> elements() {
        return new Enumeration<E>() {
            int count = 0;

            public boolean hasMoreElements() {
                return count < elementCount;
            }

            public E nextElement() {
                synchronized (Vector.this) {
                    if (count < elementCount) {
                        return elementData(count++);
                    }
                }
                throw new NoSuchElementException("Vector Enumeration");
            }
        };
    }

这个方法大概是很早前就实现的1.0,jdk 1.2开始才有iterator的,并且 Vector 早期是作为向量类,后面才实现的List 接口。

Vector 除非是需要特殊处理并发,不然优先使用 ArrayList。

队列

Queue – 先进先出

Queue 接口时队列的顶级接口,该接口的实现主要有

1.Deque 双向队列 2.BlockingQueue ( JUC 包中)阻塞队列

//TODO

哈希表

HashMap

直接看注释

实现Map接口。 允许null值和null键。

HashMapHashtable 很像,除了它是不同步的,并允许null。不能保证Map的顺序,而且不能保证随着时间变化顺序不变化。

容量是哈希表中的桶数量, 负载因子是在哈希表容量自动增加之前,能装多满的度量系数。

假设哈希函数能将元素正常散列到表中,那么基本操作的时间花费会是常数时间。 Map的迭代需要的时间与桶大小和键值对数量之成比例 ,所以如果需要使用迭代器,不要将负载因子设的过高或过低。

扩容方面:

当在散列表中的条目的数量超过了负载因子和容量的乘积,哈希表被重新散列 (内部数据结构被重建),使得哈希表具有桶的大约两倍。

默认负载因子(0.75)在时间和空间开销中取得了较好的权衡。在设置其初始容量时,应考虑预期的数目和负载因子,使得重新散列的次数最小化。 如果初始容量大于最大条目数除以负载因子,则不会发生重新排列操作。

如果存储的数目很多,那么创建足够大的容量会比它自动扩容的效率搞得多。

若是多个key的哈希值 hashCode 相同将会影响哈希表的功能。为了降低影响,当 keyComparable 时,这个类可以使用key之间的比较顺序来打破。

再看看成员变量

	//第一次用到的时候初始化。必要的时候调整大小。
     //分配大小时,长度都是2的幂
     //也可以为0,在引导技术暂时不需要用到的哪些操作中
    transient Node<K,V>[] table;

    //保存缓存的entrySet,AbstractMap 属性是给 keySet和 values 用的
    transient Set<Map.Entry<K,V>> entrySet;

    //键值对数量  (整个表扩容的依据)
    transient int size;

    //操作次数,保证 fail-fast
    transient int modCount;

     //表示调整大小时分配的大小值(容量*负载因子)
     //如果尚未分配表数组,则此字段保留初始数组容量,或零表示默认初始容量;
    int threshold;//阈值

    //负载因子
    final float loadFactor;

几个重要的指标

	//默认的初始化容量16                      //必须为2的幂
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    //最大容量。
	//任何一个带参数的构造函数隐式指定更高的大小,都会使用此容量。
	//必须是2的幂<=1<<30。
    static final int MAXIMUM_CAPACITY = 1 << 30;

    //在构造器中没有指定负债因子时,使用这个默认负载因子(0.75)
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    //这个值指定了跨入用树替换链表所需的门槛。(即链表转树的时机)
    //当添加元素到超过这个值的时候,槽会从表抓换成树。
    //为了符合在收缩时将树移除转换回普通槽的情况 ,这个值必须大于二且建议最少为8。
    static final int TREEIFY_THRESHOLD = 8;

    //这个值指定了移除树换回链表结构的阈值(即树转链表的时机)
	//为了符合在移除树中缩小检测的情况,需要小于树状化的阈值,并且最大为6
    static final int UNTREEIFY_THRESHOLD = 6;

    //可以转换成树结构的最小表大小(不然表会因为单个槽中太多节点而重新调整大小)
    //为了避免重新调整大小和树化阈值冲突,这个值至少要是 4 * TREEIFY_THRESHOLD 
    static final int MIN_TREEIFY_CAPACITY = 64;

hash算法,取key的hashCode异或hashCode低16位,为什么这么做源码注释也说的挺清楚的->hash计算注释

同时这里也说明了需要覆写 hashCode 方法的其中一个原因 -> 为什么HashMap需要覆写 equals 和 hashCode

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

key

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)//判断是否是空表或者长度为0
        n = (tab = resize()).length;					//重新分配表
    if ((p = tab[i = (n - 1) & hash]) == null)		//*1*
        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;
}

1.这里说明 HashMapkey 的方式:

取key的hash码和表的长度-1 的结果作为 key 在表中的位置。(也就是取hash值的低几位作为 key 的位置)

然后就是如何插入的过程了–>HashMap插入的几种方法

然后如果表键值对数量到达阈值,就会重新调整表。

LinkedHashMap

//TODO

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值