文章目录
- Collection
- 1.单列集合框架结构
- 2.Collection接口常用方法:
- 3.Collection集合与数组间的转换
- 4.使用Collection集合存储对象,要求对象所属的类满足:
- 5.遍历Collection的两种方式:
- 要求:
- List
- 1. 存储的数据特点:存储有序的、可重复的数据。
- 2. 常用方法:(记住)
- 3. 常用实现类:
- 4. 源码分析(难点)
- 4.1 ArrayList
- 4.2 LinkedList
- 4.3 Vector
- 成员变量
- 构造函数定义
- 方法定义
- ensureCapacity(int minCapacity) 扩容详解
- copyInto(Object[] anArray)详解
- trimToSize() 详解
- setSize(int newSize) 详解
- capacity()、size()、isEmpty() 详解
- elements() 详解
- get(int index)、elementData(int index)详解
- set(int index, E element)、setElementAt(E obj, int index) 详解
- add(E e)、add(int index, E element)、insertElementAt(E obj, int index)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c) 详解
- remove(Object o)、removeElement(Object obj)、removeElementAt(int index)、remove(int index)、removeAll(Collection<?> c) 详解
- clear()、removeAllElements() 详解
- indexOf(Object o)、indexOf(Object o, int index) 详解
- contains(Object o)、containsAll(Collection<?> c) 详解
- retainAll(Collection<?> c) 详解
- equals(Object o) 详解
- hashCode() 详解
- subList(int fromIndex, int toIndex) 详解
- replaceAll(UnaryOperator operator) 详解
- sort(Comparator<? super E> c) 详解
- toArray()、toArray(T[] a) 详解
- 迭代器
- 总结
- 5. 存储的元素的要求:
- 6. ListIterator
- 7. 常见数据结构
- 8、栈Stack
- 面试题:ArrayList、LinkedList、Vector者的异同?
- Set
- Queue
- Collections
- Map
集合
Collection
1.单列集合框架结构
|----Collection接口:单列集合,用来存储一个一个的对象
-
|----List接口:存储有序的、可重复的数据。 -->“动态”数组
-
|----ArrayList、LinkedList、Vector
-
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
-
|----HashSet、LinkedHashSet、TreeSet
2.Collection接口常用方法:
1)添加
add(Object obj):添加元素
addAll(Collection c):添加集合元素
2)删除
clear():清空
remove(Object obj):删除元素
removeAll(Collection c):删除集合元素
3)判断
isEmpty():是否为空
contains(Object obj):是否包含某个元素
containsAll(Collection c):是否包含指定集合元素
4)获取
size():集合大小
Iterator iterator():获取迭代器对象
5)其它功能
Object[] toArray():集合转数组
retainAll(Collection c):两个集合交集操作
3.Collection集合与数组间的转换
//集合 —>数组:toArray()
Object[] arr = coll.toArray();
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
//拓展:数组 —>集合:调用Arrays类的静态方法asList(T … t)
List list = Arrays.asList(new String[]{“AA”, “BB”, “CC”});
System.out.println(list);
List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());//1
List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2
4.使用Collection集合存储对象,要求对象所属的类满足:
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().
5.遍历Collection的两种方式:
① 使用迭代器Iterator ② foreach循环(或增强for循环)
2.java.utils包下定义的迭代器接口:Iterator
2.1说明:
Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
2.2作用:遍历集合Collectiton元素
2.3如何获取实例:coll.iterator()返回一个迭代器实例
2.4图示说明:
2.5remove方法的使用
3.jdk5.0新特性–增强for循环:(foreach循环)
简化数组或者集合的遍历
格式:
for(元素的数据类型 变量名:数组名或集合名){
}
4、可变参数
定义方法的时候不知道参数的个数
格式
修饰符 返回值类型 方法名(数据类型… 变量名){
方法体;
}
说明:变量名是所参数构成的一个数组的名称
注意:如果既可变参数,又其它参数,需要将可变参数放在最后面
要求:
层次一:选择合适的集合类去实现数据的保存,调用其内部的相关方法。
层次二:不同的集合类底层的数据结构为何?如何实现数据的操作的:增删改查等。
List
1. 存储的数据特点:存储有序的、可重复的数据。
按顺序存放元素,元素可以重复(可以存放null值)
实现类:
ArrayList:数组实现,查询快,增删慢,轻量级;(线程不安全)
LinkedList:双向链表实现,增删快,查询慢 (线程不安全)
Vector:数组实现,重量级 (线程安全、使用少)
2. 常用方法:(记住)
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()`
遍历:① Iterator迭代器方式
② 增强for循环
③ 普通的循环
int size()//返回集合的长度
boolean isEmpty();//集合是否为空
boolean contains(Object o);//如果此列表包含指定的元素,则返回 true。
boolean add(E e);//增加元素
boolean remove(Object o);//删除元素
E get(int index);//获得指定位置上的元素
int indexOf(Object o);//返回此列表中指定元素最后一次出现的索引,如果此列表不包含该元素,则返回 -1
Iterator<E> iterator()//返回一个迭代器
3. 常用实现类:
|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储序的、可重复的数据。 -->“动态”数组,替换原的数组
|----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
|----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储 |----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
4. 源码分析(难点)
4.1 ArrayList
底层结构是数组,查询快,增删慢底层不安全,效率高
2.实现了四个接口
①:List接口:得到了List接口框架基础功能。
②:RandomAccess接口,获得了快速随机访问存储元素的功能,
③:Cloneable,得到了clone()方法,可以实现克隆功能;
④:Serializable,表示可以被序列化,通过序列化去传输,典型的应用就是hessian协议。
1) jdk 7情况下
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
list.add(123);//elementData[0] = Integer.valueOf(123);
…
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
2) jdk 8中ArrayList的变化:
//底层Object[] elementData初始化为{}.并没创建长度为10的数组
ArrayList list = new ArrayList();
//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
list.add(123);
…
后续的添加和扩容操作与jdk 7 无异。
3) 小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象
的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
ArrayList集合的使用
package com.xms.collection.day01;
import java.util.ArrayList;
//ArrayList集合的使用
public class ArrayListDemo {
public static void main(String[] args) {
// 创建ArrayList
ArrayList list = new ArrayList();
// 添加元素
list.add("hello");
list.add("World");
list.add("java");
list.add("World");
// 删除元素
// list.remove("World");//只删除符合条件的第一个元素
// list.clear();//删除所有元素,清空
// 判断
System.out.println("是否为空!" + list.isEmpty());// f
System.out.println("是否为空!" + list.contains("hello"));// t
// 获取集合大小(元素个数)
System.out.println(list.size());// 4
System.out.println("list" + list);
// 集合转数组
Object[] array = list.toArray();
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
}
package com.xms.collection.day01;
import java.util.ArrayList;
import java.util.Iterator;
//ArrayList集合的使用
public class ArrayListDemo02 {
public static void main(String[] args) {
//ArrayList中和index相关的方法
ArrayList list = new ArrayList<>();
//1)在指定索引出添加元素
list.add(0, "hello");
list.add(1, "World");
list.add(2, "java");
//删除指定索引处的元素
//list.remove(0);
//list.remove(list.size() - 1);
//设置指定索引处的元素
list.set(list.size() - 1, "javase");
//查询指定索引处的元素
System.out.println(list.get(0));
System.out.println(list.get(list.size()-1));
System.out.println(list);
/*
* list集合的遍历方式
* 1)集合转数组(不推荐)
* 2)普通for循环,通过索引获取对应元素
* 3)迭代器
*/
}
//遍历方式3:迭代器
public static void printEle2(ArrayList list) {
//获取集迭代器
Iterator it = list.iterator();
while (it.hasNext()) {//是否有下一个元素
String s = (String) it.next();//获取下一个元素
System.out.println(s);
}
}
//遍历方式2:普通for循环,通过索引获取对应元素
public static void printEle(ArrayList list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
泛型
package com.xms.collection.day01;
import java.util.ArrayList;
import java.util.Iterator;
/*
* 泛型
* <引用类型>
* 集合中的泛型的作用
* 限定集合中元素的类型
*/
public class ArrayListDemo03 {
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
//1)在指定索引出添加元素
list.add(0, "hello");
list.add(1, "World");
list.add(2, "java");
list.add(100);
Iterator<Object> it = list.iterator();
while (it.hasNext()) {
Object s = it.next();
System.out.println(s);
}
}
}
4.2 LinkedList
底层是链表,查询慢,增删快;线程不同步,不安全,效率高
LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。
其中,Node定义为:(体现了LinkedList的双向链表的说法)
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;
}
}
特有功能:
addFirst(E e):在最前面添加元素
addLast(E e):在最后面添加元素
getFirst():获取第一个元素
getLast():获取最后一个元素
E removeFirst():移除第一个元素
E removeLast():移除最后一个元素
package com.xms.collection.day02;
import org.junit.Test;
import java.util.LinkedList;
/**
* LinkedList implements List,Deque
* LinkedList 具有List的特性(使用index)
* LinkedList 具有Deque的特性(先进先出,后进后出)
* LinkedList 封装了一些方法,方便我们操作头元素
* @author hsl
* @create 2022-07-21 上午 11:32
*/
public class LinkedListDemo {
@Test
public void demo(){
LinkedList ll = new LinkedList();
ll.add("hello");
ll.add("java");
ll.add("world");
ll.addFirst("first");
ll.addLast("last");
System.out.println(ll);
System.out.println(ll.getFirst());
System.out.println(ll.getLast());
ll.removeFirst();
ll.removeLast();
System.out.println(ll);
}
}
4.3 Vector
底层结构是数组,查询快,增删慢;底层安全,效率低
jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
在扩容方面,默认扩容为原来的数组长度的2倍。
特有功能:
addElement(E obj):添加元素
E elementAt(int index):指定索引位置处对应的元素值
Enumeration elements():获取集合所元素构成的一个枚举对象
Enumeration里面方法
hasMoreElements():是否更多元素
nextElement():获取下个元素
Vector类实现了一个可增长的对象数组,像数组一样包含可以使用整数索引访问的组件,大小可以根据需要增加或缩小,以适应在创建 Vector 后添加和删除
每个向量都试图通过维护 capacity 和 capacityIncrement 增量来优化存储管理,capacity 总是至少与向量大小一样大;通常更大,因为随着组件被添加到向量中,向量的存储以 capacityIncrement 增量大小的块的形式增加
该类的 iterator 和 listIterator 方法返回的 iterator 是快速失败的:如果在创建迭代器后的任何时间对向量进行结构修改,除了通过迭代器自己的remove或add方法外,迭代器将抛出 ConcurrentModificationException。因此,面对并发修改,迭代器快速而干净地失败,而不是冒着在未来不确定的时间出现任意、非确定性行为的风险。elements方法返回的Enumerations 不是快速失败的
从 Java 2 平台 v1.2 开始,该类经过改造以实现List接口,使其成为Java Collections Framework的成员。与新的集合实现不同,Vector是同步的、线程安全的。如果不需要线程安全的实现,建议使用ArrayList代替Vector
成员变量
// 存储向量组件的数组缓冲区
protected Object[] elementData;
// Vector对象中有效组件的数量
protected int elementCount;
// 当向量的大小大于其容量时,向量的容量自动增加的量
protected int capacityIncrement;
构造函数定义
方法 | 作用 |
---|---|
Vector() | 构造一个空向量,使其内部数据数组的大小为 10 ,标准容量增量为零 |
Vector(int initialCapacity) | 构造具有指定初始容量并且其容量增量等于零的空向量 |
Vector(int initialCapacity, int capacityIncrement) | 构造具有指定的初始容量和容量增量的空向量 |
Vector(Collection<? extends E> c) | 构造一个包含指定集合元素的向量,按照集合的迭代器返回的顺序 |
详解
/**
* 无参构造
*/
public Vector() {
// 调用Vector(int initialCapacity),初始容量 initialCapacity 默认为 10
this(10);
}
/**
* 具有指定初始容量的构造器,增量等于零的空向量
*/
public Vector(int initialCapacity) {
// 调用 Vector(int initialCapacity, int capacityIncrement),初始容量为10,增量为0
this(initialCapacity, 0);
}
/**
* 具有指定的初始容量和容量增量的空向量构造器
*/
public Vector(int initialCapacity, int capacityIncrement) {
super();
// 如果初始容量小于0,抛出IllegalArgumentException
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
// 向量数组缓冲区为 initialCapacity 长度的Object数组
this.elementData = new Object[initialCapacity];
// 增量为0
this.capacityIncrement = capacityIncrement;
}
/**
* 构造一个包含指定集合元素的向量,按照集合的迭代器返回的顺序
*/
public Vector(Collection<? extends E> c) {
// 向量数组缓冲区为指定对象转为数组
elementData = c.toArray();
// 有效元素数量为向量数组缓冲区的长度
elementCount = elementData.length;
// 如果向量数组缓冲区类对象不等于Object数组类对象
if (elementData.getClass() != Object[].class)
// 调用本地函数拷贝数组
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
方法定义
方法 | 作用 | |
添加 | boolean add(E e) | 将指定的元素追加到此Vector的末尾 |
void add(int index, E element) | 在此Vector中的指定位置插入指定的元素 | |
boolean addAll(Collection<? extends E> c) | 将指定集合中的所有元素追加到该向量的末尾,按照它们由指定集合的迭代器返回的顺序 | |
boolean addAll(int index, Collection<? extends E> c) | 将指定集合中的所有元素插入到此向量中的指定位置 | |
void addElement(E obj) | 将指定的组件添加到此向量的末尾,将其大小增加1 | |
void insertElementAt(E obj, int index) | 在指定的index插入指定对象作为该向量中的一个 index | |
修改 | E set(int index, E element) | 用指定的元素替换此Vector中指定位置的元素 |
void setElementAt(E obj, int index) | 设置在指定的组件 index此向量的要指定的对象 | |
void replaceAll(UnaryOperator operator) | 将该列表的每个元素替换为将该运算符应用于该元素的结果 | |
void setSize(int newSize) | 设置此向量的大小 | |
void trimToSize() | 修改该向量的容量成为向量的当前大小 | |
查询 | int size() | 返回此向量中的组件数 |
int capacity() | 返回此向量的当前容量 | |
boolean contains(Object o) | 如果此向量包含指定的元素,则返回 true | |
boolean containsAll(Collection<?> c) | 如果此向量包含指定集合中的所有元素,则返回true | |
boolean equals(Object o) | 将指定的对象与此向量进行比较以获得相等性 | |
E elementAt(int index) | 返回指定索引处的组件 | |
Enumeration elements() | 返回此向量的组件的枚举 | |
E firstElement() | 返回此向量的第一个组件(索引号为 0的项目) | |
E get(int index) | 返回此向量中指定位置的元素 | |
int hashCode() | 返回此Vector的哈希码值 | |
int indexOf(Object o) | 返回此向量中指定元素的第一次出现的索引,如果此向量不包含元素,则返回-1 | |
int indexOf(Object o, int index) | 返回此向量中指定元素的第一次出现的索引,从 index向前 index ,如果未找到该元素,则返回-1 | |
int lastIndexOf(Object o) | 返回此向量中指定元素的最后一次出现的索引,如果此向量不包含元素,则返回-1 | |
int lastIndexOf(Object o, int index) | 返回此向量中指定元素的最后一次出现的索引,从 index ,如果未找到元素,则返回-1 | |
E lastElement() | 返回向量的最后一个组件 | |
boolean isEmpty() | 测试此矢量是否没有组件 | |
List subList(int fromIndex, int toIndex) | 返回此列表之间的fromIndex(包括)和toIndex之间的独占视图 | |
删除 | void clear() | 从此Vector中删除所有元素 |
E remove(int index) | 删除此向量中指定位置的元素 | |
boolean remove(Object o) | 删除此向量中指定元素的第一个出现如果Vector不包含元素,则它不会更改 | |
boolean removeAll(Collection<?> c) | 从此Vector中删除指定集合中包含的所有元素 | |
void removeAllElements() | 从该向量中删除所有组件,并将其大小设置为零 | |
boolean removeElement(Object obj) | 从此向量中删除参数的第一个出现次数 | |
void removeElementAt(int index) | 删除指定索引处的组件 | |
boolean removeIf(Predicate<? super E> filter) | 删除满足给定谓词的此集合的所有元素 | |
protected void removeRange(int fromIndex, int toIndex) | 从此列表中删除所有索引为 fromIndex (含)和 toIndex之间的元素 | |
boolean retainAll(Collection<?> c) | 仅保留此向量中包含在指定集合中的元素 | |
扩容 | void ensureCapacity(int minCapacity) | 如果需要,增加此向量的容量,以确保它可以至少保存最小容量参数指定的组件数 |
克隆 | Object clone() | 返回此向量的克隆 |
迭代器 | Iterator iterator() | 以正确的顺序返回该列表中的元素的迭代器 |
ListIterator listIterator() | 返回列表中的列表迭代器 | |
ListIterator listIterator(int index) | 从列表中的指定位置开始,返回列表中的元素的列表迭代器 | |
Spliterator spliterator() | 在此列表中的元素上创建late-binding和故障切换 Spliterator | |
循环 | void forEach(Consumer<? super E> action) | 对 Iterable的每个元素执行给定的操作,直到所有元素都被处理或动作引发异常 |
复制到数组 | void copyInto(Object[] anArray) | 将此向量的组件复制到指定的数组中 |
转数组 | Object[] toArray() | 以正确的顺序返回一个包含此Vector中所有元素的数组 |
T[] toArray(T[] a) | 以正确的顺序返回一个包含此Vector中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型 | |
转string | String toString() | 返回此Vector的字符串表示形式,其中包含每个元素的String表示形式 |
排序 | void sort(Comparator<? super E> c) | 使用提供的 Comparator对此列表进行排序以比较元素 |
ensureCapacity(int minCapacity) 扩容详解
/**
* 扩容
* 增加此向量的容量,确保它可以至少保存最小容量参数指定的元素
*/
public synchronized void ensureCapacity(int minCapacity) {
// 如果参数大于0
if (minCapacity > 0) {
modCount++;
// 确保容量
ensureCapacityHelper(minCapacity);
}
}
/**
* 确保容量
*/
private void ensureCapacityHelper(int minCapacity) {
// 如果参数减去向量数组缓冲区长度大于0
if (minCapacity - elementData.length > 0)
// 增长容量
grow(minCapacity);
}
/**
* 扩容
*/
private void grow(int minCapacity) {
// 获取当前向量数组缓冲区长度(旧容量)
int oldCapacity = elementData.length;
// 如果增量大于0,新容量 为 旧容量 + 增量
// 如果增量小于等于0,新容量 为 旧容量 + 旧容量
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
// 如果新容量 - minCapacity 小于0
if (newCapacity - minCapacity < 0)
// 新容量为 minCapacity
newCapacity = minCapacity;
// 如果新容量 - 2147483639 大于0
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 获取大容量
newCapacity = hugeCapacity(minCapacity);
// 复制数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 大容量
*/
private static int hugeCapacity(int minCapacity) {
// 如果参数小于0,throw OutOfMemoryError
if (minCapacity < 0)
throw new OutOfMemoryError();
// 如果 minCapacity 大于 21474836339,返回 21474836347
// 如果 minCapacity 小于 21474836339,返回 21474836339
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
copyInto(Object[] anArray)详解
/**
* 将此向量的元素复制到指定的数组中
*/
public synchronized void copyInto(Object[] anArray) {
// 调用本地函数copy数组
System.arraycopy(elementData, 0, anArray, 0, elementCount);
}
trimToSize() 详解
/**
* 修改该向量的容量成为向量的当前大小
*/
public synchronized void trimToSize() {
modCount++;
// 获取向量数组缓冲区当前长度
int oldCapacity = elementData.length;
// 如果有效元素数量 < 向量数组缓冲区当前长度
if (elementCount < oldCapacity) {
// 复制数组
elementData = Arrays.copyOf(elementData, elementCount);
}
}
setSize(int newSize) 详解
设置向量的大小
*/
public synchronized void setSize(int newSize) {
modCount++;
// 如果新大小 大于 有效元素数量
if (newSize > elementCount) {
// 确认容量,判断是否调用 grow 方法
ensureCapacityHelper(newSize);
} else {
// 循环置空元素
for (int i = newSize ; i < elementCount ; i++) {
elementData[i] = null;
}
}
// 设置有效元素数量为 newSize
elementCount = newSize;
}
capacity()、size()、isEmpty() 详解
/**
* 获取向量数组缓冲区当前长度
*/
public synchronized int capacity() {
return elementData.length;
}
/**
* 获取有效元素数量
*/
public synchronized int size() {
return elementCount;
}
/**
* 判断是否为null
*/
public synchronized boolean isEmpty() {
return elementCount == 0;
}
elements() 详解
/**
* 返回此向量的组件的枚举
*/
public Enumeration<E> elements() {
return new Enumeration<E>() {
int count = 0;
// 是否包含更多元素
public boolean hasMoreElements() {
// 返回 0是否小于有效元素数量
return count < elementCount;
}
// 如果此枚举对象至少有一个要提供的元素,则返回此枚举的下一个元素
public E nextElement() {
synchronized (Vector.this) {
// 0小于有效元素数量
if (count < elementCount) {
// 返回下标为count的元素,之后count+1
return elementData(count++);
}
}
throw new NoSuchElementException("Vector Enumeration");
}
};
}
get(int index)、elementData(int index)详解
/**
* 根据下标获取元素
*/
public synchronized E get(int index) {
// 如果下标大于等于有效元素数量,throw ArrayIndexOutOfBoundsException
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
// 调用elementData(int index)
return elementData(index);
}
/**
* 根据下标获取元素
*/
E elementData(int index) {
// 返回index所在对象
return (E) elementData[index];
}
set(int index, E element)、setElementAt(E obj, int index) 详解
/**
* 用指定的元素替换此Vector中指定位置的元素,并返回旧值
*/
public synchronized E set(int index, E element) {
// 如果下标大于等于有效元素数量,throw ArrayIndexOutOfBoundsException
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
// 获取index所在元素
E oldValue = elementData(index);
// 赋值新元素
elementData[index] = element;
// 返回旧元素
return oldValue;
}
/**
* 用指定的元素替换此Vector中指定位置的元素
*/
public synchronized void setElementAt(E obj, int index) {
// 如果下标大于等于有效元素数量,throw ArrayIndexOutOfBoundsException
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
// 赋值
elementData[index] = obj;
}
add(E e)、add(int index, E element)、insertElementAt(E obj, int index)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c) 详解
/**
* 将指定的元素追加到此Vector的末尾
*/
public synchronized boolean add(E e) {
modCount++;
// 判断有效元素数量+1-向量数组缓冲区长度是否大于0,是否增长
ensureCapacityHelper(elementCount + 1);
// 根据下标赋值
elementData[elementCount++] = e;
return true;
}
/**
* 指定位置插入指定的元素
*/
public void add(int index, E element) {
// 调用 insertElementAt(E obj, int index)
insertElementAt(element, index);
}
/**
* 指定位置插入指定的元素
*/
public synchronized void insertElementAt(E obj, int index) {
modCount++;
// 如果下标大于有效元素数量,throw ArrayIndexOutOfBoundsException
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index
+ " > " + elementCount);
}
// 判断有效元素数量+1-向量数组缓冲区长度是否大于0,是否增长
ensureCapacityHelper(elementCount + 1);
// 拷贝数组
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
// 赋值
elementData[index] = obj;
// 有效元素数量增长
elementCount++;
}
/**
* 将指定集合中的所有元素追加到该向量的末尾
*/
public synchronized boolean addAll(Collection<? extends E> c) {
modCount++;
// 将参数集合转数组
Object[] a = c.toArray();
// 获取数组长度
int numNew = a.length;
// 判断有效元素数量 + 数组长度 - 向量数组缓冲区长度是否大于0,是否增长
ensureCapacityHelper(elementCount + numNew);
// 拷贝数组
System.arraycopy(a, 0, elementData, elementCount, numNew);
// 有效元素数量增长 numNew 大小
elementCount += numNew;
return numNew != 0;
}
/**
* 将指定集合中的所有元素插入到此向量中的指定位置
*/
public synchronized boolean addAll(int index, Collection<? extends E> c) {
modCount++;
// 如果 index 小于 0 或者 大于 有效元素数量
if (index < 0 || index > elementCount)
throw new ArrayIndexOutOfBoundsException(index);
// 将参数集合转数组
Object[] a = c.toArray();
// 获取数组长度
int numNew = a.length;
// 判断有效元素数量 + 数组长度 - 向量数组缓冲区长度是否大于0,是否增长
ensureCapacityHelper(elementCount + numNew);
// 偏移量 = 有效元素数量 - index
int numMoved = elementCount - index;
// 偏移量大于0
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 偏移量小于或等于0
System.arraycopy(a, 0, elementData, index, numNew);
// 有效元素数量增长 numNew 大小
elementCount += numNew;
return numNew != 0;
}
remove(Object o)、removeElement(Object obj)、removeElementAt(int index)、remove(int index)、removeAll(Collection<?> c) 详解
/**
* 删除指定元素的第一个出现,如果Vector不包含元素
*/
public boolean remove(Object o) {
// 调用removeElement(Object obj)
return removeElement(o);
}
/**
* 删除指定元素的第一个出现,如果Vector不包含元素
*/
public synchronized boolean removeElement(Object obj) {
modCount++;
// 获取指定元素第一次出现的索引
int i = indexOf(obj);
// 元素存在
if (i >= 0) {
// 调用removeElementAt(int index)
removeElementAt(i);
return true;
}
// 元素不存在
return false;
}
/**
* 根据下标删除元素
*/
public synchronized void removeElementAt(int index) {
modCount++;
// 如果index 大于等于 有效元素数量,throw ArrayIndexOutOfBoundsException
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
// 如果index 小于 0,throw ArrayIndexOutOfBoundsException
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
// j 等于 有效元素数量减index减1
int j = elementCount - index - 1;
//j 大于0,复制数组
if (j > 0) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
// 有效元素数量-1
elementCount--;
// 末尾置空
elementData[elementCount] = null;
}
/**
* 删除指定位置的元素,并返回
*/
public synchronized E remove(int index) {
modCount++;
// 如果index 大于等于 有效元素数量,throw ArrayIndexOutOfBoundsException
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
// 获取下标指向元素
E oldValue = elementData(index);
// 偏移量 等于 有效元素数量减index减1
int numMoved = elementCount - index - 1;
// 偏移量大于0
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 末尾置空
elementData[--elementCount] = null;
// 返回原值
return oldValue;
}
/**
* 删除指定集合中包含的所有元素
*/
public synchronized boolean removeAll(Collection<?> c) {
// 调用父类removeAll(Collection<?> c)
return super.removeAll(c);
}
/**
* 父类removeAll(Collection<?> c)
*/
public boolean removeAll(Collection<?> c) {
// 判断是否为null
Objects.requireNonNull(c);
boolean modified = false;
// 获取迭代器
Iterator<?> it = iterator();
// 循环删除
while (it.hasNext()) {
// 指定集合包含下一个元素,删除
if (c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
clear()、removeAllElements() 详解
/**
* 删除所有元素
*/
public void clear() {
// 调用removeAllElements()
removeAllElements();
}
/**
* 删除所有元素
*/
public synchronized void removeAllElements() {
modCount++;
// 循环置空
for (int i = 0; i < elementCount; i++)
elementData[i] = null;
// 有效元素数量置为0
elementCount = 0;
}
indexOf(Object o)、indexOf(Object o, int index) 详解
/**
* 返回指定元素第一次出现的索引,如果不包含此元素,则返回-1
*/
public int indexOf(Object o) {
// 调用indexOf(Object o, int index)
return indexOf(o, 0);
}
/**
* 返回指定元素第一次出现的索引,如果不包含此元素,则返回-1
*/
public synchronized int indexOf(Object o, int index) {
// 指定元素为null
if (o == null) {
// 循环
for (int i = index ; i < elementCount ; i++)
// 下标为i的元素等于null
if (elementData[i]==null)
// 返回下标
return i;
// 指定元素不为null
} else {
for (int i = index ; i < elementCount ; i++)
// 指定元素等于下标为i的元素
if (o.equals(elementData[i]))
// 返回下标
return i;
}
// 不包含此元素,则返回-1
return -1;
}
contains(Object o)、containsAll(Collection<?> c) 详解
/**
* 是否包含指定元素
*/
public boolean contains(Object o) {
// 如果元素所在下标大于等于0,存在
return indexOf(o, 0) >= 0;
}
/**
* 是否包含指定集合中的所有元素
*/
public synchronized boolean containsAll(Collection<?> c) {
return super.containsAll(c);
}
/**
* 父类 containsAll(Collection<?> c)
*/
public boolean containsAll(Collection<?> c) {
// 循环判断是否包含
for (Object e : c)
if (!contains(e))
return false;
return true;
}
retainAll(Collection<?> c) 详解
/**
* 保留包含在指定集合中的元素
*/
public synchronized boolean retainAll(Collection<?> c) {
return super.retainAll(c);
}
/**
* 父类retainAll(Collection<?> c)
*/
public boolean retainAll(Collection<?> c) {
// 判断是否为null
Objects.requireNonNull(c);
boolean modified = false;
Iterator<E> it = iterator();
while (it.hasNext()) {
// 如果指定集合不包含
if (!c.contains(it.next())) {
// 移除
it.remove();
modified = true;
}
}
return modified;
}
equals(Object o) 详解
/**
* 判断是否相等
*/
public synchronized boolean equals(Object o) {
return super.equals(o);
}
/**
* 父类equals(Object obj)
*/
public boolean equals(Object obj) {
return (this == obj);
}
hashCode() 详解
/**
* 获取哈希码值
*/
public synchronized int hashCode() {
return super.hashCode();
}
/**
* 父类hashCode()
*/
public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
subList(int fromIndex, int toIndex) 详解
/**
* 返回此列表fromIndex(包括)和toIndex之间的元素集合
*/
public synchronized List<E> subList(int fromIndex, int toIndex) {
return Collections.synchronizedList(super.subList(fromIndex, toIndex),this);
}
replaceAll(UnaryOperator operator) 详解
/**
* 将该列表的每个元素替换为将该运算符应用于该元素的结果
*/
public synchronized void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final int expectedModCount = modCount;
final int size = elementCount;
for (int i=0; modCount == expectedModCount && i < size; i++) {
// operator.apply()传入一个T类型参数,返回一个R类型对象
elementData[i] = operator.apply((E) elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
sort(Comparator<? super E> c) 详解
/**
* 使用提供的 Comparator对此列表进行排序以比较元素
*/
@SuppressWarnings("unchecked")
@Override
public synchronized void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
// 调用数组工具类,以指定条件排序
Arrays.sort((E[]) elementData, 0, elementCount, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
toArray()、toArray(T[] a) 详解
/**
* 以正确的顺序返回一个包含此Vector中所有元素的数组
*/
public synchronized Object[] toArray() {
// 数组拷贝
return Arrays.copyOf(elementData, elementCount);
}
/**
* 以正确的顺序返回一个包含此Vector中所有元素的数组;
* 返回的数组的运行时类型是指定数组的运行时类型
*/
@SuppressWarnings("unchecked")
public synchronized <T> T[] toArray(T[] a) {
// 如果a的长度 小于 有效元素数量
if (a.length < elementCount)
return (T[]) Arrays.copyOf(elementData, elementCount, a.getClass());
// 数组拷贝
System.arraycopy(elementData, 0, a, 0, elementCount);
// 如果a的长度 大于 有效元素数量
if (a.length > elementCount)
// 末尾置空
a[elementCount] = null;
return a;
}
可以发现 Vector 中所有方法都是由 synchronized 修饰
迭代器
Itr
private class Itr implements Iterator<E> {
int cursor; // 下一个要返回的元素的索引
int lastRet = -1; // 返回的最后一个元素的索引;如果没有返回-1
// 用来判断是否 fail-fast 的变量,迭代器认为应该具有的 modCount 值
// 如果违反此预期,则迭代器已检测到并发修改
int expectedModCount = modCount;
// 判断是否有下一个元素
public boolean hasNext() {
// 下一个要返回的元素的索引 != 元素数量
return cursor != elementCount;
}
// 获取下一个元素
public E next() {
// 加锁
synchronized (Vector.this) {
checkForComodification();
int i = cursor;
if (i >= elementCount)
throw new NoSuchElementException();
cursor = i + 1;
return elementData(lastRet = i);
}
}
// 删除元素
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
// 加锁
synchronized (Vector.this) {
checkForComodification();
Vector.this.remove(lastRet);
expectedModCount = modCount;
}
cursor = lastRet;
lastRet = -1;
}
// 对每个剩余元素执行给定的操作,直到所有元素都已处理或该操作引发异常
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
// 加锁
synchronized (Vector.this) {
final int size = elementCount;
int i = cursor;
if (i >= size) {
return;
}
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) Vector.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
action.accept(elementData[i++]);
}
// 在迭代结束时更新一次下一个要返回的元素的索引及最后一个元素的索引
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
// 判断是否并发修改
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ListItr
final class ListItr extends Itr implements ListIterator<E> {
// 构造器
ListItr(int index) {
super();
cursor = index;
}
// 是否又前一个元素
public boolean hasPrevious() {
return cursor != 0;
}
// 下一个元素索引
public int nextIndex() {
return cursor;
}
// 前一个元素索引
public int previousIndex() {
return cursor - 1;
}
// 获取前一个元素
public E previous() {
synchronized (Vector.this) {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
cursor = i;
return elementData(lastRet = i);
}
}
// 修改
public void set(E e) {
if (lastRet == -1)
throw new IllegalStateException();
synchronized (Vector.this) {
checkForComodification();
Vector.this.set(lastRet, e);
}
}
// 添加
public void add(E e) {
int i = cursor;
synchronized (Vector.this) {
checkForComodification();
Vector.this.add(i, e);
expectedModCount = modCount;
}
cursor = i + 1;
lastRet = -1;
}
}
总结
- Vector实际上是通过一个数组去保存数据的,当我们构造Vecotr时,若使用默认构造函数,则Vector的默认容量大小是10
- 当Vector容量不足以容纳全部元素时,Vector的容量会增加。若容量增加系数 大于0,则将容量的值增加“容量增加系数”;否则,将容量大小增加一倍
- Vector的克隆函数是将全部元素克隆到一个数组中
- 很多方法都加入了synchronized同步语句,来保证线程安全
- 同样在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理,Vector中也允许元素为null
- 遍历Vector,使用索引的随机访问方式最快,使用迭代器最慢
- Vector很多地方都与ArrayList实现大同小异,区别在于ArrayList非线程安全,Vector为线程安全,ArrayList效率更高,Vector更安全
5. 存储的元素的要求:
添加的对象,所在的类要重写equals()方法
只要是实体类就要重写Equals和hashCode()
原因:该类的实例可能会作为HashSet的元素,而HashSet集合是通过HashCode()和equals()来保证元素的唯一性
6. ListIterator
继承Iterator,允许正向或逆向遍历集合,且允许在迭代期间添加集合元素
成员方法:
boolean hasPrevious():是否上一个元素
Object previous():获取上一个元素
add(E ele):添加元素
package com.xms.collection.day02;
import org.junit.Test;
import java.util.ArrayList;
import java.util.ListIterator;
/**
* ListIterator集合的专有迭代器
* ListIterator支持逆向遍历
* ListIterator支持在迭代期间添加、删除、设置元素
* @author hsl
* @create 2022-07-21 下午 2:21
*/
public class ListIteratorDemo {
@Test
public void demo01(){
ArrayList list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");
//获取ListIterator
ListIterator li = list.listIterator();
while (li.hasNext()){
li.next();
}
//逆向遍历(必须要先正向遍历)
while (li.hasPrevious()) {
String previous = (String) li.previous();
System.out.println(previous);
}
}
//需求:判断集合中是否有“world”,如果有就添加“javase”
@Test
public void demo02(){
ArrayList list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");
//获取ListIterator
ListIterator li = list.listIterator();
while (li.hasNext()){
String next = (String) li.next();//ConcurrentModificationException
if ("world".equals(next)){
//迭代期间给集合添加元素
//注意:迭代期间只能通过迭代器去删除和添加集合中的元素
li.add("javase");
//list.add("javase");
}
}
System.out.println(list);
}
}
7. 常见数据结构
数据结构:数据的存储方式
1)栈:先进后出
2)队列:先进先出
3)数组:查询快,增删慢
4)链表:查询慢,增删快
5)树:
6)哈希表:
8、栈Stack
继承Vector
成员方法
E peek():获取栈顶元素
E pop():弹栈(获取栈顶元素并移除
E push(E e):压栈(将元素添加到栈中
package com.xms.collection.day02;
import org.junit.Test;
import java.util.Iterator;
import java.util.Stack;
/**
* @author hsl
* @create 2022-07-21 下午 2:42
*/
public class StackDemo {
@Test
public void demo01(){
//创建栈
Stack stack = new Stack();
stack.add("hello");
stack.add("java");
stack.add("world");
Iterator it = stack.iterator();
while (it.hasNext()) {
Object next = it.next();
System.out.println(next);
}
}
@Test
public void demo02(){
//创建栈
Stack stack = new Stack();
//入栈
stack.add("hello");
stack.add("java");
stack.add("world");
System.out.println("栈顶元素:"+stack.peek());
//弹栈(获取并删除栈顶元素)
/*String popEle = (String) stack.pop();
System.out.println(popEle);*/
/*for (int i = stack.size();i >=0 ;i++){
String popEle = (String) stack.pop();
System.out.println(popEle);
System.out.println(i);
}*/
while (stack.size() > 0) {
String popEle = (String) stack.pop();
System.out.println(popEle);
}
System.out.println(stack);
// Iterator it = stack.iterator();
// while (it.hasNext()) {
// Object next = it.next();
// System.out.println(next);
// }
}
}
面试题:ArrayList、LinkedList、Vector者的异同?
同:三个类都是实现了List接口,存储数据的特点相同:存储序的、可重复的数据
不同:见上
ArrayList和LinkedList的异同?
答:二者都线程不安全,相对线程安全的Vector,执行效率高。此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。对于新增和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。
ArrayList和Vector的区别?
Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。
作业:
写一个类MyLinked,实现双向循环链表的操作
Node head(链表的头)
size(元素个数)
boolean add(E e)
boolean remove(E e)
int size()
Node(节点,内部类)
element(元素)
next(下一个节点)
prev(上一个节点)
Set
1.Set接口
存储的数据特点:无序的、不可重复的元素
具体的:
以HashSet为例说明:
1)无序性:不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的。
2)不可重复性:保证添加的元素照equals()判断时,不能返回true.即:相同的元素只能添加一个。
2. 常用实现类:
|----Collection接口:单列集合,用来存储一个一个的对象
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
|----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
|----TreeSet:可以照添加对象的指定属性,进行排序。
3. HashSet
第1部分HashSet 简介
HashSet 是一个没有重复元素的集合。
唯一性通过HashCode(),equals()来保证
package com.xms.collection.day02;
import java.util.HashSet;
import java.util.Iterator;
/**
* @author hsl
* @create 2022-07-21 下午 5:12
*/
public class HashSetDemo {
public static void main(String[] args) {
HashSet<String> hs = new HashSet<>();
//分析add()源码发现HashSet元素唯一性是通过我们的HashCode(),equals()来保证
hs.add("hello");
hs.add("world");
hs.add("javase");
hs.add("javaee");
Iterator<String> it = hs.iterator();
while (it.hasNext()) {
String next = it.next();
System.out.println(next);
}
}
}
它是由HashMap实现的,不保证元素的顺序,而且HashSet允许使用 null 元素。
HashSet是非同步的。如果多个线程同时访问一个哈希 set,而其中至少一个线程修改了该 set,那么它必须 保持外部同步。这通常是通过对自然封装该 set 的对象执行同步操作来完成的。如果不存在这样的对象,则应该使用 Collections.synchronizedSet 方法来“包装” set。最好在创建时完成这一操作,以防止对该 set 进行意外的不同步访问:
Set s = Collections.synchronizedSet(new HashSet(…));
HashSet通过iterator()返回的迭代器是fail-fast的。
HashSet的构造函数
// 默认构造函数
public HashSet()
// 带集合的构造函数
public HashSet(Collection<? ``extends` `E> c)
// 指定HashSet初始容量和加载因子的构造函数
public` `HashSet(``int` `initialCapacity, ``float` `loadFactor)
// 指定HashSet初始容量的构造函数
public` `HashSet(``int` `initialCapacity)
// 指定HashSet初始容量和加载因子的构造函数,dummy没有任何作用
HashSet(``int` `initialCapacity, ``float` `loadFactor, ``boolean` `dummy)
**HashSet的主要API **
boolean` `add(E object)
void` `clear()
Object clone()
boolean` `contains(Object object)
boolean` `isEmpty()
Iterator<E> iterator()
boolean` `remove(Object object)
int` `size()
第2部分 HashSet数据结构
HashSet的继承关系如下:
java.lang.Object
``java.util.AbstractCollection<E>
``java.util.AbstractSet<E>
``java.util.HashSet<E>
public` `class` `HashSet<E>
``extends` `AbstractSet<E>
``implements` `Set<E>, Cloneable, java.io.Serializable { }
从图中可以看出:
(01) HashSet继承于AbstractSet,并且实现了Set接口。
(02) HashSet的本质是一个"没有重复元素"的集合,它是通过HashMap实现的。HashSet中含有一个"HashMap类型的成员变量"map,HashSet的操作函数,实际上都是通过map实现的。
第3部分 HashSet源码解析(基于JDK1.6.0_45)
为了更了解HashSet的原理,下面对HashSet源码代码作出分析。
package` `java.util;
public` `class` `HashSet<E>
``extends` `AbstractSet<E>
``implements` `Set<E>, Cloneable, java.io.Serializable
{
``static` `final` `long` `serialVersionUID = -5024744406713321676L;
``// HashSet是通过map(HashMap对象)保存内容的
``private` `transient` `HashMap<E,Object> map;
``// PRESENT是向map中插入key-value对应的value
``// 因为HashSet中只需要用到key,而HashMap是key-value键值对;
``// 所以,向map中添加键值对时,键值对的值固定是PRESENT
``private` `static` `final` `Object PRESENT = ``new` `Object();
``// 默认构造函数
``public` `HashSet() {
``// 调用HashMap的默认构造函数,创建map
``map = ``new` `HashMap<E,Object>();
``}
``// 带集合的构造函数
``public` `HashSet(Collection<? ``extends` `E> c) {
``// 创建map。
``// 为什么要调用Math.max((int) (c.size()/.75f) + 1, 16),从 (c.size()/.75f) + 1 和 16 中选择一个比较大的树呢?
``// 首先,说明(c.size()/.75f) + 1
``// 因为从HashMap的效率(时间成本和空间成本)考虑,HashMap的加载因子是0.75。
``// 当HashMap的“阈值”(阈值=HashMap总的大小*加载因子) < “HashMap实际大小”时,
``// 就需要将HashMap的容量翻倍。
``// 所以,(c.size()/.75f) + 1 计算出来的正好是总的空间大小。
``// 接下来,说明为什么是 16 。
``// HashMap的总的大小,必须是2的指数倍。若创建HashMap时,指定的大小不是2的指数倍;
``// HashMap的构造函数中也会重新计算,找出比“指定大小”大的最小的2的指数倍的数。
``// 所以,这里指定为16是从性能考虑。避免重复计算。
``map = ``new` `HashMap<E,Object>(Math.max((``int``) (c.size()/.75f) + ``1``, ``16``));
``// 将集合(c)中的全部元素添加到HashSet中
``addAll(c);
``}
``// 指定HashSet初始容量和加载因子的构造函数
``public` `HashSet(``int` `initialCapacity, ``float` `loadFactor) {
``map = ``new` `HashMap<E,Object>(initialCapacity, loadFactor);
``}
``// 指定HashSet初始容量的构造函数
``public` `HashSet(``int` `initialCapacity) {
``map = ``new` `HashMap<E,Object>(initialCapacity);
``}
``HashSet(``int` `initialCapacity, ``float` `loadFactor, ``boolean` `dummy) {
``map = ``new` `LinkedHashMap<E,Object>(initialCapacity, loadFactor);
``}
``// 返回HashSet的迭代器
``public` `Iterator<E> iterator() {
``// 实际上返回的是HashMap的“key集合的迭代器”
``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);
``}
``// 将元素(e)添加到HashSet中
``public` `boolean` `add(E e) {
``return` `map.put(e, PRESENT)==``null``;
``}
``// 删除HashSet中的元素(o)
``public` `boolean` `remove(Object o) {
``return` `map.remove(o)==PRESENT;
``}
``public` `void` `clear() {
``map.clear();
``}
``// 克隆一个HashSet,并返回Object对象
``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();
``}
``}
``// java.io.Serializable的写入函数
``// 将HashSet的“总的容量,加载因子,实际容量,所有的元素”都写入到输出流中
``private` `void` `writeObject(java.io.ObjectOutputStream s)
``throws` `java.io.IOException {
``// Write out any hidden serialization magic
``s.defaultWriteObject();
``// Write out HashMap capacity and load factor
``s.writeInt(map.capacity());
``s.writeFloat(map.loadFactor());
``// Write out size
``s.writeInt(map.size());
``// Write out all elements in the proper order.
``for` `(Iterator i=map.keySet().iterator(); i.hasNext(); )
``s.writeObject(i.next());
``}
``// java.io.Serializable的读取函数
``// 将HashSet的“总的容量,加载因子,实际容量,所有的元素”依次读出
``private` `void` `readObject(java.io.ObjectInputStream s)
``throws` `java.io.IOException, ClassNotFoundException {
``// Read in any hidden serialization magic
``s.defaultReadObject();
``// Read in HashMap capacity and load factor and create backing HashMap
``int` `capacity = s.readInt();
``float` `loadFactor = s.readFloat();
``map = (((HashSet)``this``) ``instanceof` `LinkedHashSet ?
``new` `LinkedHashMap<E,Object>(capacity, loadFactor) :
``new` `HashMap<E,Object>(capacity, loadFactor));
``// Read in size
``int` `size = s.readInt();
``// Read in all elements in the proper order.
``for` `(``int` `i=; i<size; i++) {
``E e = (E) s.readObject();
``map.put(e, PRESENT);
``}
``}
}
说明: HashSet的代码实际上非常简单,通过上面的注释应该很能够看懂。它是通过HashMap实现的,若对HashSet的理解有困难,建议先学习以下HashMap;学完HashMap之后,在学习HashSet就非常容易了。
3.1元素添加过程:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置,判断
数组此位置上是否已经元素:
如果此位置上没其他元素,则元素a添加成功。 —>情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素,则比较元素a与元素b的hash值:
如果hash值不相同,则元素a添加成功。—>情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。—>情况3
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下
第4部分 HashSet遍历方式
4.1 通过Iterator遍历HashSet
第一步:根据iterator()获取HashSet的迭代器。
第二步:遍历迭代器获取各个元素。
// 假设set是HashSet对象
for``(Iterator iterator = set.iterator();
``iterator.hasNext(); ) {
``iterator.next();
}
4.2 通过for-each遍历HashSet
第一步:根据toArray()获取HashSet的元素集合对应的数组。
第二步:遍历数组,获取各个元素。
// 假设set是HashSet对象,并且set中元素是String类型
String[] arr = (String[])set.toArray(``new` `String[``0``]);
for` `(String str:arr)
``System.out.printf(``"for each : %s\n"``, str);
HashSet的遍历测试程序如下:
import` `java.util.Random;
import` `java.util.Iterator;
import` `java.util.HashSet;
/*
* @desc 介绍HashSet遍历方法
*
*
*/
public class HashSetIteratorTest {
``public static void main(String[] args) {
``// 新建HashSet
``HashSet set = new HashSet();
``// 添加元素 到HashSet中
``for (int i=; i<; i++)
``set.add(""+i);
``// 通过Iterator遍历HashSet
``iteratorHashSet(set) ;
``// 通过for-each遍历HashSet
``foreachHashSet(set);
``}
``/*
``* 通过Iterator遍历HashSet。推荐方式
``*/
``private static void iteratorHashSet(HashSet set) {
``for(Iterator iterator = set.iterator();
``iterator.hasNext(); ) {
``System.out.printf("iterator : %s\n", iterator.next());
``}
``}
``/*
``* 通过for-each遍历HashSet。不推荐!此方法需要先将Set转换为数组
``*/
``private static void foreachHashSet(HashSet set) {
``String[] arr = (String[])set.toArray(new String[]);
``for(String str:arr)
``System.out.printf("for each : %s\n", str);
``}
}
运行结果:
iterator : 3
iterator : 2
iterator : 1
iterator : 0
iterator : 4
for` `each : 3
for` `each : 2
for` `each : 1
for` `each : 0
for` `each : 4
第5部分 HashSet示例
下面我们通过实例学习如何使用HashSet
import` `java.util.Iterator;
``import` `java.util.HashSet;
``/*
``* @desc HashSet常用API的使用。
``*
``* @author skywang
``*/
``public class HashSetTest {
``public static void main(String[] args) {
``// HashSet常用API
``testHashSetAPIs() ;
``}
``/*
``* HashSet除了iterator()和add()之外的其它常用API
``*/
``private` `static` `void` `testHashSetAPIs() {
``// 新建HashSet
``HashSet set = ``new` `HashSet();
``// 将元素添加到Set中
``set.add(``"a"``);
``set.add(``"b"``);
``set.add(``"c"``);
``set.add(``"d"``);
``set.add(``"e"``);
``// 打印HashSet的实际大小
``System.out.printf(``"size : %d\n"``, set.size());
``// 判断HashSet是否包含某个值
``System.out.printf(``"HashSet contains a :%s\n"``, set.contains(``"a"``));
``System.out.printf(``"HashSet contains g :%s\n"``, set.contains(``"g"``));
``// 删除HashSet中的“e”
``set.remove(``"e"``);
``// 将Set转换为数组
``String[] arr = (String[])set.toArray(``new` `String[]);
``for` `(String str:arr)
``System.out.printf(``"for each : %s\n"``, str);
``// 新建一个包含b、c、f的HashSet
``HashSet otherset = ``new` `HashSet();
``otherset.add(``"b"``);
``otherset.add(``"c"``);
``otherset.add(``"f"``);
``// 克隆一个removeset,内容和set一模一样
``HashSet removeset = (HashSet)set.clone();
``// 删除“removeset中,属于otherSet的元素”
``removeset.removeAll(otherset);
``// 打印removeset
``System.out.printf(``"removeset : %s\n"``, removeset);
``// 克隆一个retainset,内容和set一模一样
``HashSet retainset = (HashSet)set.clone();
``// 保留“retainset中,属于otherSet的元素”
``retainset.retainAll(otherset);
``// 打印retainset
``System.out.printf(``"retainset : %s\n"``, retainset);
``// 遍历HashSet
``for``(Iterator iterator = set.iterator();
``iterator.hasNext(); )
``System.out.printf(``"iterator : %s\n"``, iterator.next());
``// 清空HashSet
``set.clear();
``// 输出HashSet是否为空
``System.out.printf(``"%s\n"``, set.isEmpty()?``"set is empty"``:``"set is not empty"``);
``}
``}
实现Set接口,元素是无序唯一的
底层是哈希表,哈希表实际上是一个数组。
在jdk1.8之前,数组里面的元素为链表;
在jdk1.8时,但是如果链表元素个数 > 8 ,此时,链表要变成红黑二叉树(红黑二叉树查询速度快)
4. LinkedHashSet
里面的元素有序且唯一
链表保证元素序;由哈希表保证元素唯一
package com.xms.collection.day02;
import java.util.Iterator;
import java.util.LinkedHashSet;
/**
* @author hsl
* @create 2022-07-21 下午 5:26
*/
public class LinkedHashSetDemo {
public static void main(String[] args) {
LinkedHashSet<String> ls = new LinkedHashSet<>();
//分析add()源码发现HashSet元素唯一性是通过我们的HashCode(),equals()来保证
ls.add("hello");
ls.add("world");
ls.add("java");
ls.add("javase");
ls.add("javaee");
Iterator<String> it = ls.iterator();
while (it.hasNext()) {
String next = it.next();
System.out.println(next);
}
}
}
5. 存储对象所在类的要求:
HashSet/LinkedHashSet:
要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
6. TreeSet的使用
能够对元素照某个规则进行排序,且元素唯一
排序方式:
1)自然排序:(元素具备比较性)
让元素所在的类实现自然排序接口Comparable
2比较器排序:(集合具备比较性)
让集合的构造方法接收一个比较器接口(Comparator的实现类对象
使用说明:
1)向TreeSet中添加的数据,要求是相同类的对象。
2)两种排序方式:自然排序(实现Comparable接口 和 定制排序(Comparator)
package com.xms.collection.day02;
import java.util.Objects;
/**
* @author hsl
* @create 2022-07-21 下午 5:28
*/
public class Dog{
String name;
int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Dog cat = (Dog) o;
return age == cat.age && Objects.equals(name, cat.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
package com.xms.collection.day02;
import java.util.Objects;
/**
* @author hsl
* @create 2022-07-21 下午 5:28
*/
public class Cat implements Comparable<Cat>{
String name;
int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Cat cat) {
//先按照年龄由小到大进行排序
int num1 = this.age - cat.age;
//如果年龄相等,安装姓名的字典顺序进行排序
int num2 = num1 == 0?this.name.compareTo(cat.name):num1;
return num2;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Cat cat = (Cat) o;
return age == cat.age && Objects.equals(name, cat.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
package com.xms.collection.day02;
import org.junit.Test;
import java.util.Comparator;
import java.util.HashSet;
import java.util.TreeSet;
/**
* @author hsl
* @create 2022-07-21 下午 5:30
*/
public class TreeSetDemo {
@Test
public void demo01(){
HashSet<Cat> ts = new HashSet<>();
ts.add(new Cat("xiaohua",1));
ts.add(new Cat("xiaohei",1));
ts.add(new Cat("xiaobai",2));
ts.add(new Cat("xiaohuang",3));
ts.add(new Cat("dabai",3));
ts.add(new Cat("xiaohua",1));
for (Cat cat : ts) {
System.out.println(cat);
}
}
@Test
public void demo02(){
TreeSet<Dog> ts = new TreeSet<>(new Comparator<Dog>() {
@Override
public int compare(Dog dog1, Dog dog2) {
//先按照年龄由小到大进行排序
int num1 = dog1.age - dog2.age;
//如果年龄相等,安装姓名的字典顺序进行排序
int num2 = num1 == 0?dog1.name.compareTo(dog2.name):num1;
return num2;
}
});
ts.add(new Dog("xiaohua",1));
ts.add(new Dog("xiaohei",1));
ts.add(new Dog("xiaobai",2));
ts.add(new Dog("xiaohuang",3));
ts.add(new Dog("dabai",3));
ts.add(new Dog("xiaohua",1));
for (Dog dog : ts) {
System.out.println(dog);
}
}
@Test
public void demo03(){
TreeSet<Dog> ts = new TreeSet<>((dog1, dog2) -> {
//先按照年龄由小到大进行排序
int num1 = dog1.age - dog2.age,
//如果年龄相等,安装姓名的字典顺序进行排序
num2 = num1 == 0?dog1.name.compareTo(dog2.name):num1;
return num2;
});
ts.add(new Dog("xiaohua",1));
ts.add(new Dog("xiaohei",1));
ts.add(new Dog("xiaobai",2));
ts.add(new Dog("xiaohuang",3));
ts.add(new Dog("dabai",3));
ts.add(new Dog("xiaohua",1));
for (Dog dog : ts) {
System.out.println(dog);
}
}
}
SortedSet
SortedSet 是一个接口,它继承自 Set。比 Set 多的功能是,它保证元素按照大小排序。因为这个特点,所有它有 first(), last() 这样的成员函数。
需要区分的是,LinkedHashSet 保证元素的顺序,是保证插入顺序与遍历顺序一只。而SortedSet 保证的顺序是指遍历顺序始终是按照从小到大排序。当然了,也可以自定义比较函数,让它按照从大到小排序。
Queue
队列Queue(接口)
队列需要满足先进先出的原则
队列的遍历是一次性的,想要获取队列中的某个元素,需要先将队列中该
元素之前的所元素获取到,才能访问该元素
成员方法:
boolean offer(E e):向队列末尾添加元素(入队)
E poll():获取并删除队列的首元素(出队)
E peek():获取队列的首元素
双端队列Deque
队列两端都可以进出
当使用双端队列存储元素,如果只从一侧操作,就形成了一种存储模式(先进后出)
package com.xms.collection.day03;
import java.util.LinkedList;
import java.util.Queue;
/**
* @author hsl
* @create 2022-07-22 下午 4:18
*/
public class QueueDemo {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
//入队(添加元素)
queue.offer("java");
queue.offer("world");
queue.offer("hello");
System.out.println("首元素"+queue.peek());
//出队
while (!queue.isEmpty()){
System.out.println(queue.poll());
}
System.out.println(queue);
}
}
Deque
可以实现和栈相似的功能,先进后出
package com.xms.collection.day03.generic;
import java.util.Deque;
import java.util.LinkedList;
/**
* @author hsl
* @create 2022-07-22 下午 4:25
*/
public class DequeDemo {
public static void main(String[] args) {
Deque<Object> deque = new LinkedList<>();
//压入元素
deque.push("hello");
deque.push("world");
deque.push("java");
//弹出元素
while (deque.size() > 0){
System.out.println(deque.pop());
}
System.out.println(deque);
}
}
Collections
Collections工具类
1.作用:操作Collection和Map的工具类
2.常用方法:
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
binarySearch(List list,T key);二分法查找,查找的是索引
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所旧值
说明:ArrayList和HashMap都是线程不安全的,如果程序要求线程安全,我们可以将ArrayList、HashMap转换为线程的。
使用synchronizedList(List list) 和 synchronizedMap(Map map)
package com.xms.collection.day03;
import java.util.*;
/**
* @author hsl
* @create 2022-07-22 下午 4:30
*/
public class CollectionsDemo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Tom", "Tony", "Jerry");
//1)反转
Collections.reverse(list);//[Jerry, Tony, Tom]
System.out.println(list);
//2)打乱元素顺序
//Collections.shuffle(list);
System.out.println("打乱元素顺序"+list);
//3)自然排序
Collections.sort(list);
System.out.println(list);
//4)使用比较器进行排序
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int num = o1.compareTo(o2);
return num;
}
});
System.out.println(list);
//Collections.sort(list,(n1,n2) -> n2.compareTo(n1));
//5)交换元素
Collections.swap(list,0,1);
System.out.println(list);
//6)二分法查找
Collections.sort(list);
System.out.println(Collections.binarySearch(list,"Tom"));
//7)获取最大值
String max = Collections.max(list);
System.out.println(max);
//8)通过指定比较器来获取“最大”元素
String max1 = Collections.max(list, (n1, n2) -> n2.compareTo(n1));
System.out.println(max1);
//9)获取元素出现的次数
System.out.println(Collections.frequency(list,"Tom"));
System.out.println(Collections.frequency(list,"NiHao"));
//10)复制集合(dest集合长度>=src长度,不然会发生IndexOutOfBoundsException)
List<String> dest = new ArrayList<>();
dest.add("1");
dest.add("1");
dest.add("1");
dest.add("1");
Collections.copy(dest,list);
System.out.println(dest);
//1)替换
List<String> list2 = Arrays.asList("a", "b", "c", "d", "e", "b");
Collections.replaceAll(list2,"a","A");
System.out.println(list2);
}
}
3.面试题:
面试题:Collection 和 Collections的区别?
Map
双列集合框架:Map
map集合的作用
和查字典类似,通过key找到对应的value,通过页数找到对应的信息。用学生类来说,key相当于学号,value对应name,age,sex等信息。用这种对应关系方便查找。
Map和Set的关系
可以说关系是很密切了,虽然Map中存放的时键值对,Set中存放的是单个对象,但如果把value看做key的附庸,key在哪里,value就在哪里,这样就可以像对待Set一样来对待Map了。事实上,Map提供了一个Entry内部类来封装key-value对,再计算Entry存储时则只考虑Entry封装的key。
如果把Map集合里的所有value放在一起来看,它们又类似于一个List,元素可以重复,每个元素可以根据索引来找,只是Map中的索引不再是整数值,而是以另一个对象作为索引。
1.常用实现类结构
|----Map:双列数据,存储key-value对的数据 —类似于高中的函数:y = f(x)
|----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
|----LinkedHashMap:保证在遍历map元素时,可以照添加的顺序实现遍历。
原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行效率高于HashMap。
|----TreeMap:保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序,底层使用红黑树
|----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
|----Properties:常用来处理配置文件。key和value都是String类型
HashMap的底层:
数组+链表 (jdk7及之前)
数组+链表+红黑树 (jdk 8)
[面试题]
-
- HashMap的底层实现原理?
-
- HashMap 和 Hashtable的异同?
-
- CurrentHashMap 与 Hashtable的异同?
2.存储结构的理解:
Map中的key:无序的、不可重复的(Set) —> key所在的类要重写equals()和hashCode() (以HashMap为例)
Map中的value:无序的、可重复的(Collection) —>value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的(Set)
图示:
3.常用方法
-
添加功能:
V put(K key,V value):添加元素(也可以实现修改功能)
-
删除功能:
void clear():清空所键值对元素
V remove(Object key):根据键移除对应的值,并把值返回
-
判断功能:
containsKey(Object key):是否包含指定的键
containsValue(Object value):是否包含指定的值
isEmpty():是否为空
-
遍历功能:
Set<Map.Entry<K,V>> entrySet():获取键值对
V get(Object key):根据键获取值
Collection values():获取所值的集合
-
获取功能:
Set keySet():获取所键的集合
int size():获取集合元素的个数
-
void clear():删除该Map对象中所有键值对;
-
boolean containsKey(Object key):查询Map中是否包含指定的key值;
-
boolean containsValue(Object value):查询Map中是否包含一个或多个value;
-
Set entrySet():返回map中包含的键值对所组成的Set集合,每个集合都是Map.Entry对象。
-
Object get():返回指定key对应的value,如果不包含key则返回null;
-
boolean isEmpty():查询该Map是否为空;
-
Set keySet():返回Map中所有key组成的集合;
-
Collection values():返回该Map里所有value组成的Collection。
-
Object put(Object key,Object value):添加一个键值对,如果集合中的key重复,则覆盖原来的键值对;
-
void putAll(Map m):将Map中的键值对复制到本Map中;
-
Object remove(Object key):删除指定的key对应的键值对,并返回被删除键值对的value,如果不存在,则返回null;
-
boolean remove(Object key,Object value):删除指定键值对,删除成功返回true;
-
int size():返回该Map里的键值对个数;
-
HashMap<String, Integer> hm = new HashMap<>(); //放入元素 hm.put("Harry",23); hm.put("Jenny",24); hm.put("XiaoLi",20); System.out.println(hm);//{XiaoLi=20, Harry=23, Jenny=24} System.out.println(hm.keySet());//[XiaoLi, Harry, Jenny] System.out.println(hm.values());//[20, 23, 24] Set<Map.Entry<String, Integer>> entries = hm.entrySet(); for (Map.Entry<String, Integer> entry : entries) { System.out.println(entry.getKey()); System.out.println(entry.getValue()); }
java8为Map新增的方法
Object compute(Object key,BiFurcation remappingFunction):使用remappingFunction根据原键值对计算一个新的value,只要新value不为null,就覆盖原value;如果新value为null则删除该键值对,如果同时为null则不改变任何键值对,直接返回null。
-
HashMap<String, Integer> hm = new HashMap<>(); //放入元素 hm.put("Harry",23); hm.put("Jenny",24); hm.put("XiaoLi",20); System.out.println(hm);//{XiaoLi=20, Harry=23, Jenny=24} hm.compute("Harry",(key,value)->(Integer)value+10); System.out.println(hm);//{XiaoLi=20, Harry=33, Jenny=24}
-
Object computeIfAbsent(Object key,Furcation mappingFunction):如果传给该方法的key参数在Map中对应的value为null,则使用mappingFunction根据key计算一个新的结果,如果计算结果不为null,则计算结果覆盖原有的value,如果原Map原来不包含该Key,那么该方法可能会添加一组键值对。
-
HashMap<String, Integer> hm = new HashMap<>(); //放入元素 hm.put("Harry",23); hm.put("Jenny",24); hm.put("XiaoLi",20); hm.put("LiMing",null); //指定key为null则计算结果作为value hm.computeIfAbsent("LiMing",(key)->10); System.out.println(hm);//{XiaoLi=20, Harry=23, Jenny=24, LiMing=10} //如果指定key本来不存在,则添加对应键值对 hm.computeIfAbsent("XiaoHong",(key)->34); System.out.println(hm);//{XiaoLi=20, Harry=23, XiaoHong=34, Jenny=24, LiMing=10}
-
Object compute(Object key,BiFurcation remappingFunction):如果传给该方法的key参数在Map中对应的value不为null,则通过计算得到新的键值对,如果计算结果不为null,则覆盖原来的value,如果计算结果为null,则删除原键值对。
hm.computeIfPresent("Harry",(key,value)->(Integer)value*(Integer)value);
System.out.println(hm);
//{XiaoLi=20, Harry=529, Jenny=24, LiMing=null}
void forEach(BiConsumer action):遍历键值对。
HashMap<String, Integer> hm = new HashMap<>();
//放入元素
hm.put("Harry",23);
hm.put("Jenny",24);
hm.put("XiaoLi",20);
hm.put("LiMing",null);
hm.forEach((key,value)-> System.out.println(key+"->"+value));
/*XiaoLi->20
Harry->23
Jenny->24
LiMing->null*
- Object getOrDefault(Object key,V defaultValue):获取指定key对应的value,如果key不存在则返回defaultValue;
System.out.println(hm.getOrDefault("Harry",33));//23
- Object merge(Object key,Object value,BiFurcation remappingFunction):该方法会先根据key参数获取该Map中对应的value。如果获取的value为null,则直接用传入的value覆盖原有的value,如果获取的value不为null,则使用remappingFunction函数根据原value、新value计算一个新的结果,并用得到的结果去覆盖原有的value。
HashMap<String, Integer> hm = new HashMap<>();
//放入元素
hm.put("Harry",23);
hm.put("Jenny",24);
hm.put("XiaoLi",20);
hm.put("LiMing",null);
hm.merge("LiMing",24,(key,value)->value+10);
System.out.println(hm);//{XiaoLi=20, Harry=23, Jenny=24, LiMing=24}
hm.merge("Harry",24,(key,value)->value+10);
System.out.println(hm);//{XiaoLi=20, Harry=34, Jenny=24, LiMing=24}
- Object putIfAbsent(Object key,Object value):该方法会自动检测指定key对应的value是否为null,如果为null,则用新value去替换原来的null值。
- Object replace(Object key,Object value):将key对应的value替换成新的value,如果key不存在则返回null。
- boolean replace(K key,V oldValue,V newValue):将指定键值对的value替换成新的value,如果未找到则返回false;
- replaceAll(BiFunction Function):使用BiFunction对原键值对执行计算,并将结果作为该键值对的value值。
java8改进的HashMap和Hashtable实现类
HashMap和Hashtable的关系完全类似于ArrayList和Vector的关系。
区别
Hashtable是线性安全的,HashMap是线性不安全的,所以后者效率更高。
Hashtable不允许使用null作为key和value,否则会引发异常,而HashMap可以;
和HashSet的关系
与HashSet集合不能保证元素顺序一样,HashMap和Hashtable也不能保证键值对的顺序。他们判断两个key相等的标准也是:两个key通过equals方法比较返回true,两个key的hashCode值也相等。而判断value值相等的标准:只要两个对象通过equals方法比较返回true即可。
不能修改集合中的key,否则程序再也无法准确访问到Map中被修改过的key。
LinkedHashMap实现类
和HashSet中的LinkedHashSet一样,HashMap也有一个LinkedHashMap子类,使用双向链表来维护键值对的次序,迭代顺序和插入顺序保持一致。
使用Properties读写属性文件
Properties类是Hashtable类的子类,该对象在处理属性文件时特别方便。Properties类可以把Map对象和属性文件关联起来,从而把Map对象的键值对写入属性文件中,也可以把属性文件中的“属性名=属性值”加载到Map对象中。
4.内存结构说明:(难点)
4.1 HashMap在jdk7中实现原理:
HashMap map = new HashMap():
在实例化以后,底层创建了长度是16的一维数组Entry[] table。
…可能已经执行过多次put…
map.put(key1,value1):
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在 类的equals(key2)方法,比较:
如果equals()返回false:此时key1-value1添加成功。----情况3
如果equals()返回true:使用value1替换value2。
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。
4.2 HashMap在jdk8中相较于jdk7在底层实现方面的不同:
1)new HashMap():底层没创建一个长度为16的数组
2)jdk 8底层的数组是:Node[],而非Entry[]
3)首次调用put()方法时,底层创建长度为16的数组
4)jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树。
4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
4.3 HashMap底层典型属性的属性的说明:
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
4.4 LinkedHashMap的底层实现原理(了解)
LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap.
区别就在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node.
LinkedHashMap实现类
和HashSet中的LinkedHashSet一样,HashMap也有一个LinkedHashMap子类,使用双向链表来维护键值对的次序,迭代顺序和插入顺序保持一致。
4.5使用Properties读写属性文件
Properties类是Hashtable类的子类,该对象在处理属性文件时特别方便。Properties类可以把Map对象和属性文件关联起来,从而把Map对象的键值对写入属性文件中,也可以把属性文件中的“属性名=属性值”加载到Map对象中。
Properties相当于一个key、value都是String类型的Map
String getProperty(String key):获取Properties中指定的属性名对应的属性值。
String getProperty(String key,String defaultValue):和前一个方法相同,只不过如果key不存在,则该方法指定默认值。
Object setProperty(String key,String value):设置属性值,类似于Hashtable的put方法;
void load(InputStream inStream):从属性文件中加载键值对,把加载出来的键值对追加到Properties里。
void store(OutputStream out,String comments):将Properties中的键值对输出到指定的属性文件中。
public static void main(String[] args) throws Exception {
Properties props = new Properties();
//向Properties中添加属性
props.setProperty("username","yeeku");
props.setProperty("password","123456");
//保存到文件中
props.store(new FileOutputStream("a.ini"),"comment line");
//新建一个Properties对象
Properties props2 = new Properties();
props2.setProperty("gender","male");
//将文件中的键值对追加到对象中
props2.load(new FileInputStream("a.ini"));
System.out.println(props2);//{password=123456, gender=male, username=yeeku}
}
文件内容
还可以把键值对以XML文件的形式保存起来,同样可以从文件中加载出来,用法与上述案例相同。
5.SortedMap接口和TreeMap实现类
TreeMap实现类
元素可以根据键进行排序,元素具唯一性
排序方式:
1)自然排序
让元素所在的类实现自然排序接口Comparable
2)比较器排序
让集合的构造方法接收一个比较器接口(Comparator的实现类对象)
SortedMap接口和TreeMap实现类
正如Set接口派生出SortedSet子接口,Sorted接口有一个TreeSet实现类一样,Map接口也派生出一个SortedMap子接口,SortedMap接口也有一个TreeMap实现类。
TreeMap就是一个红黑树数据结构,每个键值对作为红黑树的一个节点。存储键值对时根据key对节点进行排序。可以保证所有键值对处于有序状态。
和TreeSet一样,TreeMap也有自然排序和定制排序两种排序方式。
与TreeSet类似的是,TreeMap中提供了一系列根据key顺序访问键值对的方法:
public static void main(String[] args) {
TreeMap<String, Integer> stuTreeMap = new TreeMap<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int num=o1.compareTo(o2);
return num;
}
});
stuTreeMap.put("LiMing",14);
stuTreeMap.put("LiMing",24);
stuTreeMap.put("Jenny",16);
stuTreeMap.put("Denny",24);
System.out.println(stuTreeMap);//{Denny=24, Jenny=16, LiMing=24}
System.out.println(stuTreeMap.firstEntry());//Denny=24
}
- Map.Entry firstEntry():返回该Map中最小的key所对应的键值对,如果Map为空则返回null;
System.out.println(stuTreeMap.firstEntry());//Denny=24
-
Object firstKey():返回该Map中的最小key值,如果Map为空则返回null;
-
Object lastKey():返回该Map中的最大key值,如果Map为空则返回null;
-
Map.Entry higherEntry(Object key):返回该Map中位于key后一位的键值对;
-
Object higherKey(Object key):返回该Map中位于key后一位的key;
-
Map.Entry lowerEntry(Object key):返回该Map中位于key前一位的键值对;
-
Object lowererKey(Object key):返回该Map中位于key前一位的key;
-
NavigableMap tailMap(Object fromkey,boolean fromInclusive,Object toKey,boolean toInclusive):返回该Map的子Map,其key范围从fromkey(是否包含取决于第二个参数)到toKey(是否包含取决于第四个参数)。
-
WeakHashMap实现类
WeakHashMap与HashMap的用法基本相似,区别在于HashMap的key保留了对实际对象的强引用,这意味着只要该对象不销毁,该HashMap的所有key所引用的对象就不会被垃圾回收,HashMap也不会自动删除这些key所对应的键值对,但WeakHashMap的key只保留了对实际对象的弱引用,这意味着如果WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,WeakHashMap也可能自动删除这些key对应的键值对。 -
IdentityHashMap实现类
这个类的实现机制与HashMap基本相似,但它在处理两个key相等时比较独特:在IdentityHashMap中,当且仅当两个key严格相等(key1==key2)时,IdentityHashMap才认为两个key相等,对于普通的HashMap而言,只要key1和key2通过equals方法比较返回true,且它们的hashcode相等即可。
IdentityHashMap map = new IdentityHashMap<>();
map.put(new String("语文"), 89);
map.put(new String("语文"), 90);
map.put("java",70);
map.put("java",71);
System.out.println(map);//{java=71, 语文=90, 语文=89}
前面是两个对象虽然通过equal方法比较是相等的,但是通过==
比较不相等,后面两个字符串在常量池中同一位置,所以使用==
判断相等。
6.Properties
表示了一个持久的属性集,是Hashtable的子类,可保存在流中或从流中加载。
属性列表中每个键及其对应值都是一个字符串。
文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔。
成员方法
Object setProperty(String key,String value):添加元素
String getProperty(String key):获取值元素
Set stringPropertyNames():获取所的键的集合
Enumeration<?> propertyNames() 返回属性列表中所键的枚举
这里的集合必须是Properties集合:
public void load(Reader reader):把文件中的数据读取到集合中
public void store(Writer writer,String comments):把集合中的数据存储到文件
使用Properties读取配置文件
//Properties:常用来处理配置文件。key和value都是String类型
public static void main(String[] args) {
FileInputStream fis = null;
try {
Properties pros = new Properties();
fis = new FileInputStream("jdbc.properties");
pros.load(fis);//加载流对应的文件
String name = pros.getProperty("name");
String password = pros.getProperty("password");
System.out.println("name = " + name + ", password = " + password);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
7.内部类Entry
Map中包括一个内部类Entry,该类封装一个键值对,常用方法:
Object getKey():返回该Entry里包含的key值;
Object getvalue():返回该Entry里包含的value值;
Object setValue(V value):设置该Entry里包含的value值,并设置新的value值。
HashMap<String, Integer> hm = new HashMap<>();
//放入元素
hm.put("Harry",23);
hm.put("Jenny",24);
hm.put("XiaoLi",20);
System.out.println(hm);//{XiaoLi=20, Harry=23, Jenny=24}
System.out.println(hm.keySet());//[XiaoLi, Harry, Jenny]
System.out.println(hm.values());//[20, 23, 24]
Set<Map.Entry<String, Integer>> entries = hm.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}