课程链接:https://edu.aliyun.com/course/1012
第1~6章 关于线程:https://blog.csdn.net/weixin_43494837/article/details/113764127?spm=1001.2014.3001.5501
第7~14章 类库+正则+国际化+比较器:https://blog.csdn.net/weixin_43494837/article/details/113764156?spm=1001.2014.3001.5501
第15~20章 关于IO:https://blog.csdn.net/weixin_43494837/article/details/113764173?spm=1001.2014.3001.5501
第21~27章 关于反射:https://blog.csdn.net/weixin_43494837/article/details/112797304?spm=1001.2014.3001.5501
(思维导图在最后)
————————————————————————————————————
类集:一套动态对象数组的实现方案。
在实际开发中,没有任何一项开发可以离开数组,但是传统的数组实现起来非常繁琐,而且长度是其致命伤。正因为长度问题,所以传统的数组是不可能大范围使用的,但开发又离不开数组,所以最初就只能依靠一些数据结构来实现动态的数组处理,其中最为重要的两个结构:链表、树。然而这些数据结构的实现,也有如下的一些问题:
因此,在JDK1.2,Java引入了类集开发框架。主要就是对常见的数据结构进行完整的实现包装,并提供一系列的接口与实现子类,来帮助用户减少使用数据结构而带来的开发困难。(最初的类集实现,由于Java本身的技术所限,所以对于数据的控制并不严格,全部采用了Object类型进行数据接收;在JDK1.5后,由于泛型技术的推广,所以类集本身也得到了良好的改进,可以直接使用泛型来保存相同类型的数据;并且随着数据量的不断增加,从JDK1.8开始,类集的实现算法也得到了良好的性能提升。)
类集框架的核心接口:Collection、List、Set、Map、Iterator、Enumeration、Queue、ListIterator。
java.util.Collection是单值集合操作的最大的父接口,在该接口中定义有所有的单值数据的处理操作。(单值:每次操作只能保存一个对象,就类似每次子弹上膛只能上一颗)。
Collection包是从JDK1.2开始有的(JDK1.2最大的特征,除了引入图形界面Swing开发包,然后就是类集)。
Collection接口的核心方法:
No | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public boolean add(E e) | 普通 | 保存数据 |
2 | public boolean addAll(Collection extends E> c) | 普通 | 追加一组数据(如一个链表连接另一个链表) |
3 | public void clear() | 普通 | 清空集合(让根节点为空,并进行GC处理) |
4 | public boolean contains(Object o) | 普通 | 查询数据是否存在(需要equals()方法支持) |
5 | public boolean remove(Object o) | 普通 | 删除数据(需要equals()方法支持) |
6 | public int size() | 普通 | 获取数据长度(最大值为Integer.MAX_VALUE) |
7 | public Object[] toArray() | 普通 | 将集合转为对象数组 |
8 | public Iterator iterator() | 普通 | 将集合转为Iterator接口 |
以上方法中,【增加】add() 和 【输出】iterator() 这两个最常用。
在JDK1.5版本之前,Collection只是一个独立的接口;在JDK1.5后,提供了Iterable父接口,并且在JDK1.8后针对于Iterable接口也得到了一些扩充。另外,在JDK1.2~JDK1.4的时代里,如果要进行集合的使用往往会直接操作Collection接口,而从JDK1.5时代开始,更多情况下选择的都是Collection的两个子接口:允许重复的List子接口、不允许重复的Set子接口。
List是Collection的子接口,其最大的特点是允许保存有重复元素数据,该接口的定义如下:
public interface List extends Collection
List子接口对于Collection接口进行了方法扩充:
No | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public E get(int index) | 普通 | 获取指定索引的数据 |
2 | public E set(int index, E element) | 普通 | 修改指定索引的数据 |
3 | public ListIterator listIterator() | 普通 | 将List转为ListIterator |
List接口的常用子类:
从JDK1.9开始,List接口中追加有一些static方法,以方便用户的处理(如of()方法):
import java.util.List;
public class JavaDemo{
public static void main(String[] args) {
List list = List.of("Hello", "World", "Hello", "Pikaqiu");
// System.out.println(list); // 输出:[Hello, World, Hello, Pikaqiu]
Object[] objArr = list.toArray(); // 调用Collection接口中的toArray()方法
for (Object obj : objArr) {
System.out.print(obj + " "); // 输出:Hello World Hello Pikaqiu
}
}
}
在JDK1.8之后,Iterable父接口之中定义有一个forEach()循环输出的方法:
default void forEach(Consumer super T> action)
使用forEach()方法实现输出:
import java.util.List;
public class JavaDemo{
public static void main(String[] args) {
List list = List.of("Hello", "World", "Hello", "Pikaqiu");
list.forEach(System.out::println);
// 输出:
// Hello
// World
// Hello
// Pikaqiu
}
}
ArrayList类的定义:
public class ArrayList
extends AbstractList
implements List, RandomAccess, Cloneable, Serializable
示例:
import java.util.ArrayList;
import java.util.List;
public class JavaDemo{
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("Hello");
list.add("World");
list.add("Hello");
list.add("Pikaqiu");
list.add(null);
System.out.println(list); // 输出:[Hello, World, Hello, Pikaqiu, null]
System.out.println(list.get(0)); // 输出:Hello
}
}
可以发现,ArrayList中保存的数据是:
forEach()输出实现(非标准输出):
list.forEach((str)->{ // 消费型函数式接口
System.out.print(str + " "); // 输出:Hello World Hello Pikaqiu
});
需要注意的是,此种输出并不是正常开发情况下要考虑的操作形式。
ArrayList源码分析:
无参构造:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
DEFAULTCAPACITY_EMPTY_ELEMENTDATA常量定义:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
单参构造(可开辟指定容量的ArrayList):
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
elementData变量定义:
transient Object[] elementData; // transient:不可序列化
EMPTY_ELEMENTDATA常量定义:
private static final Object[] EMPTY_ELEMENTDATA = {}; // 默认开辟空数组
可以发现,ArrayList实际上封装的仍然是一个数组。
add()方法:
如追加数据时,长度不足够,则会开辟一个更大的新数组,并将旧数组的内容拷贝到新数组里,然后再进行追加。
当ArrayList中保存的容量不足时,会采用成倍的方式进行增长,如第一次增长10,第二次增长就是20。
所以再使用ArrayList子类时,一定要估算出数据量有多少,如果超过了10个,那么应该采用有参构造的方法进行创建,以避免产生垃圾数组。
实现:
import java.util.ArrayList;
import java.util.List;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "姓名:" + this.name + ",年龄:" + this.age;
}
}
public class JavaDemo{
public static void main(String[] args) {
List list = new ArrayList<>();
Person pikaqiu = new Person("皮卡丘", 6);
Person xiaozhi = new Person("小智", 12);
Person xiaojin = new Person("小进", 12);
list.add(pikaqiu);
list.add(xiaozhi);
list.add(xiaojin);
list.forEach(System.out::println);
System.out.println("————————————");
list.remove(xiaojin); // remove()方法,不常用
list.forEach(System.out::println);
System.out.println("————————————");
System.out.println(list.contains(pikaqiu)); // contains()方法,不常用
}
}
LinkedList类的定义:
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, Serializable
示例:
import java.util.LinkedList;
import java.util.List;
public class JavaDemo{
public static void main(String[] args) {
List list = new LinkedList<>();
list.add("Hello");
list.add("world");
list.add("Hello");
list.add("pikaqiu");
list.add(null);
System.out.println(list); // 输出:[Hello, world, Hello, pikaqiu, null]
// get()方法:根据索引获取数据内容
System.out.println(list.get(0)); // 输出:Hello
}
}
可以发现,LinkedList和ArrayList使用是完全一样的,但它们的内部实现机制是完全不同的。
面试题:请问ArrayList与LinkedList有什么区别?
Vector是一个原始古老的程序类,这个类是在JDK1.0时提供的。到了JDK1.2时,由于许多开发者已经习惯使用Vector,并且许多系统类也是基于Vector实现的,所以考虑到其使用的广泛性,类集框架将其保留了下来,并让其多实现了一个List接口。
Vector类的定义:
public class Vector
extends AbstractList
implements List, RandomAccess, Cloneable, Serializable
示例:
import java.util.List;
import java.util.Vector;
public class JavaDemo{
public static void main(String[] args) {
List list = new Vector<>();
list.add("Hello");
list.add("world");
list.add("Hello");
list.add("pikaqiu");
list.add(null);
System.out.println(list); // 输出:[Hello, world, Hello, pikaqiu, null]
// get()方法:根据索引获取数据内容
System.out.println(list.get(0)); // 输出:Hello
}
}
Vector类源码分析:
public Vector() {
this(10);
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
可以发现,Vector类的无参构造,是直接开辟一个10长度的数组,而后其余的实现操作与ArrayList是相同的。
并且,Vector类中的操作方法采用的都是synchronized同步处理,而ArrayList并没有进行同步处理,所以Vector类中的方法是线程安全的,但性能不如ArrayList。
Set集合最大的特点:不允许保存重复元素。
在JDK1.9以前,Set集合与Collection集合的定义并无差别;但在JDK1.9后,Set集合也像List集合一样扩充了一些static方法。
Set集合的定义:
public interface Set extends Collection
需要注意的是,Set集合并不像List集合那样扩充了许多的新方法,并没有List集合中的get()方法。
示例:
import java.util.Set;
public class JavaDemo{
public static void main(String[] args) {
Set set = Set.of("Hello", "World", "Hello", "Pikaqiu"); // 抛出异常
set.forEach(System.out::println);
// 抛出异常:
// Exception in thread "main" java.lang.IllegalArgumentException: duplicate element: Hello
}
}
可以发现,当Set使用of()新方法时,如有重复元素,则会抛出异常,这与传统的Set集合不保存重复元素的特点相一致。
Set接口的常用子类:
HashSet是Set接口中使用最多的一个子类,其最大的特点就是保存的数据是无序的。
HashSet类的定义:
public class HashSet
extends AbstractSet
implements Set, Cloneable, Serializable
示例:
import java.util.HashSet;
import java.util.Set;
public class JavaDemo{
public static void main(String[] args) {
Set set = new HashSet<>();
set.add("Hello");
set.add("Pikaqiu");
set.add("Hello");
set.add("World");
set.forEach(System.out::println);
// 输出:
// Hello
// World
// Pikaqiu
}
}
可以发现,HashSet中保存的数据是:
Set接口的另外一个子接口就是TreeSet,其与HashSet最大区别在于,TreeSet集合里保存的数据是有序的。
TreeSet类定义:
public class TreeSet
extends AbstractSet
implements NavigableSet, Cloneable, Serializable
在这个子类中依然继承了AbstractSet父抽象类,同时又实现了一个NavigableSet父接口:
示例:
import java.util.Set;
import java.util.TreeSet;
public class JavaDemo{
public static void main(String[] args) {
Set set = new TreeSet<>();
set.add("Hello");
set.add("Pikaqiu");
set.add("Hello");
set.add("World");
set.forEach(System.out::println);
// 输出:
// Hello
// Pikaqiu
// World
}
}
可以发现,HashSet中保存的数据是:
使用TreeSet保存自定义类:
import java.util.Set;
import java.util.TreeSet;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "姓名:" + this.name + ",年龄:" + this.age;
}
}
public class JavaDemo{
public static void main(String[] args) {
Set set = new TreeSet<>();
Person pikaqiu = new Person("皮卡丘", 6);
Person xiaozhi = new Person("小智", 12);
Person xiaojin = new Person("小进", 12); // 年龄相同,姓名不同
Person xiaojin2 = new Person("小进", 12); // 数据重复
Person xiaozhi2 = new Person("小智", 3);
set.add(pikaqiu); // 抛出异常
set.add(xiaozhi);
set.add(xiaojin);
set.add(xiaojin2);
set.add(xiaozhi2);
set.forEach(System.out::println);
// 抛出异常:
// Exception in thread "main" java.lang.ClassCastException: Person cannot be cast to java.base/java.lang.Comparable
}
}
从抛出的异常可以发现,TreeSet类允许保存自定义类数据,但自定义类必须要实现Comparable接口。
给Person类加上实现Comparable接口:
import java.util.Set;
import java.util.TreeSet;
class Person implements Comparable{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "姓名:" + this.name + ",年龄:" + this.age;
}
@Override
public int compareTo(Person person) {
if (this.age > person.age) return 1;
else if (this.age < person.age) return -1;
else return this.name.compareTo(person.name);
}
}
public class JavaDemo{
public static void main(String[] args) {
Set set = new TreeSet<>();
Person pikaqiu = new Person("皮卡丘", 6);
Person xiaozhi = new Person("小智", 12);
Person xiaojin = new Person("小进", 12); // 年龄相同,姓名不同
Person xiaojin2 = new Person("小进", 12); // 数据重复
Person xiaozhi2 = new Person("小智", 3);
set.add(pikaqiu);
set.add(xiaozhi);
set.add(xiaojin);
set.add(xiaojin2);
set.add(xiaozhi2);
set.forEach(System.out::println);
// 输出:
// 姓名:小智,年龄:3
// 姓名:皮卡丘,年龄:6
// 姓名:小智,年龄:12
// 姓名:小进,年龄:12
}
}
在使用自定义类对象进行比较处理的时候,一定要将该类中所有属性都依次进行大小关系的匹配,否则某一个或者几个属性相同的时候也会被认为是重复数据,所以TreeSet是利用了Comparable接口来确认重复数据的。
由于TreeSet在操作过程之中需要将类中的所有属性进行比对,这样的实现难度太高了(如类有很多个属性),所以在实际的开发中应该首选HashSet子类进行存储。
把上课时的代码中,主类里的TtreeSet换成HashSet:
public class JavaDemo{
public static void main(String[] args) {
Set set = new HashSet<>();
Person pikaqiu = new Person("皮卡丘", 6);
Person xiaozhi = new Person("小智", 12);
Person xiaojin = new Person("小进", 12); // 年龄相同,姓名不同
Person xiaojin2 = new Person("小进", 12); // 数据重复
Person xiaozhi2 = new Person("小智", 3);
set.add(pikaqiu);
set.add(xiaozhi);
set.add(xiaojin);
set.add(xiaojin2);
set.add(xiaozhi2);
set.forEach(System.out::println);
// 输出:
// 姓名:小智,年龄:3
// 姓名:小进,年龄:12
// 姓名:小进,年龄:12
// 姓名:小智,年龄:12
// 姓名:皮卡丘,年龄:6
}
}
可以发现,输出数据中仍然有重复的。
TreeSet类是利用了Comparable接口来实现了重复元素的判断,但HashSet判断重复元素的方式并不是利用Comparable接口完成的,它利用的是Object类中提供的方法实现的:
在进行重复元素判断时,首先利用hashCode()进行编码的匹配:
(hashCode()生成的编码就类似身份证号码一样,是根据一定的算法进行生成的。)
去除重复元素(重写equals()和hashCode()方法):
( idea编辑器自动equals()和hashCode()方法:Code → Generate → equals() and hashCode() )
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "姓名:" + this.name + ",年龄:" + this.age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
在Java程序中,真正的重复元素的判断处理,利用的就是equals()和hashCode()这两个方法共同作用完成的,而只有在有排序要求的情况下(如TreeSet),才会需要实现Comparable接口。
集合输出实际上从JDK1.8开始,就在Iterable接口中提供了一个forEach()方法可以进行输出,但它并不是传统意义上的集合输出形式,并且也很难在实际的开发中出现。对于集合操作而言,一共有四种输出形式:Iterator迭代输出(95%)、ListIterator双向迭代输出(0.1%)、Enumeration枚举输出(4.9%)、foreach输出(与Iterator相当)。
通过Collection接口的继承关系可以发现,从JDK1.5开始,其多继承了一个Iterable父接口,并且在这个接口里面定义有一个iterator()操作方法,通过此方法可以获取Iterator接口对象(在JDK1.5之前,这一方法直接定义在Collection接口之中):
Iterator接口的操作方法:
No | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public boolean hasNext() | 普通 | 判断是否还有数据 |
2 | public E next() | 普通 | 获取下一个数据 |
3 | default void remove() | 普通 | 删除数据 |
hasNext()和next()这两个方法,我们在之前使用Scanner类的时候就使用过,而Scanner类就是Iterator接口的子类。
示例:
import java.util.Iterator;
import java.util.Set;
public class JavaDemo{
public static void main(String[] args) {
// Set set = Set.of("Hello", "World", "Hello", "Pikaqiu"); // 有重复元素,抛出异常
// 抛出异常:Exception in thread "main" java.lang.IllegalArgumentException: duplicate element: Hello
Set set = Set.of("Hello", "World", "Pikaqiu");
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 输出:
// World
// Hello
// Pikaqiu
}
}
Iterator接口的remove()方法:
如非必须,请勿使用
Collection接口也有remove()方法,但它会导致迭代失败:
import java.util.Iterator;
import java.util.Set;
public class JavaDemo{
public static void main(String[] args) {
Set set = Set.of("Hello", "World", "Pikaqiu");
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
if (str.equals("World")) set.remove(str); // 抛出异常
// 抛出异常:Exception in thread "main" java.lang.UnsupportedOperationException
else System.out.println(str);
}
}
}
使用Iterator接口的remove()方法:
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class JavaDemo{
public static void main(String[] args) {
// 使用Iterator的remove()方法时,set要使用子类进行实例化
Set set = new HashSet<>();
set.add("Hello");
set.add("World");
set.add("Pikaqiu");
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
if (str.equals("World")) iterator.remove();
else System.out.println(str);
}
System.out.println("————————————");
System.out.println(set);
// 输出:
// Hello
// Pikaqiu
// ————————————
// [Hello, Pikaqiu]
}
}
面试题:请解释Collection.remove() 与 Iterator.remove() 的区别:
使用Iterator进行的迭代输出操作有一个特点:只允许由前向后输出,而如果现在需要进行双向迭代处理,那么就必须依靠Iterator的子接口:ListIterator接口来实现了。
ListIterator类的定义:
public interface ListIterator
extends Iterator
需要注意的是,如果想要获取ListIterator接口对象,Collection中并没有定义相关的处理方法,但是List子接口有,也就是说这个输出的接口是专门为List集合准备的。
ListIterator接口的操作方法:
No | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public boolean hasNext() | 普通 | 判断是否还有数据 |
2 | public E next() | 普通 | 获取下一个数据 |
3 | public boolean hasPrevious() | 普通 | 判断是否还有前一个数据 |
4 | public E previous() | 普通 | 获取前一个数据 |
示例:
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class JavaDemo{
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("Hello");
list.add("World");
list.add("Pikaqiu");
ListIterator listIterator = list.listIterator();
while (listIterator.hasNext()) {
System.out.println(listIterator.next());
}
System.out.println("————————————");
while (listIterator.hasPrevious()) {
System.out.println(listIterator.previous());
}
// 输出:
// Hello
// World
// Pikaqiu
// ————————————
// Pikaqiu
// World
// Hello
}
}
如果想实现由后向前的遍历,那么首先要实现的是由前向后实现遍历处理。
(也就是在调用previous()方法之前,需要有调用next()方法,否则previous()方法输出是没有内容的。)
Enumeration是JDK1.0时就提供的输出接口,它只为Vector一个类服务。
Enumeration类的定义:
public interface Enumeration
获取Enumeration对象:
Enumeration接口的操作方法:
No | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public boolean hasMoreElements() | 普通 | 判断是否还有数据 |
2 | public E nextElement() | 普通 | 获取下一个数据 |
示例:
import java.util.Enumeration;
import java.util.Vector;
public class JavaDemo{
public static void main(String[] args) {
Vector vector = new Vector<>();
vector.add("Hello");
vector.add("World");
vector.add("Pikaqiu");
Enumeration enumeration = vector.elements();
while (enumeration.hasMoreElements()) {
System.out.println(enumeration.nextElement());
}
// 输出:
// Hello
// World
// Pikaqiu
}
}
除了使用迭代接口实现输出之外,从JDK1.5开始,加强型for循环也可以实现集合的输出了。这种输出的形式与数组的输出操作形式类似:
import java.util.ArrayList;
import java.util.List;
public class JavaDemo{
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("Hello");
list.add("World");
list.add("Pikaqiu");
for (String str: list) {
System.out.println(str);
}
// 输出:
// Hello
// World
// Pikaqiu
}
}
这种输出最初出现时,很多人并不建议使用,因为标准的集合操作还是应该以Iterator为主,但毕竟JDK1.5都已经推出十多年了,很多语法也开始被大部分人所习惯。
Collection接口保存的数据:单个对象;Map接口保存的数据:二元偶对象(key=value),而存储二元偶对象的核心意义在于,需要通过key来获取对应的value。
Collection集合保存数据的目的:为了输出;Map集合保存数据的目的:为了进行key的查找。
Map接口时进行二元偶对象保存的最大父接口,接口定义如下:
public interface Map
Map接口的核心操作方法:
No | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public V put(K key,V value) | 普通 | 保存数据 |
2 | public V get(Object key) | 普通 | 获取数据 |
3 | public Set |
普通 | 将Map转为Set |
4 | public boolean containsKey(Object key) | 普通 | 判断key是否存在 |
5 | public Set keySet() | 普通 | 将所有的key转为Set |
6 | public V remove(Object key) | 普通 | 根据指定key删除数据 |
示例:
import java.util.Map;
public class JavaDemo{
public static void main(String[] args) {
Map map = Map.of("one", 1, "two", 2); // {one=1, two=2}
// Map map = Map.of("one", 1, "two", 2, "one", 11); // 抛出异常
// 抛出异常:Exception in thread "main" java.lang.IllegalArgumentException: duplicate key: one
// Map map = Map.of("one", 1, "two", 2, null, 11); // 抛出异常
// 抛出异常:Exception in thread "main" java.lang.NullPointerException
System.out.println(map);
}
}
在Map的of()方法中:
of()方法并不是标准用法,正常的开发中需要通过子类进行接口对象的实例化。
Map接口的常用子类:
HashMap是Map接口中最为常见的一个子类,其主要特点是无序存储。
HashMap类的定义:
public class HashMap
extends AbstractMap
implements Map, Cloneable, Serializable
示例:
import java.util.HashMap;
import java.util.Map;
public class JavaDemo{
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("one", 11); // key重复
map.put(null, 0); // key为null
map.put("zero", null); // value为null
System.out.println(map);
// 输出:
// {null=0, zero=null, one=11, two=2}
}
}
put()方法会返回该key原来的value:
import java.util.HashMap;
import java.util.Map;
public class JavaDemo{
public static void main(String[] args) {
Map map = new HashMap<>();
System.out.println(map.put("one", 1)); // 输出:null
System.out.println(map.put("one", 11)); // 输出:1
}
}
put()方法的源码分析:
在使用put()方法进行数据保存时,会调用一个putVal()方法,同时会将key进行hash处理(生成一个hash码);而在putVal()方法中,会提供一个Node节点类进行数据的保存,并会调用一个resize()方法进行容量的扩充。
面试题:在进行HashMap的put()操作时,如何实现容量扩充?
面试题:请解释HashMap的工作原理?
HashMap虽然是Map集合中最为常用的子类,但其保存的数据都是无序的(有序与否对Map没有影响),如果现在希望Map集合中保存的数据为有序的,则可以使用LinkedHashMap(基于链表实现的)。
LinkedHashMap类的定义:
public class LinkedHashMap
extends HashMap
implements Map
因为是用链表保存,所以在使用LinkedHashMap类时,数据量不要很大,否则会造成时间复杂度攀升。
示例:
import java.util.LinkedHashMap;
import java.util.Map;
public class JavaDemo{
public static void main(String[] args) {
Map map = new LinkedHashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("one", 11); // key重复
map.put(null, 0); // key为null
map.put("zero", null); // value为null
System.out.println(map);
// 输出:
// {one=11, two=2, null=0, zero=null}
}
}
Hashtable类是从JDK1.0就提供了的,与Vector、Enumeration为最早的一批动态数组实现类,并且是最早的key-value接口,后来为了将其继续保留下来,让其多实现了一个Map接口。
Hashtable类的定义:
public class Hashtable
extends Dictionary
implements Map, Cloneable, Serializable
示例:
import java.util.Hashtable;
import java.util.Map;
public class JavaDemo{
public static void main(String[] args) {
Map map = new Hashtable<>();
map.put("one", 1);
map.put("two", 2);
map.put("one", 11); // key重复
// map.put(null, 0); // key为null,抛出异常
// 抛出异常:java.lang.NullPointerException
// map.put("zero", null); // value为null,抛出异常
// 抛出异常:java.lang.NullPointerException
System.out.println(map);
// 输出:
// {two=2, one=11}
}
}
可以发现,在Hashtable中保存数据时,设置的key或value都不允许为null,否则会出现NullPointerException异常。
面试题:请解释HashMap与Hashtable的区别?
在LinkedList中,数据存储依靠的是链表,会把数据存储到Node节点中;而HashMap中的Node内部类实现了Map.Entry的内部接口:
static class Node implements Map.Entry
Map.Entry接口的定义:
public static interface Map.Entry
在JDK1.9之前,基本都不会考虑创建Map.Entry对象;但在JDK1.9之后,Map接口新增了entry()方法。
创建Map.Entry对象:
static Map.Entry entry (K k, V v)
Map.Entry接口的方法:
示例:
import java.util.Map;
public class JavaDemo{
public static void main(String[] args) {
Map.Entry entry = Map.entry("one", 1);
System.out.println("key = " + entry.getKey());
System.out.println("value = " + entry.getValue());
System.out.println(entry.getClass().getName());
// 输出:
// key = one
// value = 1
// java.util.KeyValueHolder
}
}
在Map集合里,Map.Entry就是作为Key+Value的包装类。在数据存储时,都会把key和value包装为一个Map.Entry对象来使用。
迭代输出Map集合有两种方式:
虽然Map集合可以进行迭代输出,但它主要的用法在于进行key的查找。
利用Iterator输出Map集合(集合的标准输出):
集合的标准输出:利用Iterator接口进行循环输出。但Map接口中并没有方法可以直接返回Iterator对象。
Map集合中提供的方法:
利用Iterator输出Map集合的步骤:
示例:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class JavaDemo{
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
// 1. 将Map集合转为Set集合
Set> set = map.entrySet();
// 2. 将Set集合转为Iteratpr接口实例
Iterator> iterator = set.iterator();
// 3. 利用Iterator进行迭代输出
while (iterator.hasNext()) {
Map.Entry entry = iterator.next();
System.out.println(entry.getKey() + ":" + entry.getValue());
}
// 输出:
// one:1
// two:2
// three:3
}
}
利用foreach输出Map集合(集合的非标准输出):
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class JavaDemo{
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
// 1. 将Map集合转为Set集合
Set> set = map.entrySet();
// 2. 利用foreach进行迭代输出
for (Map.Entry entry : set) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
// 输出:
// one:1
// two:2
// three:3
}
}
Map集合的key使用自定义类:
import java.util.HashMap;
import java.util.Map;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "姓名:" + this.name + ",年龄:" + this.age;
}
}
public class JavaDemo{
public static void main(String[] args) {
{
Map map = new HashMap<>();
map.put("林弱", new Person("林强", 20));
System.out.println(map);
System.out.println(map.get("林弱"));
// 输出:
// {林弱=姓名:林强,年龄:20}
// 姓名:林强,年龄:20
}
System.out.println("————————————");
{
Map map = new HashMap<>();
Person linqiang = new Person("林强", 20);
map.put(linqiang, "林弱");
System.out.println(map);
System.out.println(map.get(linqiang));
// 输出:
// {姓名:林强,年龄:20=林弱}
// 林弱
}
System.out.println("————————————");
{
Map map = new HashMap<>();
map.put(new Person("林强", 20), "林弱");
System.out.println(map);
System.out.println(map.get(new Person("林强", 20)));
// 输出:
// {姓名:林强,年龄:20=林弱}
// null
}
}
}
Map集合的源码分析:
put()方法源码:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
可以发现,在保存数据时,利用hash()方法生成了一个hash码,而hash()方法中又调用了Object类的hashCode()方法。
get()方法源码:
public V get(Object key) {
Node e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
而在获取数据时,也仍然使用到了hash码。
继续深入getNode()方法查找数据时,可以发现其调用了key的equals()方法。
所以,如Map集合中的key为自定义类时,这个类中需要重写hashCode()与equals()方法,如此,便可以根据自定义类的key查找到对应数据。
重写自定义类的hashCode()与equals()方法(果断编辑器直接生成):
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "姓名:" + this.name + ",年龄:" + this.age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public class JavaDemo{
public static void main(String[] args) {
Map map = new HashMap<>();
map.put(new Person("林强", 20), "林弱");
System.out.println(map);
System.out.println(map.get(new Person("林强", 20)));
// 输出:
// {姓名:林强,年龄:20=林弱}
// 林弱
}
}
虽然Map集合中可以使用自定义类作为key的类型,但常用的key类型就是String、Long、Integer,应该尽量使用系统类。
面试题:HashMap是如何解决在进行数据操作时出现的Hash冲突(Hash码相同)?
栈:先进后出的数据结构。
栈的基本操作形式:
在Java中,使用Stack来描述栈的操作,Stack类的定义:
public class Stack
extends Vector
Stack类是Vector类的子类,但它并没有使用Vector类的方法。
Stack类的方法:
示例:
import java.util.Stack;
public class JavaDemo{
public static void main(String[] args) {
Stack stack = new Stack<>();
stack.push("A");
stack.push("B");
stack.push("C");
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
// 输出:
// C
// B
// A
// Exception in thread "main" java.util.EmptyStackException
}
}
队列:先进先出的数据结构。
队列的基本操作形式:
在Java中,使用Queue来描述队列的操作,Queue接口的定义:
public interface Queue
extends Collection
Queue接口的方法:
示例:
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
public class JavaDemo{
public static void main(String[] args) {
// 方式一:使用LinkedList子类来实现Queue接口
// Queue queue = new LinkedList<>();
// 方式二:使用PriorityQueue优先级队列子类来实现Queue接口
Queue queue = new PriorityQueue<>();
queue.offer("A");
queue.offer("B");
queue.offer("C");
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
// 输出:
// A
// B
// C
// null
}
}
国际化程序中使用的资源文件(*.properties),其存储结构与Map集合类似,但其保存的只能是字符串。所以为了方便属性的描述,java.util包中提供了Properties类。
Properties类的定义:
public class Properties
extends Hashtable
Properties类的方法:
No | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public Object setProperty(String key, String value) | 普通 | 设置属性 |
2 | public String getProperty(String key) | 普通 | 获取属性,key不存在时返回null |
3 | public String getProperty(String key, String defaultValue) | 普通 | 获取属性,key不存在时返回默认值 |
4 | public void store(OutputStream out, String comments) throws IOException | 普通 | 通过输出流输出属性内容 |
5 | public void load(InputStream inStream) throws IOException | 普通 | 通过输入流读取属性内容 |
示例:
import java.util.Properties;
public class JavaDemo{
public static void main(String[] args) {
Properties properties = new Properties();
properties.setProperty("name", "pikaqiu");
System.out.println(properties.getProperty("name"));
System.out.println(properties.getProperty("age"));
System.out.println(properties.getProperty("age", "12"));
// 输出:
// pikaqiu
// null
// 12
}
}
Properties类可以像Map集合那样进行内容的设置和获取,但它只能操作String类型。
Properties类最重要的功能:可以通过输出流输出属性、通过输入流读取属性:
保存文件:
import java.io.File;
import java.io.FileOutputStream;
import java.util.Properties;
public class JavaDemo{
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
properties.setProperty("name", "pikaqiu");
properties.setProperty("age", "16");
String fileName = "D:" + File.separator + "test" + File.separator + "info.properties";
properties.store(new FileOutputStream(new File(fileName)), "pikaqiu-info");
}
}
info.properties文件内容:
#pikaqiu-info
#Sun Feb 14 22:11:48 CST 2021
name=pikaqiu
age=16
读取文件:
import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;
public class JavaDemo{
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
String fileName = "D:" + File.separator + "test" + File.separator + "info.properties";
properties.load(new FileInputStream(new File(fileName)));
System.out.println(properties.getProperty("name"));
System.out.println(properties.getProperty("age"));
// 输出:
// pikaqiu
// 16
}
}
ResourceBundle一般读取信息资源,Properties一般读取配置资源,主要在程序初始化准备时使用。
Collections:是Java提供的操作集合数据的工具类,也就是利用它可以实现各个集合的操作。
示例:
使用Collections操作List集合:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class JavaDemo{
public static void main(String[] args) {
List list = new ArrayList<>();
Collections.addAll(list, "Hello", "World", "Pikaqiu");
System.out.println(list); // 输出:[Hello, World, Pikaqiu]
Collections.reverse(list); // 实现反转
System.out.println(list); // 输出:[Pikaqiu, World, Hello]
Collections.sort(list); // 实现排序
System.out.println(list); // 输出:[Hello, Pikaqiu, World]
// binarySearch():二分查找,查找前需要先sort
System.out.println(Collections.binarySearch(list, "World")); // 输出:2
}
}
面试题:请解释Collection与Collections的区别?
从JDK1.8开始,进入到了大数据时代,所以在类集里也支持有数据的流式分析处理操作,为此就专门提供了Stream的接口,同时在Collection接口里也提供有为此接口实例化的方法:
Stream主要功能:
Stream接口的方法:
示例:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class JavaDemo{
public static void main(String[] args) {
List list = new ArrayList<>();
Collections.addAll(list, "Java", "JavaScript", "Ruby", "PHP", "Python", "GO", "Json", "JSP");
Stream stream = list.stream(); // 获取Stream接口对象
// System.out.println(stream.count()); // 获取个数,返回的为long型
{ // 通过函数式编程进行流式处理
// System.out.println(stream.filter(
// (ele)->ele.toLowerCase().contains("j")
// ).count()); // 输出:4
}
{ // 数据采集
// List result = stream.filter(
// (ele)->ele.toLowerCase().contains("j")
// ).collect(Collectors.toList());
// System.out.println(result); // 输出:[Java, JavaScript, Json, JSP]
}
{ // 数据分页,在采集之前分页
List result = stream.filter(
(ele)->ele.toLowerCase().contains("j")
).skip(2).limit(2).collect(Collectors.toList());
System.out.println(result); // 输出:[Json, JSP]
}
}
}
MapReduce基础模型:
在数据分析之前,必须要对数据进行合理的处理,然后才可以做统计分析操作。
示例:
import java.util.ArrayList;
import java.util.DoubleSummaryStatistics;
import java.util.List;
class Order { // 订单类
private String name; // 商品名称
private double price; // 商品价格
private int amount; // 商品数量
public Order(String name, double price, int amount) {
this.name = name;
this.price = price;
this.amount = amount;
}
public String getName() {
return this.name;
}
public double getPrice() {
return this.price;
}
public int getAmount() {
return this.amount;
}
}
public class JavaDemo{
public static void main(String[] args) {
List listOrder = new ArrayList<>();
listOrder.add(new Order("小强娃娃", 9.9, 10));
listOrder.add(new Order("林弱充气娃娃", 2987.9, 3));
listOrder.add(new Order("不强牌笔记本电脑", 8987, 8));
listOrder.add(new Order("弱强茶杯", 2.9, 800));
listOrder.add(new Order("阿强牌煎饼", 0.9, 138));
DoubleSummaryStatistics stat = listOrder.stream().filter(
(ele)->ele.getName().contains("强")
).mapToDouble(
(orderObject)->orderObject.getPrice() * orderObject.getAmount()
).summaryStatistics();
System.out.println("购买数量:" + stat.getCount());
System.out.println("购买总价:" + stat.getSum());
System.out.println("平均花费:" + stat.getAverage());
System.out.println("最高花费:" + stat.getMax());
System.out.println("最低花费:" + stat.getMin());
// 输出:
// 购买数量:4
// 购买总价:74439.2
// 平均花费:18609.8
// 最高花费:71896.0
// 最低花费:99.0
System.out.println(stat);
// 输出:
// DoubleSummaryStatistics{count=4, sum=74439.200000, min=99.000000, average=18609.800000, max=71896.000000}
}
}
这些分析操作只是JDK本身提供的支持,但实际中并不这样进行操作,因为如果所有的数据都保存在内存中,而当数据量很大时,就崩掉了。
————————————————————————————————————
————————————————————————————————————