集合
1、集合概述
1.1 集合的概念
集合可以看作是一种容器,用来从出对象信息。所有集合类都为于java.util.concurrent包下。
1.2 常用集合的继承关系
java集合都是由Collection接口、Map接口派生出的,通过各层抽象类、接口的扩展可实现不同特性的集合子类,在实际业务中需要通过实际情况选择使用哪种集合。


1.3 集合与数组区别
- 数组是静态的,实例化时就声明了其长度,之后无法扩容。而集合可以根据需求动态的改变容量,操作更加灵活、方便。
- 数组的存放类型只能是一种(基本类型/引用类型),而集合存放的类型是Object,在不加泛型的情况下可以存放不同类型的对象(实际上也都属于Object)。
- 数组是java内置的数据类型,是线性排列的,执行效率更高。
2、Collection接口
存储一组Object对象,实现了Iterable接口,其子类通过迭代器进行遍历。
2.1 接口方法
添加元素:boolean add(E e);
添加集合:boolean addAll(Collection<? extends E> c);
删除某个对象:boolean remove(Object o);
删除参数集合中的所有对象:boolean removeAll(Collection<?> c);
清空集合:void clear();
Iterable迭代器进行遍历:Iterator iterator();
Iterator iterator = arrayList.iterator();//获取迭代器
while (iterator.hasNext()){//从-1开始,判断是否还有下一个
//遍历输出元素
System.out.println(iterator.next());
}
获取元素个数:int size();
判断是否为空:boolean isEmpty();
判断是否包含某个元素:boolean contains(Object o);
集合转为数组:Object[] toArray();
删除不属于Collection对象的所有元素:boolean retainAll(Collection<?> c);
判断对象引用是否相等:boolean equals(Object o);
2.2 List接口
有序集合,此接口的具体实现类是有序的,可以通过索引的位置获取、插入元素
2.2.1 接口方法
获取指定位置的元素:E get(int index);
更新指定位置元素:E set(int index, E element);
更新指定位置元素:E set(int index, E element);
获取子类迭代器:ListIterator listIterator();(支持向前遍历,不常用)
2.2.2 接口实现类
(1)ArrayList
特点:底层为数组;查询速度快;增删速度慢(涉及创建新数组、复制数组等工作);效率高但线程不安全
构造函数:
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
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;
}
}
- public ArrayList();初始化存储数组长度为0;添加第一个元素时初始化为10;当添加的对象要超过10时,以1.5倍容量扩容
- public ArrayList(int initialCapacity);初始化数组长度为initialCapacity;当添加的对象要超过initialCapacity时,以1.5倍容量扩容
扩容逻辑:
//扩容机制分析,执行add方法时:
public boolean add(E e) {
//1、调用ensureCapacityInternal方法,size为当前list对象中存储的对象个数
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//2、返回要添加这个新元素的最小长度。elementData长度为0时,返回10,否则返回当前存入数据的数量+1
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断elementData是不是空数组,即是不是第一次添加
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//返回长度10
return Math.max(DEFAULT_CAPACITY, minCapacity);//private static final int DEFAULT_CAPACITY = 10;
}
//后面添加返回需要的长度
return minCapacity;
}
//3、比较最小长度和当前数组的总长度,最小长度大于现有长度时,需要进行扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)//elementData为当前list对象
grow(minCapacity);
}
//4、传入最小长度对现有数组进行扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//当前数组长度
int newCapacity = oldCapacity + (oldCapacity >> 1);//获取当前数组长度的1.5倍数
if (newCapacity - minCapacity < 0)//当前数组长度的1.5倍数 比 期望长度小的情况(初始化未指定长度第一次添加时)
newCapacity = minCapacity;//新数组长度为 期望长度
if (newCapacity - MAX_ARRAY_SIZE > 0) //新数组长度为大于允许的最大长度情况下
newCapacity = hugeCapacity(minCapacity);//设置新数组长度为MAX_ARRAY_SIZE、newCapacity中大的值。
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//按照新数组的长度创建新的数组,并将之前保存的数据复制进来
}
(2)Vector
特点:线程安全。其他与ArrayList类似
构造函数:
/**
* Constructs an empty vector with the specified initial capacity and
* capacity increment.
*
* @param initialCapacity the initial capacity of the vector
* @param capacityIncrement the amount by which the capacity is
* increased when the vector overflows
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
/**
* 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 so that its internal data array
* has size {@code 10} and its standard capacity increment is
* zero.
*/
public Vector() {
this(10);
}
/**
* Constructs a vector containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this
* vector
* @throws NullPointerException if the specified collection is null
* @since 1.2
*/
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);
}
- public Vector();初始化存储数组长度为10;当添加的对象要超过10时,以2倍容量扩容
- public Vector(int initialCapacity) ;初始化存储数组长度为initialCapacity;当添加的对象要超过数组长度时,以2倍容量扩容
- public Vector(int initialCapacity, int capacityIncrement); 初始化存储数组长度为initialCapacity;容量增量为:capacityIncrement;当添加的对象要超过数组长度时,容量扩容为:数组原长度+capacityIncrement
扩容逻辑:
//elementCount为当前vector对象中存储的对象个数
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
//比较最小长度和当前数组的总长度,最小长度大于现有长度时,需要进行扩容
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//扩容,默认扩容为原来数组长的2倍
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//capacityIncrement再初始化时,默认设置为0;故默认情况下
//newCapacity = oldCapacity+oldCapacity,即默认扩容两倍
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);
}
(3)Stack
特点:是Vector的子类。栈,实现了标准的先进先出
特殊方法:
堆栈是否为空:boolean empty() ;
查看堆栈顶部的对象,但不从堆栈中移除它:Object peek( );
移除堆栈顶部的对象,并作为此函数的值返回该对象:Object pop( );
把项压入堆栈顶部:Object push(Object element);
查找对象在堆栈中的位置:int search(Object element); 栈顶为1,栈底为站的高度
Tips:Stack继承了Vector类,在初始化时,会执行Vector的无参构造,底层与Vector一样将对象保存在数组中,默认长度是10,长度不够时,扩容机制与Vector一致。
(4)LinkedList
特点:底层使用双链表实现,增删效率快,但查询需要遍历寻找效率较慢。线程不安全。对象维护first、last两个指针,分别指向两表的头、尾节点;
构造函数:
/**
* Constructs an empty list.
*/
public LinkedList() {
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
特殊方法:(头尾的增删查)
链表头增加节点:public void addFirst(E e) ;
链表尾增加节点:public void addLast(E e);
链表头删除节点:public E removeFirst() ;
链表尾删除节点:public E removeLast() ;
链表头获取节点:public E getFirst();
链表尾获取节点:public E getLast() ;
增加逻辑:
//实质为双向链表
public boolean add(E e) {
linkLast(e);
return true;
}
//往链表中添加的逻辑
void linkLast(E e) {
//类last指针指向的节点,也就是未添加之前的最后一个节点,如果是第一次添加则指向null
final Node<E> l = last;
//通过添加的对象生成新的节点,前置节点是l(第一个添加的元素也是null),后置节点为null
final Node<E> newNode = new Node<>(l, e, null);
//将last指针指向新的节点
last = newNode;
if (l == null)//如果l为null(即添加第一个节点)first指针也指向新节点
first = newNode;
else//l不为空将l的next指向新节点
l.next = newNode;
size++;//数量加1
modCount++;
}
删除逻辑
public E remove(int index) {
checkElementIndex(index);//判断index有没有越界
return unlink(node(index));
}
//通过index位置尽可能块的找到要删除的Node(判断从头找或从尾找,这里必须一个一个找,所以链表的查询比较慢)
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;
}
}
//删除操作
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;//获取要删除节点的值
final Node<E> next = x.next;//得到要删除节点的后置节点
final Node<E> prev = x.prev;//得到要删除节点的前置节点
if (prev == null) {//前置节点为空,即删除的时第一个节点
first = next;//将first指针指向下一个节点即可
} else {//不为空时,前置接点的后直接点修改为当前节点的后置节点;当前节点的前直接点置为null
prev.next = next;
x.prev = null;
}
if (next == null) {//后直接点为空,即删除的时最后一个节点
last = prev;//last指针指向要删除节点的前置接点
} else {//不为空时,后直节点的前置节点指向要删除节点的前置节点;当前节点的后置节点置为null
next.prev = prev;
x.next = null;
}
x.item = null;//当前节点的值设置为null,后由垃圾回收器回收
size--;
modCount++;
return element;//返回删除节点的值
}
2.2.3 选择那个实现类?
- 对线程安全有要求:Vector;
- 增删操作较多时使用LinkedList,查询较多使用ArrayList。原因:ArralyList删除元素后会导致数组不连续,底层数组要进行复制操作,添加在数组容量溢出时产生扩容+数组复制的开销;
- 一般情况下使用ArrayList即可;
2.3 Set接口
元素唯一、无序(不能使用索引获取元素)。
2.3.1 接口方法
获取set大小:int size();
判断是否为空:boolean isEmpty();
判断是否包含某个元素:boolean contains(Object o);
转为数组:Object[] toArray(); T[] toArray(T[] a);
增加元素:boolean add(E e);
删除元素:boolean remove(Object o);
判断是否包含一个Collection的所有元素:boolean containsAll(Collection<?> c);
增加整个Collection对象的所有元素:boolean addAll(Collection<? extends E> c);
删除不属于Collection对象的所有元素:boolean retainAll(Collection<?> c);
删除属于Collection对象中的所有元素:boolean removeAll(Collection<?> c);
清空对象:void clear();
2.3.2 接口实现类
(1)HashSet
特点:
- 底层为HashMap;
- 无序性。由于HashMap底层存储方式通过hashCode计算才能得出落在数组的那个位置,故存取对象的顺序不是一致的,即是无序的;
- 唯一性。存储的对象实际存储在HashMap中key的位置,故存入的元素不可以重复,可以有一个null;
构造函数:
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
/**
* Constructs a new set containing the elements in the specified
* collection. The <tt>HashMap</tt> is created with default load factor
* (0.75) and an initial capacity sufficient to contain the elements in
* the specified collection.
*
* @param c the collection whose elements are to be placed into this set
* @throws NullPointerException if the specified collection is null
*/
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* 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
* @throws IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive
*/
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* the specified initial capacity and default load factor (0.75).
*
* @param initialCapacity the initial capacity of the hash table
* @throws IllegalArgumentException if the initial capacity is less
* than zero
*/
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
/**
* 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);
}
- public HashSet();
构造函数内操作:map = new HashMap<>();
- public HashSet(Collection<? extends E>);
构造函数内操作:map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
创建HashMap对象传入初始容量,再将Collection的值循环添加到这个对象,及将Collection的值复制到这个set中
- public HashSet(int,float);
构造函数内操作:map = new HashMap<>(initialCapacity, loadFactor);
创建HashMap对象传入初始容量、负载因子(负载因子可看作一个百分比,当map的容量到达当前容量*负载因子的值时,map进行扩容。默认负载因子的值时0.75f)
- public HashSet(int);
构造函数内操作:map = new HashMap<>(initialCapacity);
- public HashSet(int float,boolean);
构造函数内操作:map = new LinkedHashMap<>(initialCapacity, loadFactor);
创建一个LinkedHashMap对象,loadFactor是什么??
特殊方法:
源码增加逻辑:
public boolean add(E e) {
//add方法内部实际上是对map这个对象添加一项,添加的对象作为map的key,map的值都是PERRESENT(一个空的Object:private static final Object PRESENT = new Object();)
return map.put(e, PRESENT)==null;
}
由于HashSet底层是通过HashMap实现的,所以其他关于扩容等源码在HashMap介绍章节再详细介绍
(2)TreeSet
特点:
- TreeSet是一个有序的集合,它的作用是提供有序的Set集合。
- TreeSet是基于TreeMap实现的
- TreeSet的元素支持2种排序方式:自然排序或者根据提供的Comparator进行排序。
构造函数:
/**
* Constructs a set backed by the specified navigable map.
*/
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
/**
* 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>());
}
/**
* Constructs a new, empty tree set, sorted according to the specified
* comparator. All elements inserted into the set must be <i>mutually
* comparable</i> by the specified comparator: {@code comparator.compare(e1,
* 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, the
* {@code add} call will throw a {@code ClassCastException}.
*
* @param comparator the comparator that will be used to order this set.
* If {@code null}, the {@linkplain Comparable natural
* ordering} of the elements will be used.
*/
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
/**
* Constructs a new tree set containing the elements in the specified
* collection, sorted according to the <i>natural ordering</i> 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.
*
* @param c collection whose elements will comprise the new set
* @throws ClassCastException if the elements in {@code c} are
* not {@link Comparable}, or are not mutually comparable
* @throws NullPointerException if the specified collection is null
*/
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
/**
* Constructs a new tree set containing the same elements and
* using the same ordering as the specified sorted set.
*
* @param s sorted set whose elements will comprise the new set
* @throws NullPointerException if the specified sorted set is null
*/
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
- public TreeSet();
- TreeSet(NavigableMap<E,Object> m);
- public TreeSet(Comparator<? super E> comparator);
- public TreeSet(Collection<? extends E> c)
- public TreeSet(SortedSet s)
(3)LinkedHashSet
3、Map接口
3.1 概述
1.是一个双列集合,赋值的时候必须同时给key和value赋值
2.是一个无序的集合(存入和取出元素的顺序可能不一致)
3.key值不能重复,value可以重复
4.一个key只能对应一个vlaue
5.定义集合时,数据类型key和value可以使用相同的数据类型,也可以使用不同的数据类型
3.2接口方法
添加元素:V put(K key, V value);
删除元素:V remove(Object key);
清空整个集合:void clear();
修改元素:V put(K key, V value);
判断更新值与原值不一致时更新:default boolean replace(K key, V oldValue, V newValue) ;
包含此key就直接更新:default V replace(K key, V value);
通过key获取对应的值:V get(Object key);
通过key获取值,为空时返回默认值:default V getOrDefault(Object key, V defaultValue)
获取键值对数量:int size();
判断是否为空:boolean isEmpty();
判断是否包含此key:boolean containsKey(Object key);
判断是否包含此value:boolean containsValue(Object value);
复制整个Map:void putAll(Map<? extends K, ? extends V> m);
返回key的set集合(此set不支持add、addAll方法):Set keySet();
返回value集合(同样不支持add|、addAll方法):Collection values();
返回Set<Map.Entry<K, V>>:Set<Map.Entry<K, V>> entrySet();
返回hash值:int hashCode();
3.3接实现类
3.3.1HashMap
特点:
- 并允许使用 null 值和 null 键。
- 除实现了Map接口外还实现了Cloneable,Serializable,继承了AbstractMap抽象类
- 此类不保证映射的顺序
- (k,v)结构实际上是一个Node,实现了Map.Etry<K,V>
- jdk7.0的HashMap底层实现是:数组+链表;jdk8.0更新为:数组+链表+红黑树
构造方法:
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
* Constructs a new <tt>HashMap</tt> with the same mappings as the
* specified <tt>Map</tt>. The <tt>HashMap</tt> is created with
* default load factor (0.75) and an initial capacity sufficient to
* hold the mappings in the specified <tt>Map</tt>.
*
* @param m the map whose mappings are to be placed in this map
* @throws NullPointerException if the specified map is null
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
- public HashMap(int initialCapacity, float loadFactor)
通过初始化大小(initialCapacity需要是2的幂次方,不是也无妨底层代码会对初始化容量进行修正)、加载因子(loadFactor,默认0.75f,控制是否需要扩容数组)参数进行初始化;
Tip:loadFactor参数大于1并不会影响其正常存储和读取,只是超过长度后新加的Node会以链表的形式追加到相同的HahCode对应的位置,增加了hash碰撞的几率,会导致存储、读取效率低。
- public HashMap(int initialCapacity)
通过初始化大小进行初始化;
- public HashMap()
使用默认的initialCapacity(16)、loadFactor参数(.75f);
- public HashMap(Map<? extends K, ? extends V> m)
示例化的同时复制一个Map的内容;
方法解析
增加(put)方法源码
//调用putVal方法,通过hash()方法处理key对象的hash值
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//处理hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//table是hashMap的数组,判断其是否为空或长度是0,即判断是不是第一次添加
if ((tab = table) == null || (n = tab.length) == 0)
//如果是空的话,调用resize()方法进行初始化,设置数组长度为默认的16、临界值为16*0.75=12
n = (tab = resize()).length;
//通过 (n - 1) & hash 计算具体落在数组的哪一项上。如果是空,表示数组此位置还没有存储Node对象,添加的对象可以直接存储到数组中,可直接返回。否则需要进一步判断。
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//判断要加入的节点与上述数组位置的key值是否,相同的话将e对象指向 数组原有的节点 可进入下一个判断:if(e != null)
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//判断p节点是否是树节点
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//不是树节点、也不和数组节点相同,进入链表判断
for (int binCount = 0; ; ++binCount) {
//到达链表的尾节点,p的next指针指向此节点
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//判断是否达到树化的临界值,大于等于临界值(8)需要进行树化处理(此处也不一定会树化、还得判断数组的长度),这里-1是因为binCount是从0开始的
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果遇到key相同的,直接退出循环,进入下一个判断:if(e != null)
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//每次循环后,将p指向判断过的节点上,依次往后
p = e;
}
}
//e保存的是数组旧节点
if (e != null) { // existing mapping for key
V oldValue = e.value;
//onlyIfAbsent源码传参是false,或者旧节点的值为null,则将就节点的value置为要增加的节点的值,相当于值的更新,更新后直接返回更新后的Node节点。
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//增加元素的情况(非更新),会执行以下代码
//更新次数++
++modCount;
//判断元素的数量大于临界值时,进行数组扩容处理
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
//树化方法,
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//判断数组为空或数组长度小于MIN_TREEIFY_CAPACITY(64)时,先进行数组的扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
//从数组上的节点开始全部更新为TreeNode节点
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);
}
}
3.3.2LinkedHashMap
特点:
- 是HashMap的子类
- 与HashMap相比LinkedHashMap是有序的
- LinkedHashMap可以认为是HashMap+LinkedList,即它既使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。
LinkedHashMap中并没有什么操作数据结构的方法,也就是说LinkedHashMap操作数据结构(比如put一个数据),和HashMap操作数据的方法完全一样,无非就是细节上有一些的不同罢了。
LinkedHashMap只定义了两个属性:
/**
* The head of the doubly linked list.
* 双向链表的头节点
*/
private transient Entry<K,V> header;
/**
* The iteration ordering method for this linked hash map: true
* for access-order, false for insertion-order.
* true表示最近最少使用次序,false表示插入顺序
*/
private final boolean accessOrder;
构造方法:
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
特殊方法
添加元素时,使用HashMap的putVal()方法,那么怎么实现记录元素顺序的呢?通过查看对添加元素时调用的newNode方法可以看出改方法已经被LinkedHashMap重写了,除next属性外,LinkedHashMap.Entry还拥有head、tail、last、after属性来维护双向链表的结构,具体代码如下:
//LinkedHashMap.Entry节点定义
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
//头节点
transient LinkedHashMap.Entry<K,V> head;
//尾节点
transient LinkedHashMap.Entry<K,V> tail;
//再添加元素时,重写了newNode方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
//LinkedHashMap.Entry对象,继承于HashMap.Node
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
//将新创建的节点p作为尾结点tail,
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
//若原tail指向为空,即没有元素,那么可以认为它即是head节点,也是tail节点,此时节点p的before和after都为null
if (last == null)
head = p;
else {//否则,将新元素链接到链表的尾部。将当前尾节点p的前一个节点(before)设置为上一次的尾结点last,将上一次尾节点last的后一个节点(after)设置为当前尾结点p。通过此方法实现了双向链表功能,完成before,after,head,tail的值设置
p.before = last;
last.after = p;
}
}
其次:关于afterNodeAccess()方法,在HashMap中没给具体实现,而在LinkedHashMap重写了,目的是保证操作过的Node节点永远在最后,从而保证读取的顺序性,在调用put方法和get方法时都会用到。
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
//accessOrder在初始化时可以定义,为true时会将操作变更过的元素移动到链表的的尾部
if (accessOrder && (last = tail) != e) {
//p时变更的节点、b时p的前置节点、a时p的后置节点
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//p的after指向空
p.after = null;
//如果前置节点是空的,即变更的是头节点,则将头节点指向p节点的后置节点
if (b == null)
head = a;
else//否则,p节点前置节点的after指向p节点的后置节点(可以是null)
b.after = a;
if (a != null)//如果p的后置节点不为空,p的后置节点的before指向p的前置节点
a.before = b;
else//p的后置节点为null,表示p是尾节点,此时last指向p的前置节点。到此p节点被剔除到链表之外
last = b;
if (last == null)//如果last为null即b为null,表示链表中只有p一个元素,head指向p
head = p;
else {//否则将p节点链接到链表尾部
p.before = last;
last.after = p;
}
tail = p;//将尾指针指向p
++modCount;
}
}
get方法片段:
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);//查询完元素后,会执行afterNodeAccess方法,将查找的元素放到链表的尾部
return e.value;
}
put方法片段:
//插入元素时,发现key已存在时会运行此片段将旧的value覆盖掉
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
afterNodeInsertion方法:HashMap中没有具体实现
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
//获取头节点。removeEldestEntry方法默认直接返回false,可重写此方法,当满足条件时调用HashMap的removeNode方法,删除头节点。
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
//HashMap类删除节点的方法
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
//通过hash值查看落在hash表的位置是否为空,即是否有可能找到要删除的节点
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//判断是否就是hash表上的这个节点,是的话node直接指向p
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
//从红黑树中去找
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
//通过node节点的next属性循环查找该元素
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
//p指向的是查找节点的上一个节点
p = e;
} while ((e = e.next) != null);
}
}
//删除节点操作
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)//正好落在hash表上的情况
tab[index] = node.next;
else//在链表上的情况
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);//LinkedHashMap对删除后的节点进行指针的处理,HashMap改方法是空的。
return node;
}
}
return null;
}
//LinkedHashMap重写了afterNodeRemoval方法,处理伤处节点后的前后指针
void afterNodeRemoval(Node<K,V> e) { // unlink
//获取删除节点、删除节点的前置节点、后置节点
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//当前节点的前置、后置节点指向null
p.before = p.after = null;
if (b == null)//删除的节点是头节点
head = a;
else//正常情况
b.after = a;
if (a == null)//删除的节点是尾节点
tail = b;
else//正常情况
a.before = b;
}
3.3.4HashTable
特点:
- 存放的匀速是键值对,即K-V
- hashTable的键、值都不可以为空(会抛出空指针异常)
- 使用方法与HashMap一致
- hashTable时线程安全的,HashMap线程不安全
- 结构:数组+链表。不会树化,每次插入新节点都是插在链表头。
构造方法:
- 通过initialCapacity初始化大小、loadFactor加载因子实例化。构造过程中初始化的值包含:loadFactor、table、threshold(临界值,整个Hashtable中存放的节点到达临界值时会触发table的扩容)
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
- 通过initialCapacity初始化大小实例化,默认加载因子是0.75f
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
- 无参构造,默认数组初始化大小为11、加载因子为0.75f
public Hashtable() {
this(11, 0.75f);
}
- 通过Map接口的实例化对象来构造hashtable,数组长度在Map对象的长度*2和11中去较大值,默认加载因子为0.75f。构造完成后调用putAll方法将Map对象的键值对存入这个Hashtable对象。
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
方法解析
put方法:通过put方法添加新节点,其逻辑代码先获取key的hash值,判断这个key是否已经存在。存在时直接更新,否则通过addEntry()方法进行节点的添加
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
//通过hash值计算在tab的位置
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
//entry指向对应的位置,通过next循环遍历有无hash和key值相同的,相同的直接更新value即可
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
//增加entry节点
addEntry(hash, key, value, index);
return null;
}
addEntry方法:添加元素的方法会判断节点数量到达临界值时对数组进行扩容,而后再将新节点添加到Hashtable中。此处需要注意的是添加的新节点会直接放到数组上,充当这个位置链表的头节点。
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {//判断数组中的数据是否到了临界值
// Rehash the table if the threshold is exceeded
rehash();//扩容
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;//重新计算hash值
}
// Creates the new entry.
@SuppressWarnings("unchecked")
//获取index对应的entry节点
Entry<K,V> e = (Entry<K,V>) tab[index];
//将新节点插入到index处,同时通过构造方法将next指向e。这里与HashMap不同,Hashtable每次将新节点添加到tab上,即链表头上,通过next将原有的链表连接在其后。而HashMap是将新节点添加到链表尾部
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
rehash方法:节点数量到达临界值时对数组进行扩容。产生新数组后需要将旧数组原有的数据迁移过来。源码逻辑的实现方式采用了两层循环,第一层对数组进行循环遍历,第二层是对对应数组位置的链表进行遍历。与上述添加逻辑一致,调整节点位置时会将要调整的节点直接放在数组的位置,也就是链表头的位置,依次类推。
protected void rehash() {
int oldCapacity = table.length;//旧hashtable长度
Entry<?,?>[] oldMap = table;//旧hashtable
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1;//2倍+1
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
//定义新的map数组
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
//重新设置临界值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
//遍历table的每个位置
for (int i = oldCapacity ; i-- > 0 ;) {
//遍历table后的链表
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;//e指向当前节点
old = old.next;//old指向当前节点的下一个节点
//重新计算每个节点的index值
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
//当前节点next指向新的table位置
e.next = (Entry<K,V>)newMap[index];
//将当前节点保存在新的table位置,插入到链表的表头
newMap[index] = e;
}
}
}
3.3.5Properties
特点
- 继承HashTable,是Hashtable的子类
- 可通过load方法通过文件流等方式加载properties、xml文件到Properties对象中
构造函数
- 空构造,但是注意实例化时会调用父类HashTable的空构造,初始化table等属性。
public Properties() {
this(null);
}
- 传入Properties对象,设置defaults属性
public Properties(Properties defaults) {
this.defaults = defaults;
}
1633





