Java集合
Java 集合都在Java.util包下,分为两种Collection和Map
Collection:单列存储,只存储一个元素
Map:双列存储,键值对存储方式
单列集合体系结构
List:有序,可重复(实现类:ArrayList,LinkedList,Vector)
常用方法:
增:add(index,element),addAll(index,collection)
删:remove(index),removeAll(collection),clear()
改:set(index,element)
查:get(index),subList(from,to),listInerator(),indexOf(object),size()
判断:isEmpty(),contains(collection)
ArrayList
底层实现:动态数组
特点:线程不安全,效率高,因为有索引下标,所以支持随机访问,
查询快,增删慢,随机查询优于LinkedList
扩容机制:默认初始化是10,其实并不是,当第一次添加元素的时候长度扩展为10
如果使用无参构造初始化,会初始化一个长度为0的空集合,
如果使用带参构造初始化,那么去判断参数是否大于零,如果大于零,
那么参数就是指定的数组的长度,等于零的话,就去初始化一个空集合,
小于零的话,抛出异常IllegalArgumentException(非法长度)
核心是grow方法,数组的扩容机制,以当前数组的1.5倍进行扩容
/**
* ArrayList 源码分析
**/
//默认的初始化容量
private static final int DEFAULT_CAPACITY = 10;
//初始化一个空集合长度为0
private static final Object[] EMPTY_ELEMENTDATA = {};
//初始化一个空集合长度为0
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储ArrayList元素的数组缓冲区,当第一次添加元素时,长度扩展为默认长度就是10
transient Object[] elementData;
//实际数组长度默认为0
private int size;
//数组最大长度Integer的最大值减去8,超出的话,JVM就不在给数组分配,抛出OutOfMemoryError
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//带参构造
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);
}
}
//无参构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//扩容机制
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//当前数组长度>>向右移1位,就是除2,就是原来大小+原来大小的一半就是1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//判断如果扩容后的值小于指定的参数值,那么将扩容值变为参数值
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//判断如果扩容后的值是否大于JVM能分配的最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//copy数组,改变容量大小
elementData = Arrays.copyOf(elementData, newCapacity);
}
//构造一个包含指定元素的集合 不常用
public ArrayList(Collection<? extends E> c) {
//转换到当前集合
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//判断如果不是object类型是别的类型,需要当前集合处理一下,
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
LinkedList
单向链表:每一个元素都知道自己的下一个元素是谁,是单方向的
双向链表:每个元素都知道自己的上一个元素和下一个元素是谁,双方向
底层实现:双向链表
特点:链表中每一个元素称为Node对象,LinkedList是链表实现,存储过程中,每一个Node
对象都有自己的前驱和后继,当然也可以定位增删改查,只不过它不能像ArrayList一
样直接定位元素,只能从第一个元素或最后一个元素两个方向向后循环遍历查找,效率低
。下列源码可看出,它的增删改查大部分都和第一个和最后一个挂钩,就和ArrayList
下标直接定位一样,效率高,线程不安全,查询慢,增删快
扩容机制:双向链表实现的,所以说没有默认初始化容量,也没有扩容机制
/**
* LinkedList 源码分析
**/
//列表中元素的数量 默认0
transient int size = 0;
//指向第一个节点
transient Node<E> first;
//指向最后一个节点
transient Node<E> last;
//获取链表中第一个节点
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
//获取链表中最后一个节点
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
//删除链表中第一个节点
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//删除链表中最后一个节点
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
//新增链表第一个节点
public void addFirst(E e) {
linkFirst(e);
}
//新增链表中最后一个节点
public void addLast(E e) {
linkLast(e);
}
/**
* 上面的方法都是包装,核心在下面
**/
//将e插入第一个节点位置
private void linkFirst(E e) {
//第一个节点
final Node<E> f = first;
//构建一个节点并指定该节点的前驱和后继
final Node<E> newNode = new Node<>(null, e, f);
//向第一个节点插入新节点
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
//将e插入最后一个元素
void linkLast(E e) {
//获取最后一个节点
final Node<E> l = last;
//构建一个节点并指定该节点的前驱和后继
final Node<E> newNode = new Node<>(l, e, null);
//向最后一个节点插入新节点
last = newNode;
//判最后一个节点是否为空
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
//将e插入到指定元素succ之前的位置
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
//构建节点并指定前驱和后继
final Node<E> newNode = new Node<>(pred, e, succ);
//将succ(指定元素)前驱指向新节点元素
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
//将第一个元素从链表中移出
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null;
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
//将最后一个元素从链表中移出
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null;
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
//移除链表中指定的节点
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;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
//返回指定索引处的节点
Node<E> node(int index) {
// assert isElementIndex(index);
//判断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;
}
}
//构建一个node节点,就是在插入数据时通过它去构建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;
}
}
/**
* 以下是遍历的源码,我不是很了解,只知道它和上面一样要从第一个节点或
* 最后一个节点去逐个遍历,遍历LinkedList要注意,使用foreach
* 就是迭代器,如果使用随机获取,效率相当低就是普通的for循环
**/
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
}
Vector
Vector是个比较老的集合,我自己也不常用
底层实现:动态数组
特点:线程安全,效率低,查询快,增删慢,通过下标,支持而随机访问,
和ArrayList区别在于ArrayList线程不安全,Vector线程安全(有synchronized关键字)
扩容机制:当扩容量大于0时,扩容机制为原数组长度加上扩容量,否则默认初始容量10,超过之后以当前长度的2倍进行扩容
/**
* Vector源码解析
**/
//用来保存元素
protected Object[] elementData;
//数组中元素的个数
protected int elementCount;
//保存数组的扩容量
protected int capacityIncrement;
//数组最大长度为int类型的最大长度减去8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//构造一个指定初始容量和指定的扩容量的数组
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的数组
public Vector() {
this(10);
}
//构造一个指定内容的集合数组
public Vector(Collection<? extends E> c) {
elementData = c.toArray();//赋值内容
elementCount = elementData.length;//赋值长度
// defend against c.toArray (incorrectly) not returning Object[]
// (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
if (elementData.getClass() != Object[].class)//判断筛选
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
//vector的扩容机制
private int newCapacity(int minCapacity) {
int oldCapacity = elementData.length;//元数组元素的长度
//扩容:如果扩容量(capacityIncrement)大于零,那么扩容机制为原数组元素的长度加扩容量
// 如果扩容量(capacityIncrement)不大于零,那么扩容机制为原数组元素的长度加上原数组元素长度就是乘于二
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
//如果新容量小于指定容量 抛出java 堆溢出异常
if (newCapacity - minCapacity <= 0) {
if (minCapacity < 0)
throw new OutOfMemoryError();
return minCapacity;//返回指定容量
}
//如果新容量小于数组指定最大容量,返回新容量
//否则以下方法处理
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
//处理指定容量大于数组最大指定容量
private static int hugeCapacity(int minCapacity) {
//小于零抛出java 堆异常
if (minCapacity < 0)
throw new OutOfMemoryError();
//指定容量大于数组最大指定容量返回int类型的最大存储长度,否则返回数组最大指定容量
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
总结:
ArrayList: 线程不安全,查询快,增删慢,支持随机查询(索引下标),
LinkedList:线程不安全,增删快,查询慢,
Vector: 线程安全,效率低,,增删慢,查询快,支持随机查询(索引下标)
应用场景明显,各有所长,根据实际业务需求选择!
Set:无需,不可重复(实现类:HashSet,TreeSet)
常用方法:
增:add(element),addAll(collection)
删:remove(object),removeAll(collection),clear()
查:get(index),size()
判断:isEmpty(),contains()
区别list:hashCode(),equals(object)
HashSet
底层实现:数组+链表+红黑树
特点:HashMap中的Kay部分,线程不安全,按hash算法来存储元素
扩容机制:默认初始化容量为16,当超过整个数组的0.75,会以当前长度的2倍进行扩容
检索是否重复:
当你想HashSet加入值时,会去先调用HashCode来获取元素的hashcode值,
两个对象去比较hashcode值,如果两个元素的hashcode值相等,它不会去确
定为相同,然后会去调用equals方法来去比较,如果equals返回true,表示
两者整的相同,那么hashSet不会让你的操作成功!
/**
* HashSet源码解析
**/
//用来保存元素,看出其实是map
private transient HashMap<E,Object> map;
//hashSet构造器
public HashSet() {
map = new 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);
}
//看hashSet的底层原理 建议先去了解hashMap hashSet的底层其实都是hashMap在工作包括基本操作(不过多解释)
TreeSet
底层实现:红黑树
特点:TreeMap中的Kay部分,线程不安全,有序,
排序方式:支持两种排序方式,自然排序和定制排序,默认为自然排序,
自然排序:通过compareTo()方法将两个对象进行大小比较,然后将集合元素按升序排序
//看treeMap的底层
Map:键值对存储方式,键唯一,值不唯一(实现类:hashMap,treeMap)
双列集合的体系图
HashMap:
1.8之前:
hashMap在1.8之前底层实现是数组+链表来解决hash碰撞,可以理解为,一个链表数组数组的每个元素是
一个个的链表,当存入两个值时,值不同,但是得到的hash值是相同的,就会出现hash碰撞,而当遇到
hash碰撞时,hash值存放在数组,将碰撞的值放在对应的链表,再次碰撞时,存入这个元素的下一个,
遍历时,get到键,需要遍历对应的链表来获取对应的值,而当hash值相同的值较多时,获取的效率极低
,看1.8之后的优化
1.8之后
hashMap在1.8之后底层实现改为数组+链表+红黑树(平衡二叉树),其实在使用时没有达到默认指定阈值之前,还是数组+
链表形式的,当hash值碰撞的元素值数量达到指定阈值(默认是8)之后链表转换为红黑树形式,而当hash值
碰撞的元素值数量达到指定默认阈值(默认是6)后悔红黑树转换为链表形式,,这种方式来优化hash值碰撞
的值很多的场景下,增加查询的效率
底层实现:数组+链表+红黑树
特点:键值对存储方式,键不可重复,值可重复,线程不安全,允许为空,在jdk1.8之前采用数组+链表,1.8之后改为数组+链
表+红黑树
扩容机制:默认初始化容量为16,当元素的长度达到整个数组的0.75,会以当前长度的2倍进行扩容,你也可以指定初始化容量
和加载因子,建议不要去更改,既然默认是16和0.75,那么肯定有它对空间节省或者效率高低的一个比较好的选择
/**
* hashMap源码解析:
**/
//默认初始化容量为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//默认存储最大容量为1<<30 (1073741824)
static final int MAXIMUM_CAPACITY = 1 << 30;
//hashMap的扩容加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//指链表转换红黑树的阈值,当链表的元素超过之后,转换为树结构存储
static final int TREEIFY_THRESHOLD = 8;
//指红黑树转换链表的阈值,当元素小于之后,转换为链表结构存储
static final int UNTREEIFY_THRESHOLD = 6;
//桶转换为红黑树的阈值
static final int MIN_TREEIFY_CAPACITY = 64;
//看过LinkedList底层的一定知道node节点,hash值取决保存的位置,键值,和链表下一个元素封装为节点node
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//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 int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
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;
}
}
//用于遍历map集合
transient Set<Map.Entry<K,V>> entrySet;
//保存集合中键值对的数量
transient int size;
//加载因子
final float loadFactor;
//构造方法
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);
}
//构造指定初始化容量默认的加载因子 0.75
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//默认构造方法
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
//将指定集合存入新集合中
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}