尚硅谷JavaSE笔记合集
文章名 | 链接 |
---|---|
【JavaSE】异常 | 文章地址 |
【JavaSE】常用类:String、LocalDateTime… | 文章地址 |
【JavaSE】枚举 | 文章地址 |
【JavaSE】注解 | 文章地址 |
【JavaSE】集合框架 | 文章地址 | HashMap源码解析 | List相关实现类源码解析 |
【JavaSE】泛型 | 文章地址 |
【JavaSE】IO流 | 文章地址 | 字符编码详解 |
【JavaSE】网络编程,BIO需求演进 | 文章地址 |
【JavaSE】反射 | 文章地址 |
【JavaSE】jdk8新特性 | 文章地址 |
一、集合概述
1.1 集合与数组
/**
* 概述:
* - 集合、数组都是对多个数据进行存储(内存层面的存储)操作的结构,简称Java容器。
*
* 数组的特点:
* 》长度确定:初始化时,指定数组长度
* 》数据类型确定:初始化时,指定数据类型。即操作时数据类型不可变
* 》数据有序、可重复
* 比如:String[] arr;int[] str;
* 数组的缺点:
* 》长度不可修改:初始化后,无法根据需要扩缩容
* 》提供的方法非常有限:
* - 添加、删除、插入数据:非常不便,同时效率不高。
* - 获取实际存储的元素个数:数组没有现成的属性或方法可用
* 》数据有序、可重复:无法满足无序、不可重复的需求
*
*/
1.2 集合框架
/**
*
* Collection接口:单列集合,用来存储一个一个的对象
* - List接口:存储有序的、可重复的数据。 -->“动态”数组
* ArrayList、LinkedList、Vector
*
* - Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
* HashSet、LinkedHashSet、TreeSet
*
* Map接口:双列集合,用来存储key - value数据。元素无序、不可重复 -->高中函数:y = f(x)
* HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
*
*/
二、Collection接口
2.1 概述
- 是List、Set 和Queue 的父接口
- JDK只是提供此接口更具体的子接口实现:Set、List
- JDK 5.0 增加了泛型以后,Java 集合可以记住容器中对象的数据类型。
2.2 常用方法
- 要求集合元素所在类要重写equals()
- 数组转换成集合List:Arrays.asList(T…a)
- 添加
- add(Object):添加元素
- addAll(Collection):添加Collection中所有元素
- 删除
- remove(Object):删除元素。只会删除找到的第一个元素
- removeAll(Collection):删除存在于Collection中的所有元素
- clear():清空所有元素
- 获取交集
- retainAll(Collection):取两个集合中相同的元素,存放于调用者中。即取交集
- 判断
- isEmpty():集合是否为空
- contains(Object):是否包含某个元素。调用Object对象所在类的equals()
- containsAll(Collection):是否包含Collection中所有元素。拿两个集合的元素的equals方法挨个比较。
- equals(Object):判断两集合是否相同
- 其他
- toArray():转换成数组
/**
* Collection常用方法
* - 要求obj所在类要重写equals().
* - 数组转换成集合List:Arrays.asList(T...a)
* 1.add(Object):添加元素
* 2.addAll(Collection):添加Collection中所有元素
* 3.clear():清空元素
* 4.isEmpty():集合是否为空
* 5.contains(Object):是否包含某个元素。调用Object对象所在类的equals()
* 6.containsAll(Collection):是否包含Collection中所有元素。拿两个集合的元素的equals方法挨个比较。
* 7.remove(Object):删除元素。只会删除找到的第一个元素
* 8.removeAll(Collection):删除Collection中存在的元素。即取差集
* 9.retainAll(Collection):取两个集合中相同的元素,存放于调用者中。即取交集
* 10.equals(Object):判断两集合是否相同
* 11.toArray():转换成数组
* 12.hashCode():获取哈希值
* 13.iterator():获取迭代器,用于当前集合的遍历
*/
public class CollectionTest {
@Test
public void test1(){
Collection coll1=new ArrayList();
Collection coll2=new ArrayList();
//1.add(Object):添加元素
coll1.add(1);
coll1.add(2);
coll1.add("3");
coll2.add("3");
coll2.add(new Date());
//2.addAll(Collection):添加集合的所有元素
coll1.addAll(coll2);
System.out.println(coll1); //[1, 2, 3, 3, Mon Sep 26 14:14:49 CST 2022]
//3.clear():清空元素
coll1.clear();
//4.isEmpty():集合是否为空
System.out.println("集合是否为空:"+coll1.isEmpty()); //true
System.out.println(coll1); //[]
}
@Test
public void test2(){
Collection coll1=new ArrayList();
Collection coll2=new ArrayList();
coll1.add(1);
coll1.add(2);
coll1.add("3");
coll2.add(2);
coll2.add("3");
//5.contains(Object):是否包含某个元素
System.out.println("是否包含元素1:"+coll1.contains(1)); //是否包含元素1:true
//6.containsAll(Collection):是否包含集合中所有元素
System.out.println("是否包含集合2中所有元素:"+coll1.containsAll(coll2)); //是否包含集合2中所有元素:true
//7.remove(Object):删除元素
coll1.remove(1);
System.out.println(coll1); //[2, 3]
//8.removeAll(Collection):删除Collection中存在的元素。即取差集
coll1.removeAll(coll2);
System.out.println(coll1);//[]
}
@Test
public void test3(){
Collection coll1=new ArrayList();
Collection coll2=new ArrayList();
coll1.add(1);
coll1.add(2);
coll1.add("3");
coll2.add(2);
coll2.add("3");
//9.retainAll(Collection):取两个集合中相同的元素,存放于调用者中。即取交集
coll1.retainAll(coll2);
System.out.println(coll1); //[2, 3]
//10.equals(Object):判断两集合是否相同
System.out.println("两个集合是否相同:"+coll1.equals(coll2)); //两个集合是否相同:true
//11.toArray():转换成数组返回
Object[] objects = coll1.toArray();
for (Object object : objects) {
System.out.println(object); //2 3
}
//12.hashCode():获取哈希值
int i = coll1.hashCode();
System.out.println(i); //1074
}
@Test
public void test4(){
Collection coll1=new ArrayList();
coll1.add(1);
coll1.add(2);
coll1.add("3");
//13.iterator():获取迭代器,用于当前集合的遍历
Iterator iterator = coll1.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next()); //1 2 3
}
}
}
三、Iterator接口
3.1 概述
- Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection 集合中的元素。
- GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。
- Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
- Iterator 仅用于遍历集合,Iterator本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。
- 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
3.2 执行原理
3.3 遍历集合
/**
* 集合元素的遍历:Iterator接口
* 1.内部的方法:hasNext()和 next()
* 2.集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
*/
public class IteratorTest {
//正确写法
@Test
public void test1(){
Collection coll=new ArrayList();
coll.add(1);
coll.add(2);
coll.add("3");
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
//错误写法
@Test
public void test2(){
Collection coll=new ArrayList();
coll.add(1);
coll.add(2);
coll.add("3");
Iterator iterator = coll.iterator();
//1.错误写法一
while (iterator.next()!=null){
System.out.println(iterator.next());
}
//2.错误写法二:每次调用iterator()都得到一个全新的迭代器对象,默认游标指向集合的第一个元素。
while (coll.iterator().hasNext()){
System.out.println(coll.iterator().next());
}
}
}
3.4 注意remove()
注意:IllegalStateException
- remove()前需要有next():将指针移到指定位置再进行删除。
- remove()不能连续调用
/**
* 集合元素的遍历:Iterator接口
* 3.内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()
* - remove()前需要有next():将指针移到指定位置再进行删除。
* - remove()不能连续调用
*/
public class IteratorTest {
//iterator.remove():删除元素“3”
@Test
public void test3(){
Collection coll=new ArrayList();
coll.add(1);
coll.add(2);
coll.add("3");
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
if(next.equals("3")){
iterator.remove();
}
}
System.out.println(coll); //[1, 2]
}
}
3.5 新特性:增强for
- Java 5.0 新特性:遍历 Collection、数组
- 底层调用Iterator完成操作
- 操作foreach出来的元素对 原来的Collection或数组 没有影响
/**
* jdk 5.0 新增了foreach循环,用于遍历集合、数组
*/
public class ForEachTest {
//集合遍历
@Test
public void test1(){
Collection coll=new ArrayList();
coll.add(1);
coll.add(2);
coll.add("3");
for (Object o : coll) {
System.out.println(o);
}
}
//数组遍历
@Test
public void test2(){
String[] strs=new String[]{"1","2","3"};
for (String str : strs) {
str=str+"h";
}
//注意:没有影响
for (String str : strs) {
System.out.println(str); //1 2 3
}
}
}
四、List接口
4.1 概述
/**
* 数组的缺点:
* 》长度不可修改:初始化后,无法根据需要扩缩容
* 》提供的方法非常有限:
* - 添加、删除、插入数据:非常不便,同时效率不高。
* - 获取实际存储的元素个数:数组没有现成的属性或方法可用
* 》数据有序、可重复:无法满足无序、不可重复的需求(使用Set解决)
*/
- 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组
- List集合类中元素有序、可重复,集合中的每个元素都有其对应的顺序索引。
- 元素对于实现类建议重写equals(),用于方法调用判断元素是否相等。
- list.contains(Object)
- Collections.frequency(Collection,Object)
4.2 常用实现类对比
/**
* 面试题:比较ArrayList、LinkedList、Vector三者的异同?建议说一说add的源码实现
*
* 相同点:都实现了List接口,存储有序的、可重复的数据
*
* 不同点:
* ArrayList:作为List的主要实现类,把ArrayList作为缺省选择
* - 线程不安全,效率高。
* - 作为List的主要实现类,ArrayList()懒汉式(jdk8)创建集合。默认容量为10
* - 底层使用数组存储
* - 扩容是原来的1.5倍
* Vector:
* - 线程安全,效率低总是比ArrayList低。尽量避免使用。
* - 作为古老的实现类,Vector()饿汉式创建集合。默认容量为10
* - 底层使用数组存储
* - 扩容是原来的2倍
* LinkedList:
* - 底层使用双向链表存储
* - 对于频繁的插入、删除操作,建议使用LinkedList,效率比ArrayList高。
*
*/
4.3 ArrayList 源码分析
- 建议开发中使用带参的构造器,减少扩容次数
- jdk7:ArrayList() --> this.elementData = new Object[initialCapacity]
//==============================以jdk8为例===========================
//1.实例化集合时:创建空数组 --> this.elementData={}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//2.添加元素时:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
//2.1 根据需要的最小容量判断是否需要扩容
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//2.1.1 作用:让空数组进入扩容逻辑
//如果当前数组为空,返回默认大小10。否则返回数组元素长度
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//2.1.2 作用:扩容
//如果10-0>0,即还没创建数组则进行数组扩容。如果需要的最小容量-数组大小>0,则进行数组扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//扩容为原来的1.5倍
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
//数组为空时进入:将新容量设置为10
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//容量大于可接受范围时进入
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//使用数组拷贝:将原数组拷贝到新容量的数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
//2.2 进行赋值
elementData[size++] = e;
4.4 LinkedList 源码分析
/**
* LinkedList的源码分析:
* 1.内部声明了Node类型的first和last属性,默认值为null。用于记录首末元素
*
* 2.Node定义:作为LinkedList中保存数据的基本结构。体现了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; //next变量记录下一个元素的位置
* this.prev = prev; //prev变量记录前一个元素的位置
* }
* }
*/
//1.实例化集合
public LinkedList() {
}
//2.添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
//2.1 链接到尾部
void linkLast(E e) {
//(1)保存尾节点
final Node<E> l = last;
//(2)创建新节点:传入尾节点
// 2.1 将新节点的prev指针指向尾节点
final Node<E> newNode = new Node<>(l, e, null);
//(3)将尾指针指向新节点
last = newNode;
//(4)如果一开始链表为空,则将头指针指向头节点
if (l == null)
first = newNode;
//(5)如果链表不为空,将尾节点的next指针指向新节点。新节点成为尾节点
else
l.next = newNode;
size++;
modCount++;
}
4.5 Vector 源码分析
//1.实例化集合:调用一个int型参数的构造器
public Vector() {
this(10);
}
//2.调用两个int型参数的构造器
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
//3.创建容量为10的Object数组
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
//4.添加元素
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
//4.1 判断是否需要扩容:如果需要的容量大于当前容量则扩容
private void ensureCapacityHelper(int minCapacity) {
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//扩容
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//扩大为原来的两倍
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
//4.2 赋值
elementData[elementCount++] = e;
4.6 常用方法
List 集合里添加了一些根据索引来操作集合元素的方法。
- 增删改:
- addAll(int,Collection),从int位置开始将Collection中的所有元素添加进来
- remove(int) 注意:参数为整型则认为是索引,移除并返回
- set(int, Object)
- 插入:add(int, Object)
- 获取
- get(int):获取索引位置的值
- size():获取元素个数
- indexOf(Object):获取元素首次出现的位置。如果不存在,返回-1
- lastIndexOf(Object):获取元素末次出现的位置。如果不存在,返回-1
- subList(int,int):返回[int,int)范围的子集合
/**
* 1.增:add(Object)
* 2.删:remove(int) / remove(Object) 注意:参数为整型则认为是索引,移除并返回
* 3.改:set(int, Object)
* 4.查:get(int)
* 5.插:add(int, Object)
* 6.长度:size()
* 7.添加:addAll(int,Collection),从int位置开始将Collection中的所有元素添加进来
* 8.获取头部元素位置:indexOf(Object),返回集合中首次出现的位置。如果不存在,返回-1
* 9.获取尾部元素位置:lastIndexOf(Object),返回集合中末次出现的位置。如果不存在,返回-1
* 10.获取子集合:subList(int,int),返回[int,int)的子集合
* 11.遍历:迭代器,增强for循环,普通for循环
*
*/
public class ListTest {
@Test
public void test1(){
ArrayList list=new ArrayList();
//1.增:add(Object)
list.add(1);
list.add(2);
list.add(4);
System.out.println(list); //[1, 2, 4]
//2.删:remove(int) / remove(Object)
list.remove(new Integer(4));
System.out.println(list); //[1, 2]
//3.改:set(int, Object)
list.set(1,3);
System.out.println(list);
//4.查:get(int)
Object o = list.get(0);
System.out.println(o); //1
//5.插:add(int, Object)
list.add(1,2);
System.out.println(list);
//6.长度:size()
System.out.println("长度为:"+list.size());
}
@Test
public void test2(){
ArrayList list1=new ArrayList();
ArrayList list2=new ArrayList();
list2.add(1);
list2.add(2);
list2.add(2);
//7.添加:addAll(int,Collection),从int位置开始将Collection中的所有元素添加进来
list1.addAll(0,list2);
System.out.println(list1); //[1, 2, 2]
//8.获取头部元素位置:indexOf(Object),返回Object在集合中首次出现的位置
System.out.println("头部元素2的位置:"+list1.indexOf(2)); //头部元素2的位置:1
//9.获取尾部元素位置:lastIndexOf(Object),返回Object在当前集合中末次出现的位置
System.out.println("尾部元素2的位置:"+list1.lastIndexOf(2)); //尾部元素2的位置:2
//10.获取子集合:subList(int,int),返回[int,int)的子集合
List list = list1.subList(0, 2);
System.out.println(list); //[1, 2]
}
//11.遍历
@Test
public void test3(){
ArrayList list=new ArrayList();
list.add(1);
list.add(2);
list.add(3);
//迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//增强for循环
for (Object o : list) {
System.out.println(o);
}
//普通for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
4.7 相关面试题
-
请问ArrayList/LinkedList/Vector的异同?谈谈你的理解?ArrayList底层是什么?扩容机制?Vector和ArrayList的最大区别?
相同点:都是Lsit的实现类,有序可重复 不同点: ArraysList VS Vector:ArraysList和Vector底层都是用数组存储的 - ArraysList线程不安全,Vector线程安全。ArraysList效率总是比Vector高。 - jdk8后ArraysList()是懒汉式创建容量为10的数组,Vector饿汉式创建容量为10的数组 - ArraysList扩容是原来的1.5倍,Vector扩容是原来的2倍 ArraysList VS LinkedList: - ArraysList底层是用数组存储的,LinkedList底层是用双向链表存储的 - 如果是频繁的插入、删除操作,LinkedList的效率比较高
-
如何区分ArrayList中remove(int index)和remove(Object obj)?
//区分ArraysList中的remove(Object)和remove(int) @Test public void test4(){ ArrayList list=new ArrayList(); list.add(1); list.add(2); list.add(3); System.out.println(list); //实参为整型时,认为按索引删除 list.remove(2); System.out.println(list); //实参为对象时,认为按元素删除 list.remove(new Integer(2)); System.out.println(list); }
五、Set接口
5.1 概述
-
Set容器中元素对应的类一定要重写equals()和hashCode()
- 添加元素时计算索引、判断是否重复
- 方法调用判断元素是否相同:set.contans(Object)、Collections.frequency(Collection,Object)等
-
解决的问题
/** * 数组的缺点: * 》长度不可修改:初始化后,无法根据需要扩缩容 * 》提供的方法非常有限: * - 添加、删除、插入数据:非常不便,同时效率不高。 * - 获取实际存储的元素个数:数组没有现成的属性或方法可用 * 》数据有序、可重复:无法满足无序、不可重复的需求(使用Set解决) */
-
Set集合类中元素无序、不可重复
/** * Set:存储无序的、不可重复的数据 * - 无序性:存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。不等于随机性。 * - 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。 */
5.2 常用实现类对比
- Set容器中元素对应的类一定要重写equals()和hashCode()
/**
* 面试题:Set存储数据的特点是什么?常见的实现类有什么?说说彼此的特点
*
* Set存储特点:无序、不可重复
*
* 常见实现类:
* HashSet:
* - 线程不安全的
* - 元素可以存储null值
* - jdk8底层存储使用数组+链表+红黑树。具有很好的存取、查找、删除性能。
* - HashSet()懒汉式创建长度为16的数组。当数组使用率超过0.75,就会扩大到原来的2倍
* LinkedHashSet:
* - 使用双向链表维护插入次序。插入性能略低于 HashSet,但在迭代访问时有很好的性能。
* - 对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
* TreeSet:
* - 按照指定方式排序,查询速度比List快
* - 底层使用红黑树存储
*/
5.3 HashSet
- 线程不安全的
- 元素可以存储null值
- jdk8底层存储使用数组+链表+红黑树。具有很好的存取、查找、删除性能。
- HashSet()懒汉式创建长度为16的数组。当数组使用率超过0.75,就会扩大到原来的2倍
元素添加源码分析
/**
* 1. 添加元素a,首先根据元素a所在类hashCode()的值计算元素a的hash值
* 2. 将哈希值通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置)
* 3. 判断数组此位置上是否已经有元素:
* 3.1 如果没有,则元素a添加成功。 --->情况1
* 3.2 如果有元素b(或以链表形式存在的多个元素),比较元素a与b的hash值:
* 3.2.1 如果不相同,则元素a添加成功。 --->情况2
* 3.2.2 如果相同,调用元素a所在类的equals()方法:
* - 返回false,则元素a添加成功。 --->情况3
* - 返回true,元素a添加失败
*
* 对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
* jdk 7 :元素a放到数组中,作为头节点next指针指向原来的元素。
* jdk 8 :尾节点的next指针指向元素a,元素a作为新的尾节点
* 总结:七上八下
*
* HashSet底层:数组+链表(jdk7)、数组+链表+红黑树(jdk8)
*
*/
5.4 LinkedHashSet
- 使用双向链表维护插入次序。插入性能略低于 HashSet,但在迭代访问时有很好的性能。
- 对于频繁的遍历操作,效率高于HashSet
5.5 TreeSet
-
按key的指定方式排序,元素必须指定排序方式:查询效率比List快
- 自然排序:元素必须是相同类的对象,否则 ClasssCastException。不管 compareTo() 是怎么实现的。
- 定制排序:TreeSet传入 Comparator对象
-
底层使用红黑树存储:
//头节点 private transient NavigableMap<E,Object> m;
-
判断两个key相等的标准:compareTo() 或者 compare() 返回0。
注意:两个对象通过 equals() 比较返回true,则通过 compareTo(Object obj) 比较也应返回0。否则,让人难以理解。
/**
* TreeSet:
* 1.集合中对象对应的实现类必须指定排序方式,查询效率比List快。
* - 自然排序:实现Comparable接口。元素必须是相同类的对象,不管 compareTo() 是怎么实现的。
* - 定制排序:构造方法传入Comparator对象。
* - 除第一个元素外,后面添加的所有元素都会调用 compareTo() 进行比较
* 2.判断两个对象是否相同:compareTo() 或者 compare() 返回0
* 3.底层使用红黑树存储
* 4.新增方法
* - first():返回最小元素
* - last():返回最大元素
* - lower(Object e):返回比 Object 低的最大元素
* - higher(Object):返回比 Object 高的最小元素
* - subSet(Object, Object):返回 [Object,Object)范围的Set集合
* - headSet(Object):返回 [head,Object)范围的Set集合
* - tailSet(Object):返回 [Object,tail] 范围的Set集合
*
* 注意:两个对象通过 equals() 比较返回true,则通过 compareTo(Object obj) 比较也应返回0。否则,让人难以理解。
*/
public class TreeSetTest {
@Test
public void test1(){
//2.定制排序:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
TreeSet set=new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Person && o2 instanceof Person){
Person p1= (Person) o1;
Person p2= (Person) o2;
int i = p1.name.compareTo(p2.name);
if(i==0){
return Integer.compare(p1.age,p2.age);
}
return i;
}
throw new RuntimeException("类型转换错误!");
}
});
set.add(new Person("b",11));
//set.add("111"); 1.集合中元素必须都是相同类的对象:ClassCastException
set.add(new Person("c",2));
set.add(new Person("a",55));
set.add(new Person("a",5));
//4.新增方法
//(1)first():返回最小元素
System.out.println(set.first()); //Person{name='a', age=5}
//(2)last():返回最大元素
System.out.println(set.last()); //Person{name='c', age=2}
//(3)lower(Object e):返回比 Object 低的最大元素
System.out.println(set.lower(new Person("b",1))); //Person{name='a', age=55}
//(4)higher(Object):返回比 Object 高的最小元素
System.out.println(set.higher(new Person("a",5))); //Person{name='a', age=55}
//(5)subSet(Object, Object):返回 [Object,Object)范围的Set集合
System.out.println(set.subSet(new Person("a",55),new Person("b",11))); //[Person{name='a', age=55}]
//(6)headSet(Object):返回 [head,Object)范围的Set集合
System.out.println(set.headSet(new Person("a",55))); //[Person{name='a', age=5}]
//(7)tailSet(Object):返回 [Object,tail] 范围的Set集合
System.out.println(set.tailSet(new Person("c",2))); //[Person{name='c', age=2}]
}
}
class Person implements Comparable{
String name;
Integer age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//2.自然排序
@Override
public int compareTo(Object o) {
if (o instanceof Person){
Person p= (Person) o;
return this.name.compareTo(((Person) o).name);
}else{
throw new RuntimeException("类型不匹配!");
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name) && Objects.equals(age, person.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
5.6 重写 hashCode与equals
5.6.1 hashCode()重写
- 同一个对象多次调用应该返回相同的值
- 两个对象 equals() 返回true 时,他们的 hashCode() 返回值也应相等
public int hashCode() {
return Objects.hash(name, age, sex);
}
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
//31*[31*(31+name.hashcode)+age.hashcode]+sex
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
问题:为什么重写的hashCode方法,有31这个数字?
- 选择系数的时候要选择尽量大的系数。这样计算出来的hash值差别越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)
- 并且31只占用5bits,相乘造成数据溢出的概率较小。
- 31可以由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化。(提高算法效率)
- 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)
5.6.2 equals() 重写
- 当一个类根据自己特有的"逻辑相等”**重写equals()**的时候,一定要重写hashCode()。
- 因为改写equals()后,两个截然不同的实例有可能在逻辑上是相等的。
- 根据Set的添加原理,逻辑上相等的两个实例需要放在数组的同一位置,才会去判断是否逻辑相等保证不可重复
- 而放在数组的同一位置需要保证hash值相同,即hashCode()返回值相等。
public boolean equals(Object o) {
if (this == o) return true; //1.地址一样返回true
if (o == null || getClass() != o.getClass()) return false; //2.不属于同一个类返回false
Person person = (Person) o;
//3.属于同一个类,属性一样返回true。否则返回false
return age == person.age && Objects.equals(name, person.name) && Objects.equals(sex, person.sex);
}
5.7 面试题
在List内去除重复数字值,要求尽量简单
//1.在List内去除重复数字值,要求尽量简单
@Test
public void test1(){
List list=new ArrayList();
list.add(1);
list.add(1);
Set set=new HashSet();
set.addAll(list);
System.out.println(set); //1
}
回答代码中出现的问题
public class SetTest {
//2.回答代码中出现的问题
@Test
public void test2(){
Set set=new HashSet();
Student s1=new Student("a",1);
Student s2=new Student("b",2);
set.add(s1);
set.add(s2);
System.out.println(set);
s1.name="c";
set.remove(s1);
System.out.println(set); //问题1:现在set中有几个元素?:2个
set.add(s1);
System.out.println(set); //问题2:现在set中有几个元素?:3个
set.add(new Student("a",1));
System.out.println(set); //问题3:现在set中有几个元素?:4个
}
}
class Student{
String name;
int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
六、Map接口
6.1 概述
- Map容器中元素的key对应的类一定要重写equals()和hashCode()
- 添加元素时计算索引、判断是否重复
- 方法调用判断值是否相同:map.containsKey(Object)等
- Map容器中元素的value对应的类一定要重写equals()
- 方法调用判断值是否相同:map.containsValue(Object)等
- Map集合类中可元素无序、不可重复(覆盖)
注意:添加到集合中的元素不要再进行key的修改
6.2 常用实现类对比
/**
* 面试题:
* 1. HashMap的底层实现原理?
* 2. HashMap 和 Hashtable 的异同?
* - HashMap与Hashtable都实现了Map接口。由于HashMap的非线程安全性,效率上可能高于Hashtable。
* Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,
* 不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。
* - HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。
* - HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。
* 因为contains方法容易让人引起误解。
* - Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。
* - Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异。
* 3. CurrentHashMap 与 Hashtable的异同?(暂时不讲)
*
* Map:无序、不可重复(覆盖)
*
* HashMap:
* - key和value可以为null
* - 底层使用数组+链表+红黑树存储(jdk8)
* - 扩容为原来的2倍
* - 默认容量16
* LinkedHashMap:作为HashMap的子类
* - 基于HashMap底层,增加before、after指针,记录元素添加的顺序。
* - 对于频繁的遍历操作,此类执行效率高于HashMap。
* TreeMap:
* - 底层使用红黑树存储,指定排序方式,查询效率比list快
* Hashtable:作为古老的实现类
* - 线程安全的,效率低;
* - key和value不能为null
* Properties:作为Hashtable的子类
* - key和value都是String类型
*
*
*/
6.3 HashMap
- 数组初始容量为16
- 扩容为原来的2倍
- 内部存储结构:数组+链表+红黑树(jdk8),查询速度快
- 添加键值:7上8下
- key和value可以为null
注意:添加到集合中的元素不要再进行key的修改
/* 总结:
* jdk8 相较于jdk7在底层实现方面的不同:
* 1.new HashMap():数组创建为懒汉式,首次调用put()时才创建
* 2.jdk 8底层的数组是:Node[],而非Entry[]
* 3.在jdk8中底层存储结构为:数组+链表+红黑树。
* 3.1 形成链表时,七上八下
* 3.2 链表长度=8 且 数组容量>=64 时,链表转换为红黑树
* 4.jdk8扩容判断在节点添加完成后进行。jdk7扩容判断是在节点插入之前进行的
*/
6.3.1 JDK7实现原理
- 饿汉式创建数组
/**
*
* 面试题:
* 1.HashMap什么时候进行扩容呢? 元素个数大于阈值 且 bucket != null
* - 扩容是非常消耗性能的,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
* 2.添加元素的流程:
* 先判断bucket是否为空,为空就直接添加。
* 不为空就遍历链表,判断key是否存在
* key存在分为两种情况:
* 第一种情况:数组下标为0的链表中存储key=null,覆盖
* 第二种情况:对应链表中存在 hash值相同且equals为true的key,覆盖
* key不存在,添加成功
*
*
* Entry[]:数组
* Capacity:数组容量
* bucket:桶,数组中存放元素的位置。即Entry[i]
* threshold:阈值
* Entry:key-value 节点
* static class Entry<K,V> implements Map.Entry<K,V> {
* final K key;
* V value;
* Entry<K,V> next;
* int hash;
* }
*
* DEFAULT_INITIAL_CAPACITY : 数组默认容量,16
* DEFAULT_LOAD_FACTOR:默认加载因子:0.75
* threshold(扩容的临界值=容量*填充因子):16 * 0.75 => 12
*/
//一.调用有参构造器
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//二.创建容量16的数组,计算出阈值12
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY) //MAXIMUM_CAPACITY = 1 << 30
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +loadFactor);
int capacity = 1;
//容量总是2的倍数
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
//计算阈值:16*0.75=12
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}
//三.存放key-value
public V put(K key, V value) {
//3.1 判断:key为null
if (key == null)
return putForNullKey(value);
int hash = hash(key); //计算key的hash值
int i = indexFor(hash, table.length);//通过hash值计算索引位置
//3.2 判断:hash值相同 && ==或equals为true
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
//覆盖
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//3.3
//情况一:bucket为null:添加成功
//情况二:hash值不同:添加成功
//情况三:hash值相同,==或equals为false:添加成功
addEntry(hash, key, value, i);
return null;
}
//3.1 判断:key为null
private V putForNullKey(V value) {
//3.1.1 集合中存在:table[0]链表存在key为null的节点,覆盖
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//3.1.2 集合中不存在:添加成功
addEntry(0, null, value, 0);
return null;
}
//3.1.2
//3.3
//情况一:bucket为null:添加成功
//情况二:hash值不同:添加成功
//情况三:hash值相同,==或equals为false:添加成功
void addEntry(int hash, K key, V value, int bucketIndex) {
//3.1.2.1 判断是否需要扩容:(元素个数>阈值 && table[0] != null )
if ((size >= threshold) && (null != table[bucketIndex])) {
//扩容:原来的2倍
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
//扩容后重写计算索引下标
bucketIndex = indexFor(hash, table.length);
}
//3.1.2.2 创建节点(key的hash值,key,value,数组索引)
createEntry(hash, key, value, bucketIndex);
}
//3.1.2.1 扩容(最消耗性能):原来的2倍
void resize(int newCapacity) {
//(1)保存旧的数组
Entry[] oldTable = table;
//(2)保存旧的容量
int oldCapacity = oldTable.length;
//(3)旧容量是否==1 << 30:如果是,将阈值设置为整型最大值。返回
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//(4)创建新容量的空数组
Entry[] newTable = new Entry[newCapacity];
boolean oldAltHashing = useAltHashing;
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ useAltHashing;
//(5)将元素转移到新数组
transfer(newTable, rehash);
//(6)保存新数组
table = newTable;
//(7)计算新阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
//(5)将元素转移到新数组
void transfer(Entry[] newTable, boolean rehash) {
//获取新阈值
int newCapacity = newTable.length;
//遍历旧数组
for (Entry<K,V> e : table) {
while(null != e) {
//保存next指针
Entry<K,V> next = e.next;
//是否重写计算hash值
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
//计算新数组中的索引位置
int i = indexFor(e.hash, newCapacity);
//next指向新数组对应索引位置的链表
e.next = newTable[i];
//将元素放在新数组的bucket位置
newTable[i] = e;
//循环旧数组链表下一元素
e = next;
}
}
}
//3.1.2.2 创建节点:新节点指向旧节点
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
6.3.2 JDK8实现原理
- 懒汉式创建数组
/**
* 面试题:
* 1.HashMap什么时候进行扩容和树形化呢?
* - 扩容:元素个数大于阈值(数组容量*0.75)
* - 树形化:链表元素个数=8 且 数组容量>=64
* - 扩容是非常消耗性能的,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
* 2.添加元素的流程:
* 第一次调用先初始数组
* 先判断bucket是否为空,为空就直接添加。
* 不为空就遍历链表,判断key是否存在
* key已经存在:覆盖
* key不存在,添加成功
* 添加完成后:判断是否需要扩容
*
*
* Node[]:数组
* Capacity:数组容量
* bucket:桶,数组中存放元素的位置。即Entry[i]
* threshold:阈值
* Entry:key-value 节点
* static class Node<K,V> implements Map.Entry<K,V> {
* final int hash;
* final K key;
* V value;
* Node<K,V> next;
* }
*
* DEFAULT_INITIAL_CAPACITY : 数组默认容量,16
* DEFAULT_LOAD_FACTOR:默认加载因子:0.75
* threshold(扩容的临界值=容量*填充因子):16 * 0.75 => 12
* TREEIFY_THRESHOLD(链表长度大于该默认值,转化为红黑树):8
* MIN_TREEIFY_CAPACITY(数组长度大于该默认值,链表转化为红黑树):64
*
*/
//一.加载因子赋值为0.75
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
//二.存放key-value
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//三.存放key-value
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//3.1 如果数组还未初始则进行默认初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//3.2 如果bucket为null则直接添加
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null); //添加成功
//3.3 bucket不为null
else {
Node<K,V> e; K k;
//3.3.1 如果bucket位存在key重复则进行覆盖
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p; //覆盖
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//3.3.2 bucket位不存在key重复
else {
for (int binCount = 0; ; ++binCount) {
//(1) 链表中不存在key重复:尾指针指向新节点
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//如果7>=8-1 且 数组容量>=64:将链表转换为红黑树,即添加节点后链表元素个数=8
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
//(2) 链表中出现key重复
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//3.3.3 链表中出现key重复:覆盖
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//3.4 元素个数超过阈值
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
//3.1 如果数组还未初始则进行默认初始化
//3.4 元素个数超过阈值:扩容
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;
}
//3.4.1 容量和阈值都扩大为原来的2倍
//newCap=32,newThr=24
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1;
}
else if (oldThr > 0)
newCap = oldThr;
else {
newCap = DEFAULT_INITIAL_CAPACITY; //3.1.1 获取默认容量16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //3.1.2 获取默认阈值16*0.75=12
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//3.4.2 设置阈值
threshold = newThr; //3.1.3 设置阈值
@SuppressWarnings({"rawtypes","unchecked"})
//根据新的容量创建新数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//保存新数组
table = newTab;
//3.4.3 将元素转移到新数组
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;
}
}
}
}
}
//3.1.1 返回容量为16的空数组,设置阈值12
return newTab;
}
6.4 LinkedHashMap(了解)
-
基于HashMap 底层,添加 before, after 指针,记录元素添加的顺序
-
对于频繁的遍历操作,此类执行效率高于 HashMap
//1.重写HashMap的newNode方法,创建的节点为重新定义的内部类 Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMap.Entry<K,V> p =new LinkedHashMap.Entry<K,V>(hash, key, value, e); linkNodeLast(p); return p; } //2.继承HashMap的节点,添加 before, after 前后两个指针 static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } } //HashMap内部定义的Node类 static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; }
6.5 常用方法
- 增删改
- put(Object,Object):添加或修改 key-value
- putAll(Map):将Map中所有元素进行添加
- remove(Object):移除指定key-value,返回value、
- clear():清空
- 获取
- get(Object):根据key获取value
- size():获取元素个数
- keySet():获取所有key构成的Set集合
- values():获取所有value构成的Collection集合
- entrySet():获取所有key-value对构成的Set集合
- 判断
- containsKey(Object):是否包含指定key
- containsValue(Object):是否包含指定的value
- isEmpty():判断集合是否为空
- equals(Object):判断两个map是否相等
/**
* Map常用方法:
*
* 增删改
* - 1.put(Object,Object):添加或修改 key-value
* - 2.putAll(Map):将Map中所有元素进行添加
* - 3.remove(Object):移除指定key-value,返回value、
* - 4.clear():清空 与map=null不同
* 获取
* - 5.get(Object):根据key获取value
* - 6.size():获取元素个数
* - 7.keySet():获取所有key构成的Set集合
* - 8.values():获取所有value构成的Collection集合
* - 9.entrySet():获取所有key-value对构成的Set集合
* 判断
* - 10.containsKey(Object):是否包含指定key
* - 11.containsValue(Object):是否包含指定的value
* - 12.isEmpty():判断集合是否为空
* - 13.equals(Object):判断两个map是否相等
*/
public class MapTest {
//增删改
@Test
public void test1(){
Map map1=new HashMap();
Map map2=new HashMap();
//1.put(Object,Object):添加或修改 key-value
map2.put("1",new Student("小红",12));
map2.put("2",new Student("小明",12));
//2.putAll(Map):将Map中所有元素进行添加
map1.putAll(map2);
System.out.println(map1);// {1=Student{name='小红', age=12}, 2=Student{name='小明', age=12}}
//3.remove(Object):移除指定key-value,返回value、
map1.remove("2");// {1=Student{name='小红', age=12}}
System.out.println(map1);
//4.clear():清空
map1.clear();
System.out.println(map1);// {}
}
//获取
@Test
public void test2(){
Map map=new HashMap();
map.put("1",new Student("小红",12));
map.put("2",new Student("小明",12));
//5.get(Object):根据key获取value
System.out.println(map.get("1")); //Student{name='小红', age=12}
//6.size():获取元素个数
System.out.println(map.size()); //2
//7.keySet():获取所有key构成的Set集合
Set set = map.keySet();
for (Object o : set) {
System.out.println(o); //1 2
}
//8.values():获取所有value构成的Collection集合
Collection values = map.values();
for (Object value : values) {
System.out.println(value);//Student{name='小红', age=12} Student{name='小明', age=12}
}
//9.entrySet():获取所有key-value对构成的Set集合
Set set1 = map.entrySet();
for (Object o : set1) {
if (o instanceof Map.Entry){
Map.Entry entry= (Map.Entry) o;
String key = (String) entry.getKey();
Student value = (Student) entry.getValue();
System.out.println(key+"="+value);//1=Student{name='小红', age=12} 2=Student{name='小明', age=12}
}else {
throw new RuntimeException("类型转换错误!");
}
}
}
//判断
@Test
public void test3(){
Map map1=new HashMap();
Map map2=new HashMap();
map2.put("1",new Student("小红",12));
map2.put("2",new Student("小明",12));
map1.putAll(map2);
//10.containsKey(Object):是否包含指定key
System.out.println(map1.containsKey("1")); //true
//11.containsValue(Object):是否包含指定的value
System.out.println(map1.containsValue(new Student("小红",12))); //true
//12.isEmpty():判断集合是否为空
System.out.println(map1.isEmpty()); //false
//13.equals(Object):判断两个map是否相等
System.out.println(map1.equals(map2)); //true
}
}
class Student{
String name;
int age;
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
6.6 TreeMap
-
按key的指定方式排序,元素必须指定排序方式:查询效率比List快。
- 自然排序:key必须为同一个类的实现,否则 ClasssCastException。不管 compareTo() 是怎么实现的。
- Key 对应的类实现 Comparable 接口。
- 定制排序:向 TreeMap 传入 Comparator 对象
- 自然排序:key必须为同一个类的实现,否则 ClasssCastException。不管 compareTo() 是怎么实现的。
-
底层使用红黑树存储数据
//头节点 private transient Entry<K,V> root;
-
判断两个key相等的标准:compareTo() 或者 compare() 返回0。
注意:两个对象通过 equals() 比较返回true,则通过 compareTo(Object obj) 比较也应返回0。否则,让人难以理解。
6.7 Hashtable
- 线程安全
- key和value 不可以为null
6.8 Properties
- 作为 Hashtable的子类
- key、value 都是 String类型
- 常用方法:
- 保存:setProperty(String,String)
- 获取:getProperty(String)
/**
* 注意:这里没有使用标准的流处理
* 常用方法:
* 1.setProperty(String,String)
* 2.getProperty(String)
*/
public class PropertiesTest {
@Test
public void test() throws IOException {
//(1)不建议使用绝对路径
//(2)使用FileInputStream 默认路径为项目根目录下,也即是JavaseTest下面。
//fis =new FileInputStream("Collection_Demo/src/main/resources/jdbc.properties");
//(3)使用Class.getResourceAsStream() 默认路径为类路径下当前类的位置
//fis= PropertiesTest.class.getResourceAsStream("../../../jdbc.properties");
//(4)使用Class.getClassLoader().getResourceAsStream 默认路径为类路径
InputStream fis= PropertiesTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties=new Properties();
properties.load(fis);
//1.setProperty(String,String)
properties.setProperty("password","123");
//2.getProperty(String)
String name = properties.getProperty("name");
String password = properties.getProperty("password");
System.out.println("name="+name);
System.out.println("password="+password);
}
}
七、Collections工具类
提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,包括设置集合对象不可变、获取线程安全类等方法
- 用于操作 Set、List、Map 等集合
- 修改方法:
- swap(List,int,int):交换List集合中元素
- copy(List dest,List src):将src中的内容复制到dest中,注意dest内部数组容量必须足够大
- replaceAll(List,Object oldVal,Object newVal):用newVal替换 List 中所有的oldVal
- 排序方法:
- reverse(List):反转List 中元素顺序
- shuffle(List):随机排序List集合元素
- sort(List):升序排序List 集合元素
- sort(List,Comparator):升序排序List 集合元素
- 获取:
- max(Collection):返回Collection中的最大元素
- max(Collection,Comparator):返回Collection中的最大元素
- min(Collection):返回Collection中的最小元素
- min(Collection,Comparator):返回Collection中的最小元素
- frequency(Collection,Object):返回Collection中指定元素的个数
- 线程安全类获取
/**
* 面试题:Collection和Collections的区别?
* - Collection是集合类的上级接口,子接口有List和Set
* - Collections是用于操作Collection、Map的工具类,提供一系列静态方法用于集合的排序、复制、修改、线程安全类获取等操作
*
* Collections常用方法:
* - 修改:
* 1. swap(List,int,int):交换List集合中元素
* 2. copy(List dest,List src):将src中的内容复制到dest中,注意dest内部数组容量必须足够大
* 3. replaceAll(List,Object oldVal,Object newVal):用newVal替换 List 中所有的oldVal
* - 排序:
* 4. reverse(List):反转List 中元素顺序
* 5. shuffle(List):随机排序List集合元素
* 6. sort(List):自然排序List 集合元素
* 7. sort(List,Comparator):定制排序List 集合元素
* - 获取:
* 8. max(Collection):返回Collection中的最大元素
* 9. max(Collection,Comparator):返回Collection中的最大元素
* 10.min(Collection):返回Collection中的最小元素
* 11.min(Collection,Comparator):返回Collection中的最小元素
* 12.frequency(Collection,Object):返回Collection中指定元素的个数
* 13.线程安全类获取
*/
public class CollectionsTest {
@Test
public void test1(){
List<String> list1=new ArrayList();
list1.add("2");
list1.add("1");
list1.add("3");
//1. swap(List,int,int):交换List集合中元素
Collections.swap(list1,0,1);
System.out.println(list1); //[1, 2, 3]
//2. copy(List dest,List src):将src中的内容复制到dest中
List<String> list2= Arrays.asList(new String[list1.size()]);
Collections.copy(list2,list1);
System.out.println(list2); //[1, 2, 3]
//3. replaceAll(List,Object oldVal,Object newVal):用newVal替换 List 中所有的oldVal
Collections.replaceAll(list1,"1","newValue");
System.out.println(list1); //[newValue, 2, 3]
}
@Test
public void test2(){
List<Student> list=new ArrayList();
list.add(new Student("1",1));
list.add(new Student("2",2));
list.add(new Student("3",3));
//4. reverse(List):反转List 中元素顺序
Collections.reverse(list);
System.out.println(list); //[Student{name='3', age=3}, Student{name='2', age=2}, Student{name='1', age=1}]
//5. shuffle(List):随机排序List集合元素
Collections.shuffle(list);
System.out.println(list); //[Student{name='1', age=1}, Student{name='2', age=2}, Student{name='3', age=3}]
//6. sort(List):自然排序排序List 集合元素
Collections.sort(list);
System.out.println(list); //[Student{name='1', age=1}, Student{name='2', age=2}, Student{name='3', age=3}]
//7. sort(List,Comparator):定制排序List 集合元素
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
if (o1 instanceof Student && o2 instanceof Student){
return -o1.name.compareTo(o2.name); //[Student{name='3', age=3}, Student{name='2', age=2}, Student{name='1', age=1}]
}else {
throw new RuntimeException("类型转换错误!");
}
}
});
System.out.println(list);
}
@Test
public void test3(){
List<Student> list=new ArrayList();
list.add(new Student("1",1));
list.add(new Student("2",2));
list.add(new Student("3",3));
//8. max(Collection):返回Collection自然排序中的最大元素
System.out.println(Collections.max(list)); //Student{name='3', age=3}
//9. max(Collection,Comparator):返回Collection定制排序中的最大元素
System.out.println(Collections.max(list, new Comparator<Student>() { //Student{name='1', age=1}
@Override
public int compare(Student o1, Student o2) {
if (o1 instanceof Student && o2 instanceof Student){
return -o1.name.compareTo(o2.name); //[Student{name='3', age=3}, Student{name='2', age=2}, Student{name='1', age=1}]
}else {
throw new RuntimeException("类型转换错误!");
}
}
}));
//10.min(Collection):返回Collection自然排序中的最小元素
System.out.println(Collections.min(list)); //Student{name='1', age=1}
//11.min(Collection,Comparator):返回Collection定制排序中的最小元素
System.out.println(Collections.min(list, new Comparator<Student>() { //Student{name='3', age=3}
@Override
public int compare(Student o1, Student o2) {
if (o1 instanceof Student && o2 instanceof Student){
return -o1.name.compareTo(o2.name); //[Student{name='3', age=3}, Student{name='2', age=2}, Student{name='1', age=1}]
}else {
throw new RuntimeException("类型转换错误!");
}
}
}));
//12.frequency(Collection,Object):返回Collection中指定元素的个数
System.out.println(Collections.frequency(list,new Student("1",1))); //1
}
}