集合:有一个或多个确定的元素所构成的整体
框架:规范
一.Collection:是一个接口,是所有集合的父类
Collection提供的方法
1. add(Object o) addAll(Collection c) 添加元素
2.remove(Object o) removeAll(Collection c) 删除元素
3. clear() 清空集合
4.contains(Object o) containsAll(Collection c) 判断集合是否包含某个元素
5.iterator() 判断集合是否包含某个元素
6.isEmpty() 判断集合是否为空
7.size() 集合元素的长度
1.List: 列表,存放可重复的有序集合
list提供的方法
1.indexOf()获取重复对象第一次出现的索引位置
2.lastIndexOf()获取重复对象最后一次出现的索引位置
3. subList(intfromIndex, inttoIndex) 返回从索引fromIndex到toIndex的元素集合,包左不包右
1)list的子类
ArrayList
1.动态数组
2.原理:默认初始数组长度为10,长度不够用,按1.5倍扩容,把原长度数组的所有元素复制给扩容之后的数组中
3.特点:遍历元素和随机访问元素的效率比较高
源代码
普通数组:不可变的,内容和长度都是不可变的 ArrayList是一个动态数组 通过构造方法,首先创建一个普通的数组 本质就是普通的数组,默认容量为10,容量不够用的时候进行扩容: 扩容机制:增加到原长度的1.5倍 如果容量超过最大值,会有限制(报错或固定到最大值的容量)
//默认数组的容量为10
private static final int DEFAULT_CAPACITY = 10;
//没有任何元素初始化时,是一个空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//元素数组
transient Object[] elementData;
//数组中的元素个数
private int size;
//数组存储容量上限
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//1.创建一个空数组,只要new ArrayList<>(),就创建空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//2.在创建数组时给的数组的容量大小值
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);
}
}
//3.在已经有一个数组的基础上,把这个数组的元素复制给一个新数组,并且在新数组上添加新元素
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;
}
}
//4.add原代码 elementData[size++] = e;
//4.1 把指定元素添加到数组的末尾
public boolean add(E e) {
//首先数组长度为0,添加一个元素,把长度+1
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//4.2 在添加元素时,如果添加的元素个数小于10,则数组第一次扩容按长度10进行扩容
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//4.3 判断数组长度和扩容的长度
private void ensureExplicitCapacity(int minCapacity) {
//修改次数
modCount++;
// overflow-conscious code
//minCapacity=10 ,elementData.length=0
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//4.4 进行扩容,每次扩容1.5倍
//minCapacity=10
private void grow(int minCapacity) {
// overflow-conscious code
//oldCapacity=0,数组长度
int oldCapacity = elementData.length;
//>>:右移运算(/2)
//再次扩容在原基础数组长度的1.5倍进行扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
//newCapacity =10
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);
}
//5.remove 移除元素
//5.1 通过索引删除元素
public E remove(int index) {
rangeCheck(index);
modCount++;
//返回索引要删除的值
E oldValue = elementData(index);
//5.2
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//把删除之后的数组的最后一个索引置为空null
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
// 5.2 通过索引返回要删除的元素
E elementData(int index) {
return (E) elementData[index];
}
Vector
用法和ArrayList一样,是线程安全的,现在几乎不用
Stack
1.stack是Vector的子类
2.栈:后进先出(LIFO)
3.方法
a. push() 压栈,添加元素,将元素推送到堆栈顶部
b. pop() 移除并返回堆栈的顶部元素
c.peek() 返回堆栈顶部的元素,但不删除它
d.empty() 如果堆栈顶部没有任何内容,则返回true。否则,返回false
e.search() 查找对象是否存在于堆栈中。如果找到该元素,它将从堆栈顶部返回元素的位置。否则,它返回-1。
LinkedList
1.链表的结构,采用链表存储方式,实现了List接口 和 Deque接口 , 它具备集合和队列的特点
2.原理:每个元素是一个Node内部类,Node类有三个属性:前驱,当前元素,后继。不管什么操作只要调整前驱和后继的连接节点就可以
3.特点:插入、删除元素时效率比较高
源代码
linkedList:链表结构 linkedList里每个元素都是一个Node() 注:Node是LinkedList内的静态内部类 包含三个元素:前驱,当前元素,后继 构造方法:需要的参数(前驱,当前元素,后继) linkedList里没有默认的长度,初始长度为0. 每添加一个元素,就是新创建一个Node,并且把新添加Node的前驱指向原最后Node节点 原最后Node节点的后继指向新添加Node节点,并且新添加Node当做整个list的最后节点。
//属性
//长度大小为0
transient int size = 0;
//第一个结点
transient Node<E> first;
//最后一个结点
transient Node<E> last;
//1.内部类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;
}
}
//2.add添加
//2.1
public boolean add(E e) {
//向最后添加元素
linkLast(e);
return true;
}
//2.2
void linkLast(E e) {
//把最后一个元素用l接收
final Node<E> l = last;
//创建添加元素的新节点,新节点的前驱就是最后一个结点,当前元素是要添加的元素,后继没有元素为null
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//最后节点为空,表示LinkedList中没有节点,链表为空
if (l == null)
//新建的结点就为第一个结点,即是后继也是前驱
first = newNode;
else
//表示链表中有元素,即原本最后的元素的后继是新建的结点
l.next = newNode;
//长度加1
size++;
modCount++;
}
//3.获取元素
//3.1:通过索引获取元素
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
//3.2:获取元素,类似于二分查找
Node<E> node(int index) {
// assert isElementIndex(index);
//先把长度/2 ,获取中间值,把要找的索引和中间值进行判断,大于找右边,小于找左边
if (index < (size >> 1)) {
//用x接收第一个元素
Node<E> x = first;
//找后继结点
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//用x接收最后一个元素
Node<E> x = last;
//找前驱节点
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
//4.remove移除
//4.1 通过索引进行移除值
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
//4.2:找到节点,断开节点连接
E unlink(Node<E> x) {
// assert x != null;
//断开节点为x
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;
}
Queue
1.单向队列
2.先进先出(FIFO)
3.方法
a. add() 添加元素
b. offer() 将指定的元素添加此队列(如果立即可行且不会违反容量限制),添加成功返回 true;否则返回 false
c. remove() 获取并移除此队列的头,如果此队列为空,则抛出NoSuchElementException异常
d. poll() 获取并移除此队列的头,如果此队列为空,则返回 null
e. element() 返回头部元素,类表为空抛异常
f. peek() 获取队列的头但不移除此队列的头。如果此队列为空,则返回 null
4.实现方式:
ArrayDeque:数组形式实现
LinkedList:链表形式实现
做同样事情的方法,有两套实现方法。
Deque
1.是Queue的子接口,双向链表/队列。
2.先进先出(FIFO)
3.特点:前后两端都可以操作数据
4.方法:在Queue的方法基础上添加 xxxFirst()和xxxLast()
addFirst() addLast() offerFirst(E) offerLast(E) getFirst() getLast peekFirst ...
*ArrayList和LinkedList的比较*
ArrayList 和 LinkedList区别
1.ArrayList是动态数组,有默认初始容量,并且根据数据的不断增加可以动态增长list的长度 linkedList是链表结构,元素之间以Node形式链接。
2.使用上,ArrayList更适合于查找,LinkedList更适合于插入和删除操作
a.ArrayList是数组结构,每个元素都有固定位置,查找的时候可以直接定向到这个位置 插入/删除操作,每插入或删除一个元素都需要改变后面所有元素的位置。
b.LinkedList是链表结构,元素没有固定位置,是通过前驱和后继连接起来。 插入/删除操作,只需要把当前节点的前驱和后继置为空,调整一下前后节点的连接关系就ok,无需改变更多节点。 由于没有固定位置,查找数据不能直接指向,需要按顺序查找。
2.Set:存放不重复的元素,且无序
set不重复的原因:内部源码是map
内部实现本质就是map,set中的元素充当了map中的键
HashSet
1.无序,不重复的集合
2.使用的方式与List差不多 HashSet在添加数据的时候,会根据equals方法判断是否为重复数据。
3.HashSet(HashMap)内部结构:
1).HashSet内部实现就是HashMap,元素是Node类型的数组。
2).每个元素都是一个单向链表,数组初始长度为16。
3).插入数据,先比较hashCode值
a.如果值相等(哈希冲突),使用equals进行内容的比较, i.如果内容相等,当前值为重复值不允许添加 ii.如果内容不同,hashCode%长度,根据模插入到hash桶中。
b.如果hashCode值不相等,hashCode%长度,根据模插入到hash桶中。
4).每个单向链表都可能会有很多个元素,可能会产生哈希热点问题。 每个单向链表如果元素个数超过8个,数据结构就变成了红黑树。 如果单向链表里的元素个数小于6个,数据结构要变回链表。
5).如果整个元组的占用数达到了长度的3/4 (扩容因子默认是0.75), 当前元组要进行扩容(2倍,建议数组的长度是2的n次方)。
4.特点:具有很好的存取和查找性(无序,唯一)
源代码
//hashSet内部直接 new HashMap
public HashSet() {
map = new HashMap<>();
}
TreeSet
1.SortedSet的子类,按照元素的自然顺序进行排序,以树结构进行存储
2.特点:(元素无索引,元素唯一,对元素进行排序)
源代码
//TreeSet 内部 new TreeMap
public TreeSet() {
this(new TreeMap<E,Object>());
}
二.Map: 是一个接口,存储键值对类型元素的接口
1.map是1.2版本之后出现的,原版本是Dictionary(字典)
2.Map:映射 键值对(key-value) 成对出现,键不能重复。
3.常用的实现类:
1).HashMap:最常用的实现类
2).HashTable:不太常用,一般与HashMap做对比
3).TreeMap:
Map常用方法
put(K key,V value):添加元素,如果有值选择替换(可以添加任何类型,以及用null)
remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
get(Object key) 根据指定的键,在Map集合中获取对应的值
boolean containsKey(Object key):判断该集合中是否有此键
Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。
Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。
HashMap(键的唯一、不重复)
1.hash:散列,尽量让存储的数据分散存储
2.HashMap:动态数组+链表+红黑树(完美平衡二叉树)的数据结构
3.原理:
a.默认是长度为16的数组,对元素的key取hashCode值,对长度取余,余数是几就进第几桶
b.如果余数相同,会放在相同桶中但是是链表形式
c.如果链表长度达到8,变成红黑树,长度小于6,取消红黑树;
d.如果HashCode值一样,内容也相等,元素的value覆盖原有的value
4.使用:key-value键值对;如果key相同,value值被覆盖;key和value都可以为空
5.线程是不安全的,可以通过Vertor进行设置为线程安全
6.与HashSet不一样的地方就是, HashMap存的是键值对, 而HashSet只用到了键而没有用到value。
7.内部类:Node<Entry<K,V>>
内部类属性 hash key value next
内部类构造方法 Node(hash,key,value,next)
源代码
//1.属性
//默认的初始化容量为16:位运算,速度特别快
// <<:左移表示*2,移4位,乘以4个2=16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//最大容量:2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
//扩容因子:16*0.75=12,达到12时进行扩容,ArrayList是满了才进行扩容,可以自定义扩容因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//树化阈值:达到8进行树化
static final int TREEIFY_THRESHOLD = 8;
//树变链表阈值:6
static final int UNTREEIFY_THRESHOLD = 6;
//最小数化容量:64
static final int MIN_TREEIFY_CAPACITY = 64;
//数组
transient Node<K,V>[] table;
//2.构造方法
//2.1:加载扩容因子,把当前扩容因子置为默认扩容因子
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
//2.2:自定义数组长度
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//2.3:容量和扩容因子都进行自定义
//2.4:直接传已知map
//3.put()
//3.1 添加key,value
//onlyIfAbsent中(第四个值): false:key相同value值不变(忽略) true:赋值
public V put(K key, V value) {
//3.22 hash值
return putVal(hash(key), key, value, false, true);
}
//3.2 table初始数组为null
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//3.21 把数组给一个变量进行接收
if ((tab = table) == null || (n = tab.length) == 0)
//3.22 resize()是一个数组类型, 数组长度n=16
n = (tab = resize()).length;
//3.22 hash值 :正常hash值对16取余
//(n - 1) & hash = 15 & hash = 1111 & hash(10=1010) = 1010(10)
//hash值是啥就是啥
if ((p = tab[i = (n - 1) & hash]) == null)
//3.23 判断数组中取余位置是否为空,null表示没有元素
//为空创建Node tab[i]:数组索引位置
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//3.26 :当前hash值与上一个值得hash值是否一致
if (p.hash == hash &&
//3.27 :当前key值与上一个值得key值是否一致
((k = p.key) == key || (key != null && key.equals(k))))
//3.28:p是上一个key值,完全一样把P给k
e = p;
//3.29:判断是不是数的结构
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//3.291:key不冲突 & 不是树结构 则为链表
else {
for (int binCount = 0; ; ++binCount) {
//判断链表上的最后一个结点位置
if ((e = p.next) == null) {
//3.292:在最后一个结点的后继创建一个新节点
p.next = newNode(hash, key, value, null);
//3.293:判断是否树化:阈值-1(7)
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//3.294:树化
treeifyBin(tab, hash);
//3.295:跳出循环
break;
}
//3.31:判断下一个key是否冲突
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//整个链表循环条件,一直往下找结点,知道为空位置
p = e;
}
}
if (e != null) { // existing mapping for key
//原来的value(a)=oldValue
V oldValue = e.value;
//!onlyIfAbsent:true 替换
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//3.24:操作次数
++modCount;
//3.25:改变长度:1 threshold:12
if (++size > threshold)
//3.26:扩容:当size=13>12,进行扩容
resize();
afterNodeInsertion(evict);
return null;
}
//3.3 == 3.22resize()是一个数组类型
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
//oldCap:旧容量0 oldTab.length=12
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//旧阈值:0 oldThr =12
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
//容错
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//oldCap =table=16 << newCap =16*2=32
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0)
newCap = oldThr;
//走
else {
//默认容量赋值:16
newCap = DEFAULT_INITIAL_CAPACITY;
//默认扩容因子:0.75 阈值:newThr =12
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);
}
//把阈值赋给全局变量 新阈值:24
threshold = newThr;
//创建Node类型的数组,默认长度为16 新长度:32
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//变成全局变量
table = newTab;
//oldTab ==null 不走
if (oldTab != null) {
//把原数组的值复制到新的数组中
//oldCap:循环16次,因为原数组长度为16:并不知道12个hash值在哪些位置
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//判断所有位置是否为空 数据给e
if ((e = oldTab[j]) != null) {
//把该位置置为空
oldTab[j] = null;
//相当于e是最后一个值
if (e.next == null)
//对32取余:找数组位置
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
//树节点
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
//low:低位
Node<K,V> loHead = null, loTail = null;
//high:高位
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;
}
//3.4: 3.22 hash值
static final int hash(Object key) {
int h;
//map中key可以为null
// >>>:无符号右移操作 ^:异或操作,再一次进行散列化
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
TreeMap :键唯一,值可以重复,如果键重复了,值就覆盖(不重复)
1.是SortedMap的子类,可以按照key进行排序
2.排序的规则按照Comparable和Comparator实现的
3.原理:循环遍历数的每一层,判断key的大小,小的放左侧,大的放右侧
4.内部结构是红黑树, 内部结构: 红黑树(完美平衡二叉树) 在红黑树内不需要做优化
源代码
构造方法 1.没有参数:使用默认排序规则。 2.一个参数-Comparator:有已知的自定义比较器,使用这个比较规则。 3.一个参数-普通的Map:使用默认排序规则,把Map里的数据当做初始数据。 4.一个参数-SortedMap:使用传递过来的map的Comparator充当当前类的比较器。 普通的方法(增删改查) put(): 1.判断有没有自定义的Comparator,如果有使用自定义排序规则,如果没有使用默认培训规则 2.通过do...while的形式(因为树可能有很多层)判断原节点的key和新节点的key的大小 3.如果key比原key小,放在左子树; 如果key与原key相同,节点不变,value值覆盖; 如果key比原key大,放在右子树。
//1.
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
//2.put方法
public V put(K key, V value) {
//2.1:树节点
Entry<K,V> t = root;
if (t == null) {
//2.2:比较器
compare(key, key); // type (and possibly null) check
//2.3:第一个结点
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
//2.2:变量,不是第一个元素
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
//2.3:比较器
Comparator<? super K> cpr = comparator;
//1:比较器为空
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
//2.4:key为空抛异常
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
//默认比较器
Comparable<? super K> k = (Comparable<? super K>) key;
//2.5:相当于是二叉搜索树
do {
parent = t;
//-1:小于 0:等于 1
cmp = k.compareTo(t.key);
if (cmp < 0)
//左子节点=t
t = t.left;
else if (cmp > 0)
//右子节点=t
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
//3.Entry
内部类
Entry<K,V>:代表一个树的节点,也就是一个键值对
key:当前节点的键值对的键
value:当前节点的键值对的值
left:当前节点的左子节点
right:当前节点的右子节点
parent:当前节点的父节点
color:节点的颜色,根节点是黑色节点
HashTable
1.是早期的hashMap,继承Dictionary类
2.是线程安全的
三.Collections / Arrays
算法类,提供了集合的一些常用的方法,算法类就是给集合和数组提供了很多静态的方法,使操作更加简便。
sort(List<T> list):将集合中元素按照默认规则排序
sort(List<T> list,Comparator<? super T> ):将集合中元素按照指定规则排序
比较器:如果想对列表中的元素进行排序,元素的类必须实现Comparable
如果想临时使用自定义排序规则,自定义类实现Comparator
shuffle(List<?> list):打乱集合顺序