一、概述
所有的集合类和集合接口都在java.util包下。
集合是一种容器,也是对象,所以在堆中申请一块空间用来存储数据,在Java中集合就是替换掉定长的数组的一种引用数据类型。
二、特点
2.1 集合大小可以变。
在初始时赋值initialCapatity为2,说明初始容量为2,但里面没有数据,打印出来的size还是为0,只有当存入3个数据后,才会打印出size为3。此时容量应该是10,后续会进行解释。
2.2 集合中能存储引用数据类型(存储的为对象的内存地址)
- 基本类型
Java集合可以存储基本数据类型,包括 byte、short、int、long、float、double、boolean和char。Java提供了相应的包装类(Byte、Short、Integer、Long、Float、Double、Boolean和Character),将基本数据类型转换成对象,方便在集合中存储和操作。
- 对象类型
存储任意类型的对象,比如自定义实体类对象
- 集合类型
Java集合还可以存储集合类型,也就是将一个集合作为另一个集合中的元素进行存储。例如,可以使用List接口存储另一个列表
2.3 集合中可以存储不同类型数据(一般情况下也只存储同一种类型的数据)
这里是初始化list的时候没有设置泛型,所以可以add不同类型的数据。
设置泛型String后,add进list类型的数据便会报错。
三、集合关系图
绿色实线为接口之间的继承extend
绿色虚线为实体类与接口之间的实现implement
蓝色实现为实体类之间的继承extend
3.1 Collection单列集合
3.2 Map双列集合
四、Iterable接口
4.1 简介
Iterable是一个可迭代接口,与之前版本相比,增加了forEach迭代和获取Spliterator方法。
Iterable提供获取Iterator迭代器方法,用以支持集合遍历。
Iterable提供获取Spliterator可分割迭代器方法,用以支持集合的并发遍历。
public interface Iterable<T> {
// 在类型的元素上返回迭代器
Iterator<T> iterator();
// 对Iterable的每个元素执行给定的操作,直到处理完所有元素或操作引发异常。
// 除非实现类另有指定,否则按迭代顺序(如果指定了迭代顺序)执行操作
// 操作引发的异常将中继到调用方
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
// 返回一个可分割迭代器
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
4.2 Iterator接口
Iterator提供了集合和流操作等的遍历方式,是迭代器模式的应用。
Iterator提供了hasNext方法来判断是否还有下一个元素可以遍历,有则返回 true,否则返回false
/**
* Returns the next element in the iteration.
*
* @return the next element in the iteration
* @throws NoSuchElementException if the iteration has no more elements
*/
E next();
Iterator提供了next方法来获取当前迭代器的下一个元素,如果下一个元素为空则抛出NoSuchElementException。
/**
* Returns the next element in the iteration.
*
* @return the next element in the iteration
* @throws NoSuchElementException if the iteration has no more elements
*/
E next();
Iterator提供了remove方法移除当前迭代器中的当前元素。
注意点:
1.这个方法需要底层的集合类重写该方法,否则执行默认方法会抛出 UnsupportedOperationException。
2.如果调用 remove() 方法之前没有被调用 next() 方法,或者调用一次next() 方法之后连续调用了2次remove() 方法,则会抛出 IllegalStateException。很好理解,因为该方法会删除当前迭代器迭代的当前元素,如果当前元素不存在,那么肯定会抛出 IllegalStateException 异常。
说明list里的数据被remove方法删除完了
Iterator提供了forEachRemaining方法,对剩下的元素根据给定的消费器来处理。
/**
* Performs the given action for each remaining element until all elements
* have been processed or the action throws an exception. Actions are
* performed in the order of iteration, if that order is specified.
* Exceptions thrown by the action are relayed to the caller.
*
* @implSpec
* <p>The default implementation behaves as if:
* <pre>{@code
* while (hasNext())
* action.accept(next());
* }</pre>
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
4.3 Spliterator接口
Spliterator是一个可分割迭代器(splitable iterator),可以和iterator顺序遍历迭代器一起看。jdk1.8发布后,对于并行处理的能力大大增强,Spliterator就是为了并行遍历元素而设计的一个迭代器,jdk1.8中的集合框架中的数据结构都默认实现了spliterator。
Spliterator接口包含的方法:
boolean tryAdvance(Consumer action); 单个对下一个元素执行给定的动作,如果有剩下元素未处理,执行则返回true,否则返回false
default void forEachRemaining(Consumer action) 对剩余元素依次执行action函数
Spliterator trySplit(); 将一个Spliterator分割成多个Spliterator并返回。分割的Spliterator被用于每个子线程进行处理,从而达到并发处理的效果。
long estimateSize(); 用于估算还剩下多少个元素需要遍历。
int characteristics(); 给出stream流具有的特性,不同的特性,不仅是会对流的计算有优化作用,更可能对计算结果会产生影响。
public static final int ORDERED = 0x00000010;
如果为元素定义了顺序。那么,这个Split操作符保证方法trySplit拆分元素的严格前缀,方法tryAdvance steps按前缀顺序前进一个元素,forEachRemaining按顺序执行操作。
public static final int DISTINCT = 0x00000001;
public static final int SORTED = 0x00000004;
如果顺序遵循自定义的排序顺序,方法getComparator()返回相关的比较器,或者如果所有元素都是可比较的并且按照它们的自然顺序排序,则返回null。
public static final int SIZED = 0x00000040;
能够在调用estimateSize()方法时返回源中确切数量的元素
public static final int NONNULL = 0x00000100;
保证不具有空值
public static final int IMMUTABLE = 0x00000400;
表示元素源不能在结构上修改的特征值;也就是说,不能添加、替换或删除元素,因此在遍历过程中不会发生这种变化。
public static final int CONCURRENT = 0x00001000;
表示元素源可以在没有外部同步的情况下由多个线程安全地并发修改(允许添加、替换和/或移除)。
public static final int SUBSIZED = 0x00004000;
表示由trySplit()产生的所有拆分器都将被调整大小和子调整大小。(这意味着所有子拆分器,无论是直接的还是间接的,都将被调整大小。)
166464==>0x4050 0x4000 0x40 0x10
65==>0x41 0x40 0x01
多线程中使用示例
public class Spliterator1 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13,14));
Spliterator<Integer> spliterator = list.spliterator();
for(int i = 0;i < 4;i++){
Thread myThread = new MyThread(spliterator);
myThread.start();
}
}
public static class MyThread extends Thread{
private static volatile Spliterator<Integer> spliterator;
public MyThread(Spliterator<Integer> spliterator) {
this.spliterator = spliterator;
}
@Override
public void run() {
Spliterator split = spliterator.trySplit();
if(split != null){
split.forEachRemaining(res->{
System.out.println(currentThread().getName() + ":" + res);
});
}
}
}
}
五、Collection接口
单列集合的顶层接口,定义了集合中通用的方法。既然是接口就不能直接使用,需要通过实现类。
int size();返回集合大小
boolean isEmpty();判断集合是否为空
boolean contains(Object o);判断指定元素o是否在集合中
Iterator iterator(); 返回迭代器对象
Object[] toArray(); 将集合转换为对应的数组
boolean add(E e); 添加元素e
boolean remove(Object o); 删除
boolean containsAll(Collection<?> c); 判断 集合c中元素是否全部包含于该集合
boolean addAll(Collection<? extends E> c); 添加集合c中元素到该集合中
boolean removeAll(Collection<?> c); 删除该集合中包含的集合c
default boolean removeIf(Predicate<? super E> filter);会传入一个过滤器,满足条件则会进行remove操作
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
boolean retainAll(Collection<?> c); 仅保留该集合中包含集合c的元素
void clear(); 清空元素,该集合为empty
boolean equals(Object o);
int hashCode();
default Spliterator spliterator() 返回一个可分割迭代器
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
default Stream stream() 可知stream流里用的就是可分割迭代器
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
default Stream parallelStream() 并行流
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
串行流stream:串行处理数据,不产生异步线程。
并行流parallelStream:parallelStream提供了流的并行处理,它是Stream的另一重要特性,其底层使用Fork/Join框架实现。简单理解就是多线程异步任务的一种实现。
建议:数据量不大的情况下建议使用stream即可,不要盲目大量使用parallelStream,因为parallelStream是多线程异步的,也就是说会产生多线程,消耗内存不说,说不定还会更慢,并非一定会更快更好。
六、List接口
6.1特点
1)元素被添加到集合中以后,取出的时候是按照放入顺序的
2)List可以重复
3)存在下标,可以直接依靠下标取值
6.2 包含的方法
default void replaceAll(UnaryOperator operator)
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
default void sort(Comparator<? super E> c)
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
如果是实体类,name需要实现Comparable接口,并重写CompareTo方法,在此方法中指定排序原则
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o); 返回这个重复的元素第一次出现时所在的索引位置的值
int lastIndexOf(Object o); 返回这个重复的元素最后一次出现时所在的索引位置的值
ListIterator listIterator(); 返回迭代器
ListIterator listIterator(int index); 返回指定下标开始的迭代器
List subList(int fromIndex, int toIndex); 截取List
static List of(E… elements);JDK9的新特性,可以给集合一次性添加多个元素,of方法的返回值是一个不能改变的集合,集合不能再使用add方法添加元素,会抛出异常
List list = List.of("a", "b", "a", "c", "d");
System.out.println(list);//[a, b, a, c, d]
//list.add("w");//UnsupportedOperationException:不支持操作异常
七、ArrayList类
7.1构造方法
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;
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 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, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
7.2 其他方法
public boolean add(E e) 将指定的参数元素追加到集合的末尾
public void add(int index ,E e) 在集合的指定位置添加指定的元素(插入元素)后面的元素下表后移
public void addAll(E object) 用于将指定集合中所有元素添加到当前集合中
ArrayList<String> list = new ArrayList<>();//泛型定义为String
//采用默认追加的方式添加元素
System.out.println(list.add("刘德华"));
System.out.println(list.add("张学友"));
System.out.println(list.add("郭富城"));
System.out.println(list.add("黎明"));
//插入的方式添加元素
// list.add(10,"谭咏麟");//插入元素方法索引值不能大于集合中元素个数
list.add(4,"谭咏麟");//表示在集合中最后位置插入元素,与追加相同
list.add(1,"谭咏麟");//指定位置插入元素,索引位置之后的元素会自动向后进行移动
ArrayList<String> newList = new ArrayList<>();//创建新的集合
newList.add("小沈阳");
newList.add("宋小宝");
newList.add("赵四");
newList.add("刘能");
//查看集合中的元素
System.out.println("原集合内部元素:" + list);
System.out.println("新集合内部元素:" + newList);
list.addAll(newList); //将新集合全部元素添加到原集合中
System.out.println("原集合内部元素:" + list);
public boolean remove(Object o) 删除指定的元素,成功则返回true
public E remove(int index) 删除指定索引位置的元素,返回被删除的元素
public E set(int index,E e) 修改指定索引位置的元素,返回修改前的元素
public E get(int index) 获取指定索引对应的元素
public int size() 获取结合中元素个数
ArrayList<String> list = new ArrayList<>();
//追加方式添加元素
list.add("东邪");
list.add("西毒");
list.add("南帝");
list.add("北丐");
list.add("中神通");
//删除
System.out.println(list.remove("西毒"));//通过元素名称删除,返回boolean
System.out.println(list.remove(1));//通过索引删除元素,返回被删除元素名
//修改
System.out.println(list.set(1,"西毒"));//指定索引位置修改元素,并返回被修改元素
System.out.println("原集合中元素有:" + list);
//获取方法
System.out.println(list.get(1));//通过指定索引位置获取集合元素
//获取集合元素个数
System.out.println(list.size());
7.3 扩容机制
ArrayList的底层是一个Object类型的数组elementData
当创建ArrayList对象时,如果使用无参构造,则初始elementData为0,第一次添加,检查时发现不足,则扩容elementData为10,如需要再次扩容,则扩容为elementData的1.5倍。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}
可以看到两个方法都调用了ensureCapacityInternal(size + 1)方法,把数组长度加1以确保能存下下一个数据
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
此方法会先调用calculateCapacity方法,此时minCapacity为1,即size+1,因为初始时size为0
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
此方法会判断当前数组是否为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,无参构造时才会返回这个数组。所以,若创建ArrayList时调用的是无参构造,此方法会返回DEFAULT_CAPACITY(值为10)和minCapacity的最大值,因此,最终会返回固定值10;若创建ArrayList时调用了有参构造,则此方法会返回1,注意这个minCapacity变量只是第一次调用add方法时值为1,此后的调用需要根据实际的数组长度size+1。然后调用ensureExplicitCapacity方法。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount++用到了快速失败机制。
如果minCapacity大于elementData.length,则会调用grow方法,注意,这个elementData.length返回的是当前数组的容量,而不是数组实际的长度size。如果调用了有参构造,例如传入的容量为5,则此时elementData.length值即为5,而此时第一次调用add时,size值为0,因此minCapacity为1,不满足条件,此情况不需要扩容调用grow方法;如果调用了无参构造返回数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,注意这个数组只是一个空数组,因此此时elementData.length为0,满足条件,需要扩容调用grow方法。
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);
}
int newCapacity = oldCapacity + (oldCapacity >> 1)此行代码即为扩容的核心,oldCapacity为原来的容量,右移一位,即除以2,因此这句的意思就是新的容量newCapacity=oldCapacity+oldCapacity /2,即原来的1.5倍。
然后判断newCapacity如果小于传入的minCapacity,则直接让newCapacity等于minCapacity,即不需要扩容计算(当无参构造时,elementData.length为0,所以oldCapacity也为0,minCapacity为10,因此最终newCapacity为10)。
然后判断newCapacity是否大于设定的MAX_ARRAY_SIZE,此处
如果大于,则调用hugeCapacity方法
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
如果minCapacity大于MAX_ARRAY_SIZE,则返回Integer的最大值,否则返回MAX_ARRAY_SIZE
最后,通过Arrays.copyOf方法把原数组的内容放到更大容量的数组里面。
八、Vector类
线程安全类,方法加了synchronized,
8.1构造方法
elementCount为元素个数
capacityIncrement为每次扩容大小,默认为0
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
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);
}
8.2 其他方法
public synchronized boolean add(E e) 添加特定的元素到Vector的尾部
public void add(int index, E element) 插入一个指定的元素到Vector的指定位置
public synchronized boolean addAll(Collection<? extends E> c) 添加指定集合到Vector尾部
![image.png](https://img-blog.csdnimg.cn/img_convert/e86892b8499968c22eba7d4d253ebf9d.png#averageHue=#2e2d2c&clientId=ud93d81ed-e7cc-4&from=paste&height=621&id=u0c694049&originHeight=621&originWidth=445&originalType=binary&ratio=1&rotation=0&showTitle=false&size=70705&status=done&style=none&taskId=u3e5120a4-2c8e-4601-8fe6-3bb4e64b579&title=&width=445)
public synchronized E get(int index) 获取指定下标的元素
public synchronized void removeElementAt(int index) 删除指定下标元素
public synchronized boolean removeElement(Object obj) 删除指定元素
public synchronized void removeAllElements() 删除所有元素
public boolean remove(Object o) 删除指定元素
public synchronized E remove(int index) 删除指定下标元素,并返回该元素
public void clear() 清空所有元素
public boolean removeAll(Collection<?> c) 删除vector中集合c所包含的所有元素
不包含Element的方法,例如remove是来自List接口的,而removeElement方法是来自Vector本身的。
用法都是类似ArrayList的
8.3 扩容机制
public synchronized boolean add(E e) { //这是我们具体要添加的元素,
modCount++; //这是我们对集合修改的次数,不重要
ensureCapacityHelper(elementCount + 1);
//这是判断我们当前vector集合当中的容量是否足够,
//elementCount加一是我们当前所需要的最小容量
elementData[elementCount++] = e; //将元素赋值给elementData数组
return true;
}
ensureCapacityHelper(int minCapacity这个方法的代码,只要我们当前所需要的最小容量比数组的容量大就进行扩容grow()方法。、
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
可以看到vector底层是将原先的大小进行两倍扩容,然后返回给elementData。在方法内部真正实行扩容的是Arrays当中的一个静态方法。这个方法就是传入你一个原先的数组和一个你需要的空间大小,它帮你生成一个新的具有newCapacity大小的一个数组,然后将你原先数组当中的元素给拷贝到新的数组当中返回。
private void grow(int minCapacity) {
int oldCapacity = elementData.length; //将原先的数组长度赋给oldCapacity
int newCapacity =
oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);
//三目运算符,因为我们的capacityIncrement默认等于0,在vector字段当中我有点出
//因此我们的newCapacity=oldCapacity+oldCapacity,也就是原先长度的两倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);//这里进行真正的扩容。
}
如果minCapacity大于MAX_ARRAY_SIZE,则返回Integer的最大值,否则返回MAX_ARRAY_SIZE
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
Vector类是线程安全的。效率比较低,使用较少,同样使用Collections工具类的synchronizedList方法转换为线程安全的Collections.synchronizedList(List list)。
九、LinkedList类
LinkedList底层通过双向链表实现,双向链表的每个节点用内部类Node表示。LinkedList通过first和last引用分别指向链表的第一个和最后一个元素,当链表为空的时候first和last都指向null。
9.1构造函数
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
9.2 其他方法
getFirst();获取第一个元素
getLast() ;获取最后一个元素
public E removeFirst();删除第一个元素
public E removeLast();删除最后一个元素
public boolean remove(Object o);根据指定元素删除匹配相等的第一个元素,如果没有这个元素,则返回false
public E remove(int index);删除指定下标处的元素
public void addFirst(E e);在头节点增加新元素
public void addLast(E e);在尾结点增加新元素
public boolean add(E e);在LinkedList的末尾插入元素,因为有last指向链表末尾,在末尾插入元素的花费是常数时间,只需简单修改几个相关引用即可
public void add(int index, E element);该方法是在指定下标出插入元素,需要通过线性查找找到具体位置,然后修改相关引用完成插入操作
public boolean addAll(Collection<? extends E> c);在末尾添加集合c
public boolean addAll(int index, Collection<? extends E> c);在指定位置添加集合c
public void clear();清空集合
public E get(int index);根据index查询元素
主要是通过遍历的方式定位目标位置的节点。获取到节点后,取出节点存储的值返回即可。这里面有个小优化,即通过比较 index 与节点数量 size/2 的大小,决定从头结点还是尾节点进行查找。
链表的优点:
由于链表上的元素在空间存储上内存地址不连续。
所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList。
链表的缺点:
不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头
节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率较低。
ArrayList:把检索发挥到极致。(末尾添加元素效率还是很高的。)
LinkedList:把随机增删发挥到极致。
加元素都是往末尾添加,所以ArrayList用的比LinkedList多。
十、Set接口
10.1 特点
1)不允许包含重复的值,可以存NUll
2)没有索引(就不能使用普通的for循环进行遍历)
3)基于对象相等性判断元素是否重复:Set 判断元素是否重复是基于元素的 equals() 和 hashCode() 方法。Set 会先调用该元素的 hashCode() 方法得到一个哈希码(hash code),然后再根据哈希码确定该元素存储在哪个桶中。如果桶中已经存在一个或多个元素,则会依次调用它们的 equals() 方法与待添加元素进行比较,如果存在相等的元素,则不会添加该元素。
10.2 包含的方法
int size(); 返回Set集合中元素的数量,如果大于 Integer.MAX_VALUE,则返回 Integer.MAX_VALUE。
boolean isEmpty();判空。
boolean contains(Object o); 是否包含
Object[] toArray(); 转换为数组,返回值是Object类型的数组
T[] toArray(T[] a); 转换为数组,返回值是参数T对应的类型
当toArray后面的数组参数长度比stringList列表的长度 短 时,会重新开一个新的数组,新数组长度与stringList元素个数相同,将stringList的元素放进新的数组空间中;
当toArray后面的数组参数长度与stringList列表的长度 相同 时,会将stringList里的元素都放进该数组中,不开新数组;
当toArray后面的数组参数长度比stringList列表的长度 长 时,会将stringList里的元素都放进去,剩下的位置用null填充
boolean add(E e); 添加一个元素
boolean remove(Object o); 删除一个元素
boolean containsAll(Collection<?> c); 是否包含集合c中的全部元素
boolean addAll(Collection<? extends E> c); 添加集合c中的全部元素
boolean retainAll(Collection<?> c); 保留集合c中的元素,其余的都删掉
boolean removeAll(Collection<?> c); 删除集合c中包含的所有元素
void clear(); 清空Set集合
boolean equals(Object o); 是否相等
int hashCode(); 获取hashCode值
static Set of(E… elements); 和List的of方法类似,返回一个不可修改的Set集合,不能再使用add方法添加元素,会抛出异常。
static Set copyOf(Collection<? extends E> coll); 返回包含给定Collection的元素的不可修改Set集合
十一、HashSet类
HashSet是基于HashMap来实现的,实现了Set接口,同时还实现了序列化和可克隆化。而集合(Set)是不允许重复值的。所以HashSet是一个没有重复元素的集合,但不保证集合的迭代顺序,所以随着时间元素的顺序可能会改变。
11.1 构造方法
public HashSet() {
map = new HashMap<>();
}
构造一个新的空集合;后台HashMap实例具有默认的初始容量(16)和负载因子(0.75)。
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
把集合c属性放进去,而Math.max((int) (c.size()/.75f) + 1, 16)则是用来给HashMap足够的存储空间,也就是HashMap的容量达到设定的这个 * 它初始的0.75这个临界值等于它的负载值的话,会自动扩容。
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
构造一个指定初始容量和负载因子的HashMap。
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
构造一个指定初始容量的HashMap。
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
(访问权限为包权限,不对外公开的)以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。dummy 为标识 该构造函数主要作用是对LinkedHashSet起到一个支持作用。
11.2 其他方法
public Iterator<E> iterator() {
return map.keySet().iterator();
}
public int size() {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
public void clear() {
map.clear();
}
public Object clone() {
try {
HashSet<E> newSet = (HashSet<E>) super.clone();
newSet.map = (HashMap<E, Object>) map.clone();
return newSet;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
返回此HashSet实例的浅表副本:元素本身未克隆
浅表副本是创建一个新的对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其副本引用同一对象。
@Override
public Object[] toArray() {
return map.keysToArray(new Object[map.size()]);
}
返回值是Obejct类型
@Override
public <T> T[] toArray(T[] a) {
return map.keysToArray(map.prepareArray(a));
}
返回值是参数类型
十二、LinkedHashSet
继承自HashSet,所以它是在HashSet的基础上维护了元素添加顺序的功能
12.1 构造方法
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);
}
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);
}
public LinkedHashSet() {
super(16, .75f, true);
}
public LinkedHashSet(Collection<? extends E> c) {
super(Math.max(2*c.size(), 11), .75f, true);
addAll(c);
}
可见都是调用父类HashSet的这一个方法
有序的原因以及底层结构在LinkedHashMap中讲解。
12.2 其他方法
都是调用的父类HashSet的方法。
十三、SortedSet接口
13.1 特点
有序性:SortedSet中的元素按照它们的自然顺序进行排序,或者根据给定的比较器进行排序。这使得元素在集合中以有序的方式存储,并且可以按照顺序进行遍历。
独一无二性:与Set接口一样,SortedSet中不允许有重复的元素。每个元素都是唯一的。
排序方法:SortedSet接口提供了一些方法来支持对元素进行排序操作,如first()、last()、headSet()、tailSet()和subSet()等。这些方法允许我们获取集合中的第一个元素、最后一个元素以及范围内的子集。
可变性:SortedSet接口并没有添加修改集合中元素的方法,因此它不支持直接修改集合中的元素。若需要修改元素,需要先将其删除,然后再添加新的元素。
13.2 包含的方法
Comparator<? super E> comparator(); 此方法返回用于排序该集合中的元素的比较器,如果该集合使用其元素的自然排序,则返回null
SortedSet subSet(E fromElement, E toElement); 这个方法返回包含element1和element2之间元素的集合的一个排序子集。
SortedSet headSet(E toElement); 这个方法返回比排序集合中存在的元素小的元素。
SortedSet tailSet(E fromElement); 此方法返回大于或等于排序集合中存在的元素的元素。
E first(); 这个方法返回这个集合中出现的第一个(最低的)元素。
E last(); 这个方法返回集合中最后(最高)的元素。
十四、TreeSet类
代码中的底层是NavigableMap,但NavigableMap只是一个接口,实际上是TreeMap,TreeMap是实现了NavigableMap的。
特点:会对元素排序去重。
14.1 构造方法
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
无public 内部包调用
public TreeSet() {
this(new TreeMap<>());
}
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
后续在TreeMap中讲解构造方法的参数意义和作用。
在User中添加比较器,TreeMap便会按照定义的顺序排序。
下面是在初始化TreeMap时,传入了比较器接口,倒序排列。
14.2 其他方法
public Iterator<E> descendingIterator() {
return m.descendingKeySet().iterator();
}
用于按降序迭代元素
public NavigableSet<E> descendingSet() {
return new TreeSet<>(m.descendingMap());
}
返回一个降序排列的集合
public int size() {
return m.size();
}
获取集合中元素个数
public boolean isEmpty() {
return m.isEmpty();
}
判断是否为空集合
public boolean contains(Object o) {
return m.containsKey(o);
}
判断是否包含元素o
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
m.put成功会返回null;
public boolean remove(Object o) {
return m.remove(o)==PRESENT;
}
m.remove成功会返回该map的value值,
public void clear() {
m.clear();
}
清空集合
public boolean addAll(Collection<? extends E> c) {
// Use linear-time version if applicable
if (m.size()==0 && c.size() > 0 &&
c instanceof SortedSet &&
m instanceof TreeMap<E, Object> map) {
SortedSet<? extends E> set = (SortedSet<? extends E>) c;
if (Objects.equals(set.comparator(), map.comparator())) {
map.addAllForTreeSet(set, PRESENT);
return true;
}
}
return super.addAll(c);
}
通过集合添加元素
public NavigableSet<E> headSet(E toElement, boolean inclusive) {
return new TreeSet<>(m.headMap(toElement, inclusive));
}
返回小于参数toElement的元素集合,inclusive为true则包含本身,否则不包含
public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
return new TreeSet<>(m.tailMap(fromElement, inclusive));
}
返回大于参数toElement的元素集合,inclusive为true则包含本身,否则不包含
public SortedSet<E> subSet(E fromElement, E toElement) {
return subSet(fromElement, true, toElement, false);
}
返回from到to之间的值,包含from,不包含to,即【左闭右开)
public NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
E toElement, boolean toInclusive) {
return new TreeSet<>(m.subMap(fromElement, fromInclusive,
toElement, toInclusive));
}
返回from到to之间的值,fromInclusive和toInclusive代表是否包含当前值
public SortedSet<E> headSet(E toElement) {
return headSet(toElement, false);
}
返回<=toElement值的元素
public SortedSet<E> tailSet(E fromElement) {
return tailSet(fromElement, true);
}
返回>=fromElement值的集合元素
public Comparator<? super E> comparator() {
return m.comparator();
}
用于在此树映射中维护顺序的比较器,如果使用其键的自然顺序,则为null。
原因:TreeSet中使用的TreeMap来存储对象,将TreeSet的元素作为Map的key,TreeMap的put方法当compareTo返回值是0时,key值不会变化(实际是更新key对应的value,TreeSet的value是无意义的),也就是说当出现多个返回0的情况时TreeSet中只会放入第一个元素,后面的全都舍弃掉。
public E first() {
return m.firstKey();
}
获取第一个元素
public E last() {
return m.lastKey();
}
获取最后一个元素
public E lower(E e) {
return m.lowerKey(e);
}
返回严格小于给定键值的最大键值
public E higher(E e) {
return m.higherKey(e);
}
返回严格大于给定键值的最小键值
public E floor(E e) {
return m.floorKey(e);
}
返回小于或等于给定键值的最大键值
public E ceiling(E e) {
return m.ceilingKey(e);
}
返回大于或等于给定键值的最小键值
public E pollFirst() {
Map.Entry<E,?> e = m.pollFirstEntry();
return (e == null) ? null : e.getKey();
}
获取第一个值并移除第一个值
public E pollLast() {
Map.Entry<E,?> e = m.pollLastEntry();
return (e == null) ? null : e.getKey();
}
获取最后值并移除这个值
十五、Map接口
15.1 特点
- Map与Collection并列存在。用于保存具有映射关系的数据:key-value
- Map 中的key 和value 都可以是任何引用类型的数据
- Map 中的key 用Set来存放,不允许重复,即同一个Map 对象所对应的类,须重写hashCode()和equals()方法
- key 和value 之间存在单向一对一关系,即通过指定的key 总能找到唯一的、确定的value
15.2 包含的方法
int size(); 获取元素个数
boolean isEmpty(); 是否为空
boolean containsKey(Object key); key键中是否包含参数key
boolean containsValue(Object value); value值中是否包含参数value,返回true说明至少有一个
V get(Object key); 根据key获取value
V put(K key, V value); 添加key-value键值对
V remove(Object key); 根据key删除对应的键值对
void putAll(Map<? extends K, ? extends V> m); 添加Map集合
void clear(); 清空map集合
Set keySet(); 获取所有key键的Set集合
Collection values(); 获取所有value值的Collection集合
Set<Map.Entry<K, V>> entrySet(); 获取到Map集合中所有的键值对对象的集合(Set集合)
其中Entry是属于Map的静态内部类,在创建Map对象的时候就会同时创建一个Entry对象,用来记录键与值的映射关系。
得到的Set集合便可以用来遍历map键值对
Entry接口便定义在Set接口内部
boolean equals(Object o); 在Map中,equals(Object o)方法的默认实现是通过逐个比较每个键值对的方式来判断两个Map是否相等。具体而言,它会先检查给定对象是否也是一个Map类型,然后比较两个Map的大小是否相等。如果大小相等,它会逐个比较每个键值对的键和值是否相等。
int hashCode(); 将map中的每个键和值的哈希码进行异或操作,并将结果累加到最终的哈希码值中。这样做是为了提高哈希码的分布性,尽量避免碰撞
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
key在map中不存在时,会使用默认值,如果key存在于map中,则还是使用原值
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch (IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
// ise thrown from function is not a cme.
v = function.apply(k, v);
try {
entry.setValue(v);
} catch (IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
}
}
将每个entry的值替换为对该entry调用指定函数的结果
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
如果对应的key不存在,将value插入,返回null。否则,不插入,返回原值。
default boolean remove(Object key, Object value) {
Object curValue = get(key);
if (!Objects.equals(curValue, value) ||
(curValue == null && !containsKey(key))) {
return false;
}
remove(key);
return true;
}
如果当前key不存在或者key存在但value与输入的value不同则删除失败,只有当key和value都对应上时才会删除成功。
default boolean replace(K key, V oldValue, V newValue) {
Object curValue = get(key);
if (!Objects.equals(curValue, oldValue) ||
(curValue == null && !containsKey(key))) {
return false;
}
put(key, newValue);
return true;
}
如果当前key不存在或者key存在但oldValue不对应,则返回false,只有当key和oldValue都对应上时,才会将newValue覆盖掉oldValue。
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
只要key值存在,参数value就会覆盖掉旧value
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
第一个参数是HashMap的key,第二个参数是一个函数式接口,叫做映射函数,用于计算值(通过这个方法重新计算后得到的value),返回的就是value值。
如果key 不存在,那么直接把key添加到map中,存在则直接返回null。
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
if ((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
put(key, newValue);
return newValue;
} else {
remove(key);
return null;
}
} else {
return null;
}
}
第一个参数是HashMap的key,第二个参数是一个函数式接口。
如果key不存在直接返回null,存在则调用函数式接口,根据key和oldValue计算newValue,如果newValue不为null则用newValue替换掉oldValue,如果newValue为null,则删掉key,返回null。
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
// delete mapping
if (oldValue != null || containsKey(key)) {
// something to remove
remove(key);
return null;
} else {
// nothing to do. Leave things as they were.
return null;
}
} else {
// add or replace old mapping
put(key, newValue);
return newValue;
}
}
第一个参数是key,第二个参数是函数式接口,传入的参数有key和oldValue
先根据key获取oldValue,根据函数式接口计算newValue,如果newValue不为null,就put(key, newValue),可能是新增也可能是覆盖,newValue为null,则判断oldValue是否存在,存在则删掉key,返回null,不存在则直接返回null。
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if (newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
第一个参数是key,第二个参数是不为null的value,第三个参数是一个函数式接口,参数是oldValue和传入的第二个参数value。
首先根据key获取oldValue,如果oldValue为null,则newValue的值为value,否则为函数式接口的返回值。
如果newValue为null,则删除key,否则put(key,newValue)。
十六、HashMap类
1)存储无序的。
2)键和值位置都可以是 null,但是键位置只能存在一个 null。
3)键位置是唯一的,是底层的数据结构控制的。
4)jdk1.8 前数据结构是链表+数组,jdk1.8 之后是链表+数组+红黑树。
5)阈值(边界值)> 8 并且数组长度大于 64,才将链表转换为红黑树,变为红黑树的目的是为了高效的查询。
16.1 初始默认参数
DEFAULT_INITIAL_CAPACITY 集合的初始化容量(必须是 2 的 n 次幂):16
MAXIMUM_CAPACITY 集合最大容量 2的30次方1073741824
DEFAULT_LOAD_FACTOR 默认的负载因子(默认值 0.75)
TREEIFY_THRESHOLD 当链表的值超过8并且数组长度大于64,则会转为红黑树(jdk1.8新增
UNTREEIFY_THRESHOLD 当链表的值小于 6 则会从红黑树转回链表
MIN_TREEIFY_CAPACITY 64 当 Map 里面的数量超过这个值时,表中的桶才能进行树形化,否则桶内元素太多时会扩容,而不是树形化为了避免进行扩容、树形化选择的冲突,这个值不能小于4*TREEIFY_THRESHOLD(8)
Node节点
参数
transient Node<K,V>[] table; //HashMap的哈希桶数组,非常重要的存储结构,用于存放表示键值对数据的Node元素。
transient Set<Map.Entry<K,V>> entrySet; //HashMap将数据转换成set的另一种存储形式,这个变量主要用于迭代功能。
transient int size; //HashMap中实际存在的Node数量,注意这个数量不等于table的长度,甚至可能大于它,因为在table的每个节点上是一个链表(或RBT)结构,可能不止有一个Node元素存在。
transient int modCount; //HashMap的数据被修改的次数,这个变量用于迭代过程中的Fail-Fast机制,其存在的意义在于保证发生了线程安全问题时,能及时的发现(操作前备份的count和当前modCount不相等)并抛出异常终止操作。
int threshold; //HashMap的扩容阈值,在HashMap中存储的Node键值对超过这个数量时,自动扩容容量为原来的二倍。
final float loadFactor; //HashMap的负载因子,可计算出当前table长度下的扩容阈值:threshold = loadFactor * table.length。
16.2 构造方法
赋初始容量和负载因子
赋初始容量,负载因子则为默认的0.75
无参构造,所有元素都为默认值
构造参数为Map集合
负载因子为默认的0.75,然后调用putMapEntries方法。
在putMapEntries方法中,先得出参数m的大小,只有在有数据才进行下一步:
如果HashMap没有存数据,则将m的大小除以负载因子再加一,将此大小同最大容量进行对比选取,选出来的值如果大于扩容阈值,则会调整最大容量。
如果table不为null,也就是HashMap中有数据,则判断m的大小大于扩容阈值并且原HashMap的元素数量小于最大容量才会重新调整table的大小。
然后遍历m,获取每一对key-value,调用putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// tab:即table数组,p:数组下标i存储的链表或者红黑树的首节点,n:数组的长度,
// i:哈希取模后的下标
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 如果table数组的长度为0,则使用resize方法初始化数组
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 如果哈希取模后对应的数组下标节点数据为空,则新创建节点,当前k-v为节点中第 一条数据
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 当不为空时
else {
Node<K,V> e; K k;
// 如果当前k-v与首节点哈希值和key都相等,赋值p->e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 当前节点为红黑树,按照红黑树的方式添加k-v值
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 判断节点类型为链表类型,循环遍历链表 这里只是添加新的而不处理同一个元素value的更新
else {
for (int binCount = 0; ; ++binCount) {
// 节点为尾部节点,当前k-v作为新节点并添加到链表的尾部
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 当节点数>=8 转化为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 当前遍历到的节点e的哈希值和k-v相等,则退出
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;
// 如果当前map中包含的k-v键值超过了阈值threshold则扩容
if (++size > threshold)
resize();
// 空函数
afterNodeInsertion(evict);
return null;
}
HashMap中是通过对 key 的哈希取模后的值定位到数组的下标位置的,但是hash(key) % length的运算效率很低。在数学中hash(key) & (n - 1)的结果是跟hash(key) % n取模结果是一样的,但是与运算的性能要比hash对n取模要高很多。因此在源码中的tab[i = (n - 1) & hash]就是对数组长度做哈希取模运算。
16.3 其他方法
根据key获取value,其实主要是调用getNode方法
首先判断table中是否有数据,然后根据输入的key计算得到hash值,再根据hash值计算出对于的下标(n-1)&hash,如果不存在节点则直接返回null,存在则先比较第一个节点,然后判断是有子节点,有就进行do-while比较hash和key。
是否包含key,也就是调用getNode是否不返回null
新增方法,调用putVal方法,onlyIfAbsent为false表示无论旧的Value是否为空,都会更新,为true时,则只有当旧Value为null时才会更新数据
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
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
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);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
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;
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;
}
判断是否转换为红黑树,第一个if,table为空或者数组的长度小于64时只是resize扩容。
put进map集合m
根据key删除map,调用的是removeNode方法
首先是判断table不为空,并且根据hash找到的位置也不为空,然后判断第一个节点是否满足要求,不满足则遍历,有满足的则将该节点赋值给node,遍历完后判断nodel中有数据则实现节点删除,树结构是调用removeTreeNode方法,链表是判断是否是第一个节点,是则将下一个节点直接赋值给tabel[index],不是则将该节点的下一个节点赋值给上一个节点。
清空hashmap
判断是否包含某个value值,其实就是遍历table数组比对
将hashMap存进一个Set中返回,方便进行遍历。
十七、LinkedHashMap类
LinkedHashMap继承自HashMap,它的多种操作都是建立在HashMap操作的基础上的。同HashMap不同的是,LinkedHashMap维护了一个Entry的双向链表,保证了插入的Entry中的顺序。这也是Linked的含义。结构图如下:
为了实现双向链表,LinkedHashMap中提供了如下的Entry:
17.1 主要元素
accessOrder的作用就是控制访问顺序,设置为true后每次访问一个元素,就将该元素所在的Node变成最后一个节点,改变该元素在LinkedHashMap中的存储顺序。
17.2 构造函数
构造函数如果不明确传入accessOrder的话,默认都是按插入序的
17.3 维护链表的操作方法
afterNodeRemoval,afterNodeInsertion,afterNodeAccess。这三个方法的主要作用是,在删除,插入,获取节点之后,对链表进行维护。简单来说,这三个方法中执行双向链表的操作:
17.4 其他方法
遍历匹配判断
增加了判断是否改变get后的元素顺序
LinkedHashMap没有重写HashMap的put方法,所以执行put操作的时候,还是使用的是HashMap的put方法。那么这样如何保证链表的逻辑呢?原因就是HashMap的putVal方法中实际调用了维护链表的方法,下面是关键代码:HashMap的putVal()方法
在put方法中,HashMap会在合适的位置使用 afterNodeAccess(e),和afterNodeInsertion(evict);方法。因为在HashMap中也定义了这三个函数,但是都是为空函数,在LInkedHashMap中只是重写了这3个方法。我们在使用map.put(key,value)的时候,实际调用HashMap#putVal(key,value)方法,然后再调用afterNodeAccess方法。
remove方法也是没有重写hashMap的remove方法。
十八、HashTable类
HashTable和HashMap的原理是一样的。都是一个哈希表的数据结构。HashTable使用了synchronized同步方法,所以是线程安全的。
18.1 主要元素
18.2 构造方法
18.3 其他方法
是否包含value,实际是调用contains方法
put成功会返回null,put是如果是覆盖则会返回oldValue
删除成功会返回Value,删除失败返回null。
putAll会遍历Map集合t,调用put方法。
清空Hashtable集合
将key转换为Set集合,用来遍历
18.4 扩容机制
十九、Properties类
Properties(Java.util.Properties),该类主要用于读取Java的配置文件,不同的编程语言有自己所支持的配置文件,配置文件中很多变量是经常改变的,为了方便用户的配置,能让用户够脱离程序本身去修改相关的变量设置。就像在Java中,其配置文件常为.properties文件,是以键值对的形式进行参数配置的,key = value;
19.1 构造方法
19.2 其他方法
调用hashtable的put方法
public synchronized void load(Reader reader)
以字符流将文件的键值对存入propertites中
public synchronized void load(InputStream inStream)
以字节流将文件的键值对存入properties中,注意字节流默认为unicode编码,中文会乱码
public Set stringPropertyNames()
将properties的key转换为Set集合,便于遍历
public void store(Writer writer, String comments)
以字符流将propertites的键值对存入文件中,String comments:注释:用来解释说明保存的文件是做什么用的
public void store(OutputStream out, String comments)
以字节流将propertites的键值对存入文件中,String comments:注释:用来解释说明保存的文件是做什么用的。注意字节流默认为unicode编码,中文会乱码
text2.txt文件没有预先创建
public synchronized void loadFromXML(InputStream in)
从XML文件中以字节输入流的方式读取文件中的键值对
public void storeToXML(OutputStream os, String comment)
不指定编码 默认为:UTF-8,其实是调用下面一个方法,encoding参数为default
public void storeToXML(OutputStream os, String comment, String encoding)
public String getProperty(String key)
如果根据key获取的value为null并且defaults不为null,那么会返回defaults中根据key获取的value,否者会返回根据参数key获取的value
public String getProperty(String key, String defaultValue)
如果value不存在会返回defaultValue,否则会返回value
二十、SortedMap接口
20.1 特点
SortedMap的主要特征是它根据键的自然顺序或指定的比较器对键进行排序。因此,当你想要一个满足以下条件时,便可以选用:
不允许空键或空值。
键按自然顺序或指定的比较器排序。
20.2 包含的方法
Comparator<? super K> comparator(); 获取比较器
SortedMap<K,V> subMap(K fromKey, K toKey); 获取key值在[fromKey,toKey)间的子集,左闭右开
SortedMap<K,V> headMap(K toKey); 获取key值小toKey的子集
SortedMap<K,V> tailMap(K fromKey); 获取key值大于或等于fromKey的子集
K firstKey(); 获取首元素
K lastKey(); 获取尾元素
二十一、TreeMap类
TreeMap 是一个有序的key-value集合,基于红黑树(Red-Black tree)实现,每个key-value作为红黑树的一个节点。
红黑树:性质1. 节点是红色或黑色。
性质2. 根节点是黑色。
性质3 每个叶节点是黑色的。
性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
TreeMap存储时会进行排序的,会根据key来对key-value键值对进行排序,其中排序方式也是分为两种,一种是默认排序(按key的升序),一种是定制排序,具体取决于使用的构造方法。在前面TreeSet中讲过构造器定制排序的方法。
21.1 主要元素
21.2 构造方法
21.3 其他方法
public boolean containsKey(Object key);是否包含参数key
public boolean containsValue(Object value);是否包含参数value
public V get(Object key);根据key获取value,不存在则返回null
public K firstKey();获取第一个key
public K lastKey();获取最后一个key
public void putAll(Map<? extends K, ? extends V> map);将map集合put进treeMap
public V put(K key, V value);
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
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 {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
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;
}
public V remove(Object key);根据key删除
public Map.Entry<K,V> firstEntry();返回第一个Entry
public Map.Entry<K,V> lastEntry();返回最后一个Entry
public Map.Entry<K,V> pollFirstEntry();弹出第一个Entry
public Map.Entry<K,V> pollLastEntry();弹出最后一个Entry
public Map.Entry<K,V> lowerEntry(K key);返回小于key的第一个元素
public K lowerKey(K key);返回小于key的第一个键
public Map.Entry<K,V> floorEntry(K key);返回小于等于key的第一个元素
public K floorKey(K key);返回小于等于key的第一个键
public Map.Entry<K,V> ceilingEntry(K key);返回与大于或等于给定键的最小键相关联的键值映射,如果没有此键,则返回 null
public K ceilingKey(K key);返回与大于或等于给定键的最小键,如果没有此键,则返回 null
public Map.Entry<K,V> higherEntry(K key);返回大于key的第一个元素
public K higherKey(K key);返回大于key的第一个键
同HashMap中方法一致
二十二、集合遍历
22.1 普通for循环
只适合List集合
22.2 增强for循环
适合List和Set集合
22.3 For-Each
List,Set,Map都行
22.4 迭代器
Iterator适合List,Set; ListIterator只适合List
22.5 keySet和EntrySet
只适合Map集合
22.6 Stream流
所有的Collection集合和数组都可以使用
获取流:
所有的 Collection 集合都可以通过 stream 默认方法获取流;list.stream();
Stream 接口的静态方法 of 可以获取数组对应的流;Stream.of(array);
常用方法:
延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
终结方法:返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用。 count 和 forEach 方法就是终结方法。
- 逐一处理:forEach
- 过滤:filter
可以通过 filter 方法将一个流转换成另一个子集流。该方法将会产生一个boolean值结果,代表指定的条件是否满足。如果结果为true,那么Stream流的 filter 方法将会留用元素;如果结果为false,那么 filter 方法将会舍弃元素。
- 映射:map
如果需要将当前流中的T类型数据转换为另一种R类型的流,可以使用 map 方法。方法签名:
该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
Stream map(Function<? super T, ? extends R> mapper);
- 统计个数:count
流提供 count 方法来数一数其中的元素个数,该方法返回一个long值代表元素个数
- 取用前几个:limit
- 组合:concat
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat
- 去重:distinct
- 收集元素:collect
收集流中元素到指定集合中
//1.收集数据到list集合中
stream.collect(Collectors.toList())
//2.收集数据到set集合中
stream.collect(Collectors.toSet())
//3.收集数据到指定的集合中
stream.collect(Collectors.toCollection(Supplier collectionFactory))
- toArray:收集到数组中
无参是返回Object类型,有参可以返回参数指定类型