数组
概述
- 数组是相同类型数据的有序集合。集合中的数据称作元素,每个元素可以通过一个索引(下标,从0开始)来访问
特点
- 数组一旦进行初始化后,长度就固定下来了,不可以在改变
- 数组存储的元素必须是相同数据类型的数据
- 数组可以存储任何数据类型,即可以存储基本类型、也可以存储引用类型的数据
- 数组是引用类型对象
声明
方式一【推荐使用】
格式:数据类型[] 变量名称 ;
int[] arr;
方式二
格式:数据类型 变量名称[];
int arr[];
初始化
静态初始化
- 指定数组中每个元素的初始值,数组长度由JVM决定
- 格式:数组类型[] 变量名称 = new 数组类型{元素一、元素二…};
int[] arr = new int[]{1,2...}
动态初始化
- 定义时指定数组长度,数组中每个元素的的默认值由JVM进行初始化赋值
- 补充:JVM对数组元素默认值的初始化
- 整数类型:默认值 0
- 小数类型:默认值 0.0
- 字符类型:默认值 \u0000【空格】
- 布尔类型:默认值 false【二进制中0代表假、即false】
- 引用类型:默认值 null
- 格式:数组类型[] 变量名称 = new 数组类型[10];
int[] arr = new int[10]
集合
单元素集合【Collection】
特点
- 存放独立元素的容器
有序集合【List】
特点
- 有序集合、元素可重复
- 使用
动态数组
、替换原有不可变的数组
实例一【ArrayList】
特点
- List接口的主要实现类
- 内部是用线性动态数组结构实现
- 线程不安全
- 底层使用Object[] elementData数组结构存储数据
- 查询效率较高,增删效率较低,原因如下
- 查询快:根据数组中下标索引查询,复杂度为O(1)级别
- 插入、删除慢,由于底层使用的是Object[]数组存储元素,在操作非最后一个元素时,会涉及到要操作数组中i索引后的所有元素的移动、当数组长度不够时,还会涉及到扩容、数组元素复制等操作,这些操作往往会很耗时,所以操作时效率不高
源码分析
JDK7
初始化操作:ArrayList arr = new ArrayList();
public ArrayList() {
this.elementData = new Object[10];
}
add操作
- 该方法的核心为扩容机制,与jdk8类似【参考JDK8的解析】
JDK8
初始化操作:ArrayList arr = new ArrayList();
/**
* Constructs an empty list with an initial capacity of ten.
* 空参构造器会创建一个初始化为0的Object[]数组,这里的注释有问题,应该是jdk7的注释
*
* 备注
* 1.private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; ==> 空数组
* 2.transient Object[] elementData; // 存储元素的数组集合
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
add操作
- 第一次【不扩容场景】
/**
* 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})
*
* 备注:
* 1.private int size; ==> 初始化为0
*/
public boolean add(E e) {
// size + 1 => 0 + 1 = 1
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* @param minCapacity ⇒ 1
* 备注:
* 1.private static final int DEFAULT_CAPACITY = 10;
*/
private void ensureCapacityInternal(int minCapacity) {
//由初始化操作可以得到下面判断为true
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//由于DEFAULT_CAPACITY = 10 ,minCapacity = 1 ==> minCapacity = 10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
/**
* @param minCapacity ⇒ 10
* 备注:
* 1.protected transient int modCount = 0;
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code ==> 10 - 0 > 0 成立
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【10】 the desired【渴望】 minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; ==》 0
int newCapacity = oldCapacity + (oldCapacity >> 1); ==》0
// 0 - 10 < 0 成立 newCapacity ==》 10
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 为 长度为10的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* @param original elementData
* @param newLength 10
*/
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
/**
* @param original elementData
* @param newLength 10
* @return 长度为10的数组
*/
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
// original.length ==》 0 ; newLength ==》 10 ==>结果为:10
Math.min(original.length, newLength));
return copy;
}
- 第n次【扩容场景】
/** 前面方法都一样,这里以添加第11个元素为例
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity【11】 the desired minimum capacity
*
* 备注:
* 1.private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
*/
private void grow(int minCapacity) {
// overflow-conscious code 10
int oldCapacity = elementData.length;
// 10 + 5 ⇒ 15【这一步是扩容长度的代码实现 --》 扩容值为原数组长度的一半】
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 15 - 10 = 5 < 0 不成立
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 15 - 10 = 5 > Integer.MAX_VALUE - 8 不成立
if (newCapacity - MAX_ARRAY_SIZE > 0)
//最大容量计算逻辑【最大值为整数的最大值】:
//return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//参数一:原数组集合、新数组的长度
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* @param original 长度为10的元素组
* @param newLength 新生产数组的长度
*/
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
//生产长度为15的新数组
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
//这里会将原来数组中的元素copy到新生产的数组中
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
小结
- jdk7中的ArrayList的对象的创建类似于单例模式中的恶汉式;
- jdk8中的ArrayList的对象的创建类似于单例模式中的懒汉式,延迟了数组的创建、节省了内存
实例二【LinkedList】
特点
- 内部使用Node双向链表的数据结构存储数据
- 线程不安全
- 查询效率低:没有下标索引的概念,查询时会按照索引数据进行前向和后向遍历,复杂度为O(n)
- 插入、删除效率高:由于底层使用的是双向链表结构,当操作一个元素时,最多只会涉及2个元素的操作,将当前元素前后的两个节点元素引用地址变更即可,将当前元素的前一个元素Node节点的下一个节点引用当前Node地址,将当前元素的下一个元素Node节点的上一个节点引用指向当前节点即可
源码分析
初始化:List list = new LinkedList();
/**
* Constructs an empty list.
*
* 备注:
* 1.LinkedList类属性一:first节点
* Pointer to first node. Invariant: (first == null && last == null) || (first.prev == null && first.item != null)
* first == null && last == null ==> 只有一个元素情况
* first.prev == null && first.item != null ==> 有多个元素情况
* transient Node<E> first;
*
* 1.LinkedList类属性二:last节点
* Pointer to first node. Invariant: (first == null && last == null) || (first.next == null && first.item != null)
* first == null && last == null ==> 只有一个元素情况
* first.next == null && first.item != null ==> 有多个元素情况
* transient Node<E> last;
*/
public LinkedList() {
}
/**
* Node内部类
*/
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;
}
}
- 内部声明了Node【内部类】类型的first和last属性,默认值为null
Add()方法
/**
* 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})
*
* 备注:
* 1.实际调用的是linkLast(e)方法
*/
public boolean add(E e) {
linkLast(e);
return true;
}
#linkLast(e);方法
/**
* Links e as last element.
* 备注:
* 1.last:transient Node<E> last; ==> 添加第一个元素时为null,添加其他元素时为该集合最后一个添加进去的元素【默认情况下】
* 2.transient int size = 0;
* 3.protected transient int modCount = 0;
*/
void linkLast(E e) {
//null
final Node<E> l = last;
// newNode: prev = null ; next = null ; item = e
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//添加第一个元素时成立
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
实例二【Vector】
特点
- 内部是线性动态数组结构,查询效率高
- 线程安全,使用synchronized对方法加锁实现,但相较于ArrayList效率会稍低
- JDK1.0时出现,早于JDK1.2时出现的List接口
- 底层数据结构与ArrayList相同,也是使用 Object[] elementData存储数据
源码分析
初始化:
/**
* Constructs an empty vector so that its internal data array has size {@code 10} and its standard capacity increment is zero.
*/
public Vector() {
this(10);
}
/**
* Constructs an empty vector with the specified initial capacity and with its capacity increment equal to zero.
*
* @param initialCapacity the initial capacity of the vector
* @throws IllegalArgumentException if the specified initial capacity is negative
*/
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
/**
* Constructs an empty vector with the specified initial capacity and capacity increment.
*
* @param initialCapacity ==> 10 the initial capacity of the vector
* @param capacityIncrement ==> 0 the amount by which the capacity is increased when the vector overflows
* @throws IllegalArgumentException if the specified initial capacity is negative
* 备注:
* protected Object[] elementData;
* protected int capacityIncrement;
*/
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//创建长度为10的Object[]数组
this.elementData = new Object[initialCapacity];
//0
this.capacityIncrement = capacityIncrement;
}
add()方法
/**
* Appends the specified element to the end of this Vector.
*
* @param e element to be appended to this Vector
* @return {@code true} (as specified by {@link Collection#add})
* @since 1.2
* 备注:
* 底层逻辑实现与ArrayList类型,不过与ArrayList最大的区别是该方法加了synchronized来解决多线程操作时的并发问题
*/
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
/**
* @desc 扩容逻辑
* 备注:
* 底层逻辑实现与ArrayList类型,不过与ArrayList最大的区别是该方法加了synchronized来解决多线程操作时的并发问题
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//这里会扩容为原数组集合的2倍【与ArrayList的1.5倍不同】
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
小结
- 与ArrayList相比扩容大小不同:ArrayList扩容为原来的1.5倍,Vector扩容为原来的2倍
- 初始化时实现与ArrayList的JDK7版本类似,直接创建了长度为10的数组,并没有采用‘懒加载’的方式
- 与ArrayList相比在方法上都加了synchronized来解决并发问题
实例二【Stack】
特点
- Stack类继承自Vector,实现的是一个栈的数据结构。Stack刚创建后是元素是空的。Stack提供5个额外的方法使得Vector可以被当作堆栈使用:
- push()和pop()方法入栈和出栈;
- peek()方法用于得到栈顶的元素;
- empty()方法测试堆栈是否为空;
- search()方法检测某元素在堆栈中的位置;
- 由于Stack继承自Vector,自然也是由synchronized修饰过的同步容器。而除了这两个类,其他实现List接口的容器类并没有实现同步,在多线程场景下需要注意线程安全问题
源码分析
初始化
/**
* Creates an empty Stack.
* 备注:
* 这里调用父类方法创建底层数组
*/
public Stack() {
}
无序集合【Set】
特点
- 不可重复性
- 不可重复性前提是保证添加的元素按照equals()方法判断时,不能返回true【需要重写hashCode()方法】。即相同的元素只能添加一个
- 无序性
- 不是随机性,存储数据时底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值经过计算添加
实例一【HashSet】
特点
- 无序,底层内部是哈希表【本质是hashMap实例中的key】支持,不能保证Set集合中元素的迭代顺序
- 线程不安全
- 可以存储null值,且只能存储一个null值【验证:向空hashSet集合中添加两个null,输出hashSet的大小为1】,注意 向hashSet中添加元素,要求元素必须重新equals()和hashCode()方法;且必须保证一致性【实现对象相等规则:“相等的对象必须具有相等的散列码”】
- 底层存储结构:数组 + 链表
源码分析
初始化
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has default initial capacity (16) and load factor (0.75).
* 备注:
* 1.private transient HashMap<E,Object> map; ==> map为hashMap,所以hashSet底层是hashMap,元素保存在hashMap的key中
*/
public HashSet() {
map = new HashMap<>();
}
add(E e)
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element <tt>e</tt> to this set if
* this set contains no element <tt>e2</tt> such that
* <tt>(e==null ? e2==null : e.equals(e2))</tt>.
* If this set already contains the element, the call leaves the set unchanged and returns <tt>false</tt>.
*
* @param e element to be added to this set
* @return <tt>true</tt> if this set did not already contain the specified element
* 备注:
* 1.private static final Object PRESENT = new Object();
* 2.其实是调用hashMap的put方法,参考hashMap的put代码
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
实例二【LinkedHashSet】
特点
- hashSet的子类,对于频繁的遍历操作,LinkedHashSet效率高于HashSet
- LinkedHashSet按照插入排序,SortedSet可排序;遍历其内部数据时,可以按照添加的顺序遍历:在添加元素的同时,每个元素还维护了两个引用,记录了此数据前一个数据和后一个数据
源码分析
初始化
/**
* Constructs a new, empty linked hash set with the default initial capacity (16) and load factor (0.75).
*/
public LinkedHashSet() {
super(16, .75f, true);
}
/**
* Constructs a new, empty linked hash set. (This package private
* constructor is only used by LinkedHashSet.) The backing
* HashMap instance is a LinkedHashMap with the specified initial
* capacity and the specified load factor.
*
* @param initialCapacity the initial capacity of the hash map
* @param loadFactor the load factor of the hash map
* @param dummy ignored (distinguishes this
* constructor from other int, float constructor.)
* @throws IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
实例三【TreeSet】
特点
- TreeSet使用元素的自然顺序对元素进行排序【可按照添加对象的指定属性,进行排序】,或者根据创建set时提供的Comparator进行排序
- 向TreeSet中添加的数据,要求是相同类的对象
- 底层是红黑树
- 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals();定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()
源码分析
初始化
/**
* Constructs a new, empty tree set, sorted according to the
* natural ordering of its elements. All elements inserted into
* the set must implement the {@link Comparable} interface.
* Furthermore, all such elements must be <i>mutually
* comparable</i>: {@code e1.compareTo(e2)} must not throw a
* {@code ClassCastException} for any elements {@code e1} and
* {@code e2} in the set. If the user attempts to add an element
* to the set that violates this constraint (for example, the user
* attempts to add a string element to a set whose elements are
* integers), the {@code add} call will throw a
* {@code ClassCastException}.
*/
public TreeSet() {
this(new TreeMap<E,Object>());
}
add()
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element {@code e} to this set if
* the set contains no element {@code e2} such that
* <tt>(e==null ? e2==null : e.equals(e2))</tt>.
* If this set already contains the element, the call leaves the set
* unchanged and returns {@code false}.
*
* @param e element to be added to this set
* @return {@code true} if this set did not already contain the specified
* element
* @throws ClassCastException if the specified object cannot be compared
* with the elements currently in this set
* @throws NullPointerException if the specified element is null
* and this set uses natural ordering, or its comparator
* does not permit null elements
* 备注:
* 1.底层调用的是treeMap的put方法
*/
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
键值对集合【Map】
特点
- 存放key-value型的entry元素;是键值对集合,存储键、值和之间的映射;Key无序,唯一;value 不要求有序,允许重复
- jdk1.2出现
结构
key
- 无序的、不可重复的,使用Set存储所有的key
- 要求key所属的类重写hashcode()和equasl()方法【以hashMap为例,TreeMap是按照排序实现】
value
- 无序的、可重复的,使用Collection存储所有的value
- 要求value所属的类重写equasl()方法
entry
- 一个键值对【key-value】构成一个entry对象,map是将对应的entry对象存储到map中
- 无序的、不可重复的、使用Set存储所有的entry
实例一【HashMap】
特点
- 有序的Map集合实现类,相当于一个栈,先put进去的最后出来,先进后出
- map的主要实现
- 线程不安全,效率高
- 可以存储null的key和value
- 底层数据结构
- jdk7:数组 + 链表
- jdk8:数组 + 链表 + 红黑树
源码分析
JDK7
实例化: hashMap map = new HashMap()
- 在实例化时,底层创建了长度是16的一维数组Entry[] table
put(k1,v1):
- 调用k1所在类的hashCode()计算k1的哈希值,使用该哈希值经过算法计算后,得到在Entry[] table数组上的存放位置
- k1所要存放位置为空,此时将k1、v1封装成对应的Entry,并将该Entry添加到Entry[] 数组中
- k1所要存放位置不为空【意味着此位置上存在一个或多个以链表形式存放的数据】,比较k1和已经存在该位置上数据的哈希值
- k1的哈希值与已经存在的数据的哈希值都不相同,将k1、v1封装成Entry,并将该Entry添加到Entry[]数组中
- k1的哈希值与已经存在数据的哈希值相同,则比较k1所在类的equals()方法
- 如果equals()方法返回false:将k1、v1封装成Entry并保存到Entry[] 数组中
- 如果equals方法返回true:将对应位置上Entry中的value进行替换,也就是v1替换原有的value值
扩容机制:
- 扩容条件:存放元素的索引个数超出临界值【数组长度 * 负载因子:默认0.75】,且存放索引位置上不为空时
- 扩容大小:默认扩容为原来的2倍,并将原有的数据复制过来
JDK8
实例化: hashMap map = new HashMap()
- 底层没有创建长度为16的数组
- 底层的数组为:Node[],而非Entry[]
- 首次调用put()方法时,底层才会创建长度为16的数组
- jdk7底层结构只有:数组+链表。jdk8底层结构:数组+链表+红黑树;当数组的某一个索引位置上的元素以链表形式存放的数据个数 > 8 且当前数组的长度 > 64时,此时该索引位置上的数据改为使用红黑树存储
- 相关概念
- 默认初始化容量【DEFAULT_INITIAL_CAPACITY】
- static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
- 默认的加载因子【DEFAULT_LOAD_FACTOR】
- static final float DEFAULT_LOAD_FACTOR = 0.75f;
- 扩容的临界值【int threshold】
- 容量 * 加载因子 默认值:12
- 数组Bucket数组中链表长度大于该默认值,转化成红黑树,static final int TREEIFY_THRESHOLD = 8
- 由红黑树转成链表的的长度:static final int UNTREEIFY_THRESHOLD = 6
- 数组中的Node被树化时最小的hash表容量【数组长度】:64
- 默认初始化容量【DEFAULT_INITIAL_CAPACITY】
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity (16) and the default load factor (0.75).
* 备注
*
* final float loadFactor; //负载因子
* static final float DEFAULT_LOAD_FACTOR = 0.75f; //负载因子默认大小
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
put(k v):
node内部类
//继承Map的Entry接口
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 V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//重新hashCode和equals方法
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
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()方法【第一次或N次不会索引碰撞场景且不扩容情况】
- 第一次执行put()会执行resize()方法对HashMap底层存储数据的Node[16]的数组初始化
- 非第一次执行put()方法则不会执行resize()方法
public V put(K key, V value) {
//根据key求hash值
return putVal(hash(key), key, value, false, true);
}
/**
* 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
* 备注
* transient Node<K,V>[] table;
* transient int modCount; ==> 0
* transient int size; ==》 0
* transient int modCount; ==》 0
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
//声明Node【内部类】类型的tab、p、和初始值为0的i、n
Node<K,V>[] tab; Node<K,V> p; int n, i;
//第一次put table = null 赋值给tab变量 ==》 判断tab == null 成立 ==》 进入resize()方法
if ((tab = table) == null || (n = tab.length) == 0)
//n ⇒ 16 ; tab为大小为16的Node数组
n = (tab = resize()).length;
//第一次时 p == null 成立 ,tab[15 & key的hash] = null
//这里是底层数组上添加时对应索引位置没有任何元素的场景
if ((p = tab[i = (n - 1) & hash]) == null)
//在tab[i]的位置上创建一个Node,且下一个node元素为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 = 1
++modCount;
//第一次 :1 > 12 不成立 ==》不会走resize
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
resize()方法
/**
* 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.
* 备注
* 1.int threshold; ==》 0
* 2.static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
* 3.static final float DEFAULT_LOAD_FACTOR = 0.75f;
* 4.第一次调用会返回大小为16的Node类型数组
*/
final Node<K,V>[] resize() {
//这里为null
Node<K,V>[] oldTab = table;
//oldCap ==》 0
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//oldThr ==》 0
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
//第一次put值时会进入这个分支
//newCap ==》 16 ; newThr ==> 12
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);
}
//这里将12赋值给扩容时临界值
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//创建一个Node类型大小为16的数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//第一次put不会进
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;
}
put()方法【第N次会索引碰撞场景且不扩容情况】
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
//声明Node【内部类】类型的tab、p、和初始值为0的i、n
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//第n次时 p = tab[i] ==> p为当前所以上数组的值
//这里是底层数组上添加时对应索引位置没有任何元素的场景
if ((p = tab[i = (n - 1) & hash]) == null)
//在tab[i]的位置上创建一个Node,且下一个node元素为null
tab[i] = newNode(hash, key, value, null);
else {//这里是底层数组上添加时对应索引位置有元素的场景
Node<K,V> e; K k;
//情况一:数组中的元素hash 与 【数组中的元素key与要添加的key相同 或 key的equals()方法相同】 时 ,将p的值赋给e
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 {
//情况二:hash值不相同 或 key值不相同情况 或 hash和key值都不相同
for (int binCount = 0; ; ++binCount) {
//e为p的下一个元素且为空
if ((e = p.next) == null) {
//根据put方法参数创建一个新Node,将p的下一个节点执行新Node即可
p.next = newNode(hash, key, value, null);
//static final int TREEIFY_THRESHOLD = 8; == 条件不成立不需要转换为红黑树 ==》 打断循环
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//这里是链表转红黑树逻辑
treeifyBin(tab, hash);
break;
}
//e为p下一个元素不为空情况,与要添加的元素相同 == 打断循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//如果上述两种情况都不成了,将e元素赋值给p接着找下一个元素
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;
//第n次 :n > 12 成立 ==》走resize 扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
扩容resize()【第一次扩容】
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
//16
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//12
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
//static final int MAXIMUM_CAPACITY = 1 << 30; ==》 1073741824
if (oldCap >= MAXIMUM_CAPACITY) { //不成立
threshold = Integer.MAX_VALUE;
return oldTab;
}
//newCap = 16 * 2 = 32 ;
//oldCap << 1 ==》 24 < 1073741824 && 16 >= 16 成立
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//newThr = oldThr * 2 ; == 24
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 24 == 0 不成立
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
// 扩容临界值为24
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//创建大小为32的Node数组
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;
//将数组上的元素赋值给e
if ((e = oldTab[j]) != null) {
//将原数组上对应索引位置上置为null
oldTab[j] = null;
//如果e上只有一个元素时,直接根据新的数组大小32和元素的hash求&得到新数组中所在的索引位置
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
//如果e上有多个元素时场景
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
//取e的下一个元素
next = e.next;
//e为原数组上的第一个元素
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;
}
扩容resize()转红黑树
/**
* Replaces all linked nodes in bin at index for given hash unless
* table is too small, in which case resizes instead.
* 备注
* tab hashMap存储数据的Node[]数组
* hash 要保存数据的hash值
*/
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//static final int MIN_TREEIFY_CAPACITY = 64;
//tab为空 或 tab的长度的长度 < 64 ==>走扩容逻辑
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
// 数组长度>64 且 要新增的元素与65求&得到要存储数组中的位置不为空 ⇒ 将数组转换为红黑树
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
实例二【Hashtable】
特点
- 内部存储的键值对是无序的,是按照哈希算法进行排序
- map的早期实现类,JDK1.0就有,在map接口之前出现
- 线程安全,效率低
- 键或者值不能为null,为null就会抛出空指针异常
实例二【properties】
特点
- 是Hashtable的子类
- 常用来处理配置文件
- key和value都是String类型
实例三【TreeMap】
特点
- 基于红黑树(red-black tree)数据结构实现,按key排序,默认的排序方式升序
- 保证按照添加的key-value对进行排序,按照key实现排序遍历