[JavaSE] 第十章 Java集合
10.1 Java 集合框架概述
集合、数组都是多个数据进行存储操作的结构,简称 Java 容器,此时的存储,主要指的时内存层面的存储,不涉及到持久化的存储
- 数组在存储多个数据方面的特点:
- 一旦初始化以后,其长度就确定
- 数据一旦定义好,其元素的类型也就确定了,我们也就只能操作指定数据类型。比如:String[] arr; int[] arr1;
- 数组在存储多个数据方面的缺点:
- 一旦初始化以后,其长度就不可修改
- 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高
- 获取数据中实际元素的个数的需求,数组没有现成的属性或方法可用
- 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足
Java 集合可分为 Collection 和 Map 两种体系
- Collection 接口:单列数据,定义了存取一组对象的方法的集合
- List 接口:存储有序的、可重复的数据
- ArrayList、LinkedList、Vector
- Set 接口:存储无序的,不可重复的数据
- HashSet、LinkedHashSet、TreeSet
- List 接口:存储有序的、可重复的数据
- Map接口:双列集合,用来存储一对(key-value)数据
- HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
10.2 Collection 接口
-
Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法
既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合
-
在 JDK5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;从 JDK5 增加了泛型以后,Java 集合可以记住容器中对象的数据类型。
10.2.1 Collection 接口常见方法
add(Object e)
:将元素 e 添加到集合中
size()
:获取集合中元素的个数
addAll(Collection col)
:将 col 集合中的元素添加到当前的集合中
clear()
:清空集合元素
isEmpty()
:判断当前集合是否为空
contains(Object obj)
:判断当前集合中是否包含 obj
containsAll(Collection clo)
:判断形参 clo 中的所有元素是否都包含在当前集合中
remove(Object obj)
:从当前集合中移除 obj 元素
removeAll(Collection col)
:从当前集合中移除 col 中的所有元素
retainAll(Collection col)
:交集,获取当前集合和col集合的交集,并返回给当前集合
equals(Object obj)
:判断当前集合和形参集合的元素是否相同
hashCode()
:返回当前对象的哈希值
toArray()
:集合 --> 数组
@Test
public void test1() {
Collection list = new ArrayList();
//add(Object e):将元素 e 添加到集合中
list.add("AA");
list.add("BB");
list.add(123);//自动装箱
list.add(new Date());
//size():获取集合中元素的个数
System.out.println(list.size());
//addAll(Collection col):将 col 集合中的元素添加到当前的集合中
Collection list2 = new ArrayList();
list2.add("BBB");
list2.add("EEE");
list.addAll(list2);
System.out.println(list);
//clear():清空集合元素
list2.clear();
//isEmpty():判断当前集合是否为空
System.out.println(list2.isEmpty());
}
@Test
public void test1() {
Collection<Object> list = new ArrayList<>();
list.add(123);
list.add(324);
list.add(new String("Tom"));
list.add(false);
list.add(new Person("Jerry", 20));
Person anna = new Person("Anna", 23);
list.add(anna);
//1.contains(Object obj):判断当前集合中是否包含 obj
System.out.println(list.contains(123));//true
System.out.println(list.contains("Tom"));//true
System.out.println(list.contains(new Person("Jerry", 20)));//true
System.out.println(list.contains(new Person("Anna", 23)));//true
//2.containsAll(Collection clo):判断形参 clo 中的所有元素是否都包含在当前集合中
List<Integer> list1 = Arrays.asList(123, 3241);
System.out.println(list.containsAll(list1));//false
}
@Test
public void test2() {
//3.remove(Object obj):从当前集合中移除 obj 元素
Collection<Object> list = new ArrayList<>();
list.add(123);
list.add(324);
list.add(new String("Tom"));
list.add(false);
list.add(new Person("Jerry", 20));
Person anna = new Person("Anna", 23);
list.add(anna);
System.out.println(list);
list.remove(123);
System.out.println(list);
list.remove(new Person("Jerry", 20));
System.out.println(list);
//4.removeAll(Collection col):从当前集合中移除 col 中的所有元素
Collection<Integer> col = Arrays.asList(123, 324);
list.removeAll(col);
System.out.println(list);
}
@Test
public void test3() {
Collection<Object> col = new ArrayList<>();
col.add(123);
col.add(456);
col.add(new Person("Jerry", 20));
col.add(new String("Tom"));
col.add(false);
//5.retainAll(Collection col):交集,获取当前集合和col集合的交集,并返回给当前集合
Collection<Object> col1 = Arrays.asList(123, 456, 789);
System.out.println(col);//[123, 456, Person(name=Jerry, age=20), Tom, false]
//col.retainAll(col1);
System.out.println(col);//[123, 456]
//6.equals(Object obj):判断当前集合和形参集合的元素是否相同
Collection<Object> col2 = new ArrayList<>();
col2.add(123);
col2.add(456);
col2.add(new String("Tom"));
col2.add(new Person("Jerry", 20));
col2.add(false);
//ArrayList有序,所以不相等
System.out.println(col.equals(col2));//false
}
@Test
public void test4() {
Collection<Object> col2 = new ArrayList<>();
col2.add(123);
col2.add(456);
col2.add(new String("Tom"));
col2.add(new Person("Jerry", 20));
col2.add(false);
//7.hashCode():返回当前对象的哈希值
System.out.println(col2.hashCode());
//8.集合 --> 数组:toArray()
Object[] objects = col2.toArray();
System.out.println(Arrays.toString(objects));
//9.数组 --> 集合:Arrays.asList()
List<Object> objects1 = Arrays.asList(objects);
System.out.println(objects1);
List<int[]> ints = Arrays.asList(new int[]{123, 456});
System.out.println(ints);//[[I@50134894]
List<Integer> list = Arrays.asList(new Integer[]{123, 456});
System.out.println(list);//[123, 456]
}
10.3 Iterator 迭代器接口
- Iterator 对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
- Collection 接口实现了 java.lang.Iterable 接口,该接口有一个 iterator() 方法,那么所有实现了 Collection 接口的集合类都有一个 iterator() 方法,用以返回一个实现了 Iterator 接口的对象。
- Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建 Iterator 对象,则必须有一个被迭代的集合
- 集合对象每次调用 iterator() 方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前
- 内部的方法:hasNext() 和 next()
- 内部定义了 remove(),可用在遍历的时候,删除集合中的元素。此方法不同于集合直接调用 remove()
public class IteratorTest {
Collection<Object> list = new ArrayList<>();
{
list.add(123);
list.add(324);
list.add(new String("Tom"));
list.add(false);
list.add(new Person("Jerry", 20));
Person anna = new Person("Anna", 23);
list.add(anna);
}
@Test
public void test1() {
Iterator<Object> iterator = list.iterator();
//hasNext():判断是否还有下一个元素
for (;iterator.hasNext();) {
System.out.println(iterator.next());
}
}
//测试 Iterator 中的 remove()
@Test
public void test2() {
Iterator<Object> iterator = list.iterator();
//hasNext():判断是否还有下一个元素
for (;iterator.hasNext();) {
Object next = iterator.next();
if ("Tom".equals(next)) {
iterator.remove();
}
}
iterator = list.iterator();
for (;iterator.hasNext();) {
System.out.println(iterator.next());
}
}
}
10.4 Collection 子接口一:List
-
鉴于 Java 中数组用来存储数据的局限性,我们通常使用 List 替代数组
-
List 集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引
-
JDK API 中 List 接口的实现类常用的有:
- ArrayList:List 接口的主要实现类;线程不安全的,效率高;底层使用 Object[] elementData 存储
- LinkedList:对于频繁的插入、删除操作,使用此类效率比 ArrayList 高;底层使用双向链表存储
- Vector:作为 List 接口古老实现类;线程安全的,效率低;底层使用 Object[] elementData 存储
10.4.1 ArrayList 的使用
-
ArrayList 的源码分析:
- JDK7 情况下:
ArrayList list = new ArrayList();
底层创建了长度是 10 的 Object[] 数组 elementData 如果添加导致底层 elementData 数组容量不够,则扩容。默认情况下,扩容为原来的容量的 1.5 倍,同时需要将原有数组中的数据复制到新的数组中。- 结论:建议开发中使用带参的构造器
- JDK8 情况下:
ArrayList list = new ArrayList();
底层 Object[] elementData 初始化为 {},并没有创建长度为 10 的数组- 小结:JDK7 中的 ArrayList 对象的创建类似于单例的饿汉式,而 JDK8 中的 ArrayList 的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
- JDK7 情况下:
-
常用方法:
void add(int index, Object ele)
:在 index 位置插入 ele 元素boolean addAll(int index, Collection eles)
:从 index 位置开始将 eles 中的所有元素添加进来Object get(int index)
:获取指定 index 位置的元素int indexOf(Object obj)
:返回 obj 在集合中首次出现的位置int lastIndexOf(Object obj)
:返回 obj 在当前集合中末次出现的位置Object remove(int index)
:移除指定 index 位置的元素,并返回此元素Object set(int index, Object ele)
:设置指定 index 位置的元素为 eleList subList(int fromIndex, int toIndex)
:返回从 fromIndex 到 toIndex 位置的子集合
public class ListTest {
ArrayList<Object> list = new ArrayList<>();
{
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("Tom", 12));
list.add(456);
}
@Test
public void test1() {
System.out.println(list);//[123, 456, AA, Person(name=Tom, age=12), 456]
//void add(int index, Object ele):在index位置插入ele元素
list.add(1, "BB");//[123, BB, 456, AA, Person(name=Tom, age=12), 456]
System.out.println(list);
//boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
List<Object> list1 = Arrays.asList(1, 2, 3);
list.addAll(list1);
System.out.println(list);
System.out.println(list.size());//9
//Object get(int index):获取指定index位置的元素
System.out.println(list.get(0));
}
@Test
public void test2() {
System.out.println(list);//[123, 456, AA, Person(name=Tom, age=12), 456]
//int indexOf(Object obj):返回obj在集合中首次出现的位置
System.out.println(list.indexOf("BB"));//-1
System.out.println(list.indexOf("AA"));//2
//int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
System.out.println(list.lastIndexOf(456));
//Object remove(int index):移除指定index位置的元素,并返回此元素
Object remove = list.remove(0);
System.out.println(remove);//123
System.out.println(list);//[456, AA, Person(name=Tom, age=12), 456]
//Object set(int index, Object ele):设置指定index位置的元素为ele
list.set(1, "CC");
System.out.println(list);//[456, CC, Person(name=Tom, age=12), 456]
//List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
List<Object> objects = list.subList(1, 4);
System.out.println(objects);//[CC, Person(name=Tom, age=12), 456]
}
}
10.4.2 LinkedList 的使用
-
LinkedList 源码分析:
-
LinkedList list = new LinkedList()
内部声明了 Node 类型的 first 和 last 属性,默认值为 null;list.add(123);
将 123 封装到 Node 中,创建了 Node 对象,其中,Node 的定义,体现了 LinkedList 的双向链表的说法:
-
-
对于频繁的插入或删除元素的操作,建议使用 LinkedList 类,效率较高
-
新增方法:
void addFirst(Object obj)
:在集合的首位添加元素void addLast(Object obj)
:在集合末尾添加元素Object getFirst()
:获取集合首位的元素Object getLast()
:获取集合末位的元素Object removeFirst()
:删除集合中首位的元素Object removeLast()
:删除集合中末尾的元素
@Test
public void test4() {
LinkedList<Object> list = new LinkedList<>();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("Tom", 12));
list.add(456);
System.out.println(list);//[123, 456, AA, Person(name=Tom, age=12), 456]
//void addFirst(Object obj):在集合的首位添加元素
list.addFirst(12);
System.out.println(list);//[12, 123, 456, AA, Person(name=Tom, age=12), 456]
//void addLast(Object obj):在集合末尾添加元素
list.addLast("ABC");
System.out.println(list);//[12, 123, 456, AA, Person(name=Tom, age=12), 456, ABC]
//Object getFirst():获取集合首位的元素
Object first = list.getFirst();
System.out.println(first);//12
//Object getLast():获取集合末位的元素
Object last = list.getLast();
System.out.println(last);//ABC
//Object removeFirst():删除集合中首位的元素
list.removeFirst();
System.out.println(list);//[123, 456, AA, Person(name=Tom, age=12), 456, ABC]
//Object removeLast():删除集合中末尾的元素
list.removeLast();
System.out.println(list);//[123, 456, AA, Person(name=Tom, age=12), 456]
}
10.4.3 Vector 的使用
-
Vector的源码分析:Vector() 构造器创建对象时,底层都创建了长度为 10 的数组。
-
在扩容方面,默认扩容为原来的数组长度的 2 倍
-
Vector 是一个古老的集合,JDK1.0 就有了。大多数操作与 ArrayList 相同,区别之处在于 Vector 是线程安全的
10.5 Collection 子接口二:Set
- Set 接口中没有额外定义新的方法,使用的都是 Collection 中声明过的方法
- 向 Set 中添加的数据,其所在的类一定要重写 hashCode() 和 equals()
- 重写的 hashCode() 和 equals() 尽可能保持一致性:相等的对象必须具有相等的散列码
- 重写两个方法的小技巧:对象中用作 equals() 方法比较的 Filed,都应该用来计算 hashCode 值
- Set 接口:存储无序的,不可重复的数据
- HashSet:作为Set接口的主要实现类;线程不安全的;可以存储 null 值
- LinkedHashSet:HashSet 的子类;遍历其内部数据时,可以按照添加的顺序遍历,对于比较频繁的遍历操作,LinkedHashSet 效率高于 HashSet
- TreeSet:可以按照添加对象的指定属性进行排序
- 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的
- 不可重复性:保证添加的元素按照 equals() 判断时,不能返回 true。即:相同的元素只有一个
- HashSet:作为Set接口的主要实现类;线程不安全的;可以存储 null 值
10.5.1 添加元素的过程
-
添加元素的过程:以 HashSet 为例:
-
我们向 HashSet 中添加元素 a,首先调用元素 a 所在类的 hashCode(),计算元素 a 的哈希值,此哈希值接着通过某种算法计算出在 HashSet 底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
- 如果此位置上没有其他元素,则元素 a 添加成功 情况一
- 如果此位置上有其他元素 b(或以链表形式存储的多个元素),则比较元素 a 与元素 b 的hash值:
- 如果 hash 不相同,则元素 a 添加成功 情况二
- 如果 hash 值相同,进而需要调用元素 a 所在类的 equals():
- equals() 返回 true,元素 a 添加失败
- equals() 返回 false,则元素 a 添加成功 情况三
-
对于添加成功的情况二和情况三而言:元素 a 与已经存在指定索引位置上的数据以链表的方式存储
-
JDK7:元素 a 放到数组中,指向原来的元素
-
JDK8:原来的元素放在数组中,指向元素 a
-
HashSet 底层:数组 + 链表的结构
-
10.5.2 HashSet 的使用
-
HashSet 具有以下特点:
- 不能保证元素的排列顺序
- HashSet 不是线程安全的
- 集合元素可以是 null
-
HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。
-
对于存放在Set容器中的对象,对应的类一定要重写 equals() 和 hashCode(Object obj)方法,以实现对象相等规则。即:相等的对象必须具有相等的散列码
-
底层也是数组,初始容量为16,当如果使用率超过0.75,(16*0.75=12)就会扩大容量为原来的 2倍
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Data
@EqualsAndHashCode
public class User {
private String name;
private int age;
}
@Test
public void test1() {
Set<Object> set = new HashSet<>();
set.add(456);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new User("Tom", 12));
set.add(new User("Tom", 12));
set.add(129);
set.add(123);
System.out.println(set);//[AA, CC, User(name=Tom, age=12), 129, 456, 123]
}
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class Person {
int id;
String name;
}
@Test
public void test3() {
HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");
set.add(p1);
set.add(p2);
p1.name = "CC";//将 Person(id=1001, name=AA) 变成 Person(id=1001, name=CC)
set.remove(p1);//按照 Person(id=1001, name=CC) 的哈希值进行删除
System.out.println(set);//[Person(id=1002, name=BB), Person(id=1001, name=CC)]
set.add(new Person(1001,"CC"));//按 Person(id=1001, name=CC) 的哈希值进行添加,若此位置没有元素,测添加成功
System.out.println(set);//[Person(id=1002, name=BB), Person(id=1001, name=CC), Person(id=1001, name=CC)]
set.add(new Person(1001,"AA"));//按 Person(id=1001, name=AA) 的哈希值进行添加,此位置有值,比较hash值是否相同,相同则再比较equals(),不相等则添加成功
System.out.println(set);//[Person(id=1002, name=BB), Person(id=1001, name=CC), Person(id=1001, name=CC), Person(id=1001, name=AA)]
}
10.5.3 LinkedHashSet 的使用
-
LinkedHashSet 作为 HashSet 的子类,在添加的数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据
-
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的
-
LinkedHashSet 插入性能略低于 HashSet,但在遍历 Set 中的全部元素时有很好的性能
@Test
public void test2() {
Set<Object> set = new LinkedHashSet<>();
set.add(456);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new User("Tom", 12));
set.add(new User("Tom", 12));
set.add(129);
System.out.println(set);
for (Object obj: set) {
System.out.println(obj);
}
}
10.5.4 TreeSet 的使用
-
TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态
-
TreeSet 底层使用红黑树结构存储数据
-
新增的方法如下: (了解)
Comparator comparator()
Object first()
Object last()
Object lower(Object e)
Object higher(Object e)
SortedSet subSet(fromElement, toElement)
SortedSet headSet(toElement)
SortedSet tailSet(fromElement)
-
TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序。
-
特点:有序,查询速度比 List 快
自然排序
-
向 TreeSet 中添加元素时,只有第一个元素无须比较 compareTo() 方法,后面添加的所有元素都会调用 compareTo() 方法进行比较
-
因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同
一个类的对象
-
对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较返回值
-
当需要把一个对象放入 TreeSet 中,重写该对象对应的 equals() 方法时,应保证该方法与 compareTo(Object obj) 方法有一致的结果:如果两个对象通过 equals() 方法比较返回 true,则通过 compareTo(Object obj) 方法比较应返回 0。否则,让人难以理解
//按照姓名从小到大排列,如果出现姓名相同的值,则按照年龄从小到大排列
@Override
public int compareTo(User o) {
if (o.name.equals(name)) {
return age - o.age;
}
return -o.name.compareTo(name);
}
@Test
public void test1() {
TreeSet<Object> set = new TreeSet<>();
set.add(new User("Tom", 12));
set.add(new User("Jerry", 19));
set.add(new User("Anna", 11));
set.add(new User("Marry", 16));
set.add(new User("Tomas", 11));
set.add(new User("Tomas", 14));
for (Object obj: set) {
System.out.println(obj);
}
}
定制排序
-
TreeSet 的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没有实现Comparable 接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator 接口来实现。需要重写 compare(T o1,T o2)方法
-
利用 int compare(T o1,T o2) 方法,比较 o1 和 o2 的大小:如果方法返回正整数,则表示 o1 大于o2;如果返回 0,表示相等;返回负整数,表示 o1 小于 o2。
-
要实现定制排序,需要将实现 Comparator 接口的实例作为形参传递给 TreeSet 的构造器。
-
此时,仍然只能向 TreeSet 中添加类型相同的对象。否则发生 ClassCastException 异常。
-
使用定制排序判断两个元素相等的标准是:通过 Comparator 比较两个元素返回了 0
@Test
public void test2() {
Comparator<User> comparator = new Comparator<User>() {
//按照年龄从小到大排列
@Override
public int compare(User o1, User o2) {
return o1.getAge() - o2.getAge();
}
};
TreeSet<User> set = new TreeSet<User>(comparator);
set.add(new User("Tom", 12));
set.add(new User("Jerry", 19));
set.add(new User("Anna", 11));
set.add(new User("Marry", 16));
set.add(new User("Tomas", 11));
set.add(new User("Tomas", 14));
for (User user: set) {
System.out.println(user);
}
}
10.6 Map 接口
-
Map 接口:双列集合,用来存储一对(key-value)数据
- HashMap:作为 Map 的主要实现类;线程不安全的,效率高;存储 null 的 key 和 value
- LinkedHashMap:保证在遍历 map 元素时,可以按照添加的顺序实现遍历
- 原因:在原来的 HashMap 底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于 HashMap
- LinkedHashMap:保证在遍历 map 元素时,可以按照添加的顺序实现遍历
- TreeMap:保证按照添加的 key-value 对进行排序,实现排序遍历。此时考虑 key 的自然排序或定制排序。底层使用红黑树结构存储
- Hashtable:作为古老的实现类;线程安全的,效率低;不能存储 null 的 key 和 value
- Properties:常用来处理配置文件。key 和 value 都是 String 类型
- HashMap:作为 Map 的主要实现类;线程不安全的,效率高;存储 null 的 key 和 value
-
HashMap 的底层:
- 数组 + 链表(JDK8前)
- 数组 + 链表 + 红黑树(JDK8)
-
Map结构的理解:
- Map 中的 key:无序的、不可重复的、使用 Set 存储所有的 Key ---> key 所在的类要重写 equals() 和 hashCode()
- Map 的中的 value:无序的,可重复的,使用 Collection 存储所有的 value ---> value 所在的类要重写 equals()
- 一个键值对:key-value 构成了一个 Entry 对象
- Map 中的 entry:无序的,不可重复的,使用 Set 存储所有的 entry
10.6.1 Map 接口常用方法
-
添加、删除、修改操作:
Object put(Object key,Object value)
:将指定 key-value 添加到(或修改)当前 map 对象中void putAll(Map m)
:将 m 中的所有 key-value 对存放到当前 map 中Object remove(Object key)
:移除指定 key 的 key-value 对,并返回 valuevoid clear()
:清空当前 map 中的所有数据
-
元素查询的操作:
Object get(Object key)
:获取指定 key 对应的 valueboolean containsKey(Object key)
:是否包含指定的 keyboolean containsValue(Object value)
:是否包含指定的 valueint size()
:返回 map 中 key-value 对的个数boolean isEmpty()
:判断当前 map 是否为空boolean equals(Object obj)
:判断当前 map 和参数对象 obj 是否相等
-
元视图操作的方法:
-
Set keySet()
:返回所有 key 构成的 Set 集合 -
Collection values()
:返回所有 value 构成的 Collection 集合 -
Set entrySet()
:返回所有 key-value 对构成的 Set 集合
-
public class MapMethodTest {
HashMap<Object, Object> map = new HashMap<>();
{
//添加
map.put("AA", 123);
map.put(45, 123);
map.put("BB", 123);
}
@Test
public void test1() {
//修改
map.put("AA", 223);
System.out.println(map);//{AA=223, BB=123, 45=123}
//Object remove(Object key):移除指定key的key-value对,并返回value
map.remove("AA");
System.out.println(map);//{BB=123, 45=123}
map.clear();
System.out.println(map);//{}
System.out.println(map.size());//0
}
@Test
public void test2() {
System.out.println(map);
//Object get(Object key):获取指定key对应的value
System.out.println(map.get("AA"));//123
//boolean containsKey(Object key):是否包含指定的key
System.out.println(map.containsKey("AA"));//true
//boolean containsValue(Object value):是否包含指定的value
System.out.println(map.containsValue(123));
map.clear();
System.out.println(map.isEmpty());//true
}
@Test
public void test3() {
//Set keySet():返回所有key构成的Set集合
Set<Object> objects = map.keySet();
objects.forEach(System.out::println);
//Collection values():返回所有value构成的Collection集合
Collection<Object> values = map.values();
Iterator<Object> iterator = values.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
//Set entrySet():返回所有key-value对构成的Set集合
Set<Map.Entry<Object, Object>> entries = map.entrySet();
for (Map.Entry<Object, Object> entrie: entries) {
System.out.println(entrie);
System.out.print(entrie.getKey());
System.out.print(": ");
System.out.println(entrie.getValue());
}
}
}
10.6.2 HashMap 的使用
-
HashMap 的底层实现原理
-
以 JDK7 为例说明:
HashMap map = new HashMap();
,在实例化以后,底层创建了长度是 16 的一维数组Entry[] table
map.put(key1, value1);
,首先,调用 key1 所在类的 hashCode() 计算 key1 哈希值,此哈希值经过某种算法计算以后,得到在 Entry 数组中的存放位置- 如果此位置上的数据为空,此时的 key1-value1 添加成功 情况一
- 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较 key1 和已经存在的一个或多个数据的哈希值:
- 如果 key1 的哈希值与已经存在的数据的哈希值都不相同,此时 key1-value1 添加成功 情况二
- 如果 key1 的哈希值与已经存在的数据的哈希值相同,调用 key1 所在类的 equals(),比较:
- 如果 equals() 返回 false:此时 key1-value1 添加成功 情况三
- 如果 equals() 返回 true:使用 value1 替换相同 key 的 value 值
-
关于情况二和情况三:此时的 key1-value1 和原来的数据以链表的方式存储
-
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的两倍,并将原有的数据复制过来
-
JKD8 相较于 JDK7 在底层实现方面的不同:
new HashMap()
:底层没有创建一个长度为 16 的 Entry 数组- JDK8 底层的数组是 Node[],而非 Entry[]
- 首次调用 put() 时,底层创建长度为 16 的数组
- JDK7 底层结构:数组+链表。JDK8 底层结构:数组+链表+红黑树
- 形成链表时,七上八下(JDK7:新的元素指向旧的元素。JDK8:旧的元素指向新的元素)
- 当数组的某一个索引位置上的元素以链表的形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的数据改为使用红黑树存储
-
-
DEFAULT_INITIAL_CAPACITY: HashMap的默认容量,16
-
MAXIMUM_CAPACITY: HashMap的最大支持容量,2^30
-
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子 0.75
-
threshold:扩容的临界值=容量*填充因子:16 * 0.75 = 12
-
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树
-
MIN_TREEIFY_CAPACITY:桶中的 Node 被树化时最小的 hash 表容量:64
@Test
public void test1() {
HashMap<Object, Object> map = new HashMap<>();
map.put(null, 12);
System.out.println(map);
Hashtable<Object, Object> hashtable = new Hashtable<>();
hashtable.put(null, 12);
System.out.println(hashtable);//NullPointerException
}
@Test
public void test2() {
HashMap<Object, Object> map = new HashMap<>();
map = new LinkedHashMap<>();
map.put(123, "AA");
map.put(345, "BB");
map.put(12, "CC");
System.out.println(map);
}
10.6.3 LinkedHashMap 的使用
LinkedHashMap 底层使用的结构与 HashMap 相同,因为 LinkedHashMap 继承于 HashMap,区别就在于:LinkedHashMap 内部提供了 Entry,替换 HashMap 中的 Node
HashMap 中的内部类:Node
LinkedHashMap 中的内部类:Entry
10.6.4 TreeMap 的使用
-
TreeMap 存储 Key-Value 对时,需要根据 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态。
-
TreeSet底层使用红黑树结构存储数据
-
TreeMap 的 Key 的排序:
-
自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
-
定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
-
-
TreeMap判断两个 key 相等的标准:两个 key 通过 compareTo() 方法或者 compare() 方法返回0
@Test
public void test1() {
TreeMap<User, Object> treeMap = new TreeMap<>();
User u1 = new User("Tom", 23);
User u2 = new User("Jerry", 13);
User u3 = new User("Jack", 26);
User u4 = new User("Rose", 15);
treeMap.put(u1, 90);
treeMap.put(u2, 70);
treeMap.put(u3, 20);
treeMap.put(u4, 40);
Set<Map.Entry<User, Object>> entries = treeMap.entrySet();
for (Map.Entry<User, Object> en: entries) {
System.out.println(en);
}
}
//定制排序
@Test
public void test2() {
TreeMap<User, Integer> treeMap = new TreeMap<>(new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User) {
User u1 = (User) o1;
User u2 = (User) o2;
return Integer.compare(u1.getAge(), u2.getAge());
}
throw new RuntimeException("输入的类型不匹配");
}
});
User u1 = new User("Tom", 23);
User u2 = new User("Jerry", 13);
User u3 = new User("Jack", 26);
User u4 = new User("Rose", 15);
treeMap.put(u1, 90);
treeMap.put(u2, 70);
treeMap.put(u3, 20);
treeMap.put(u4, 40);
Set<Map.Entry<User, Integer>> entries = treeMap.entrySet();
for (Map.Entry<User, Integer> en: entries) {
System.out.println(en);
}
}
10.6.5 Properties 的使用
- Properties 类是 Hashtable 的子类,该对象用于处理属性文件
- 由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
- 存取数据时,建议使用
setProperty(String key,String value)
方法和getProperty(String key)
方法
public class PropertiesTest {
public static void main(String[] args) {
Properties properties = new Properties();
FileInputStream stream = null;
try {
stream = new FileInputStream("jdbc.properties");
properties.load(stream);//加载流对应的文件
} catch (IOException e) {
e.printStackTrace();
}
String name = properties.getProperty("user");
String password = properties.getProperty("password");
System.out.println(name + ": " + password);
//关闭流
try {
assert stream != null;
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
10.7 Collections 工具类
- Collections 是一个操作 Set、List 和 Map 等集合的工具类
- 排序操作(均为 stati 方法):
reverse(List)
:反转 List 中元素的顺序shuffle(List)
:对 List 集合元素进行随机排序sort(List)
:根据元素的自然顺序对指定 List 集合元素按升序排序sort(List,Comparator)
:根据指定的 Comparator 产生的顺序对 List 集合元素进行排序swap(List,int, int)
:将指定 list 集合中的 i 处元素和 j 处元素进行交换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 对象的所有旧值
- Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
public class CollectionsTest {
ArrayList<Integer> list = new ArrayList<>();
{
list.add(123);
list.add(456);
list.add(456);
list.add(-23);
list.add(78);
list.add(78);
list.add(78);
list.add(93);
}
@Test
public void test1() {
System.out.println(list);
//reverse(List):反转 List 中元素的顺序
Collections.reverse(list);
System.out.println(list);
//shuffle(List):对 List 集合元素进行随机排序
Collections.shuffle(list);
System.out.println(list);
//sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
Collections.sort(list);
System.out.println(list);
//swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Collections.swap(list, 0, 1);
System.out.println(list);
//int frequency(Collection,Object):返回指定集合中指定元素的出现次数
int frequency = Collections.frequency(list, 78);
System.out.println(frequency);
}
@Test
public void test2() {
//void copy(List dest,List src):将src中的内容复制到dest中
List<Object> list1 = Arrays.asList(new Object[list.size()]);
Collections.copy(list1, list);
System.out.println(list1);
//boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
Collections.replaceAll(list1, 78, 18);
System.out.println(list1);
}
@Test
public void test3() {
Collections.synchronizedList(list);
System.out.println(list);
}
}
);
//sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
Collections.sort(list);
System.out.println(list);
//swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Collections.swap(list, 0, 1);
System.out.println(list);
//int frequency(Collection,Object):返回指定集合中指定元素的出现次数
int frequency = Collections.frequency(list, 78);
System.out.println(frequency);
}
@Test
public void test2() {
//void copy(List dest,List src):将src中的内容复制到dest中
List<Object> list1 = Arrays.asList(new Object[list.size()]);
Collections.copy(list1, list);
System.out.println(list1);
//boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
Collections.replaceAll(list1, 78, 18);
System.out.println(list1);
}
@Test
public void test3() {
Collections.synchronizedList(list);
System.out.println(list);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!