集合
集合类是用来存放某类对象的。集合类有一个共同特点,就是它们只容纳对象(实际上是对象名,即指向地址的指针)。这一点和数组不同,数组可以容纳对象和简单数据。如果在集合类中既想使用简单数据类型,又想利用集合类的灵活性,就可以把简单数据类型数据变成该数据类型类的对象,然后放入集合中处理,但这样执行效率会降低。比如说基本数据类型的封装类,int类型对应的封装类为Integer, long类型对应的封装类为Long类型
集合类容纳的对象都是Object类的实例(所有对象继承于Object类),一旦把一个对象置入集合类中,它的类信息将丢失,也就是说,集合类中容纳的都是指向Object类对象的指针。这样的设计是为了使集合类具有通用性,因为Object类是所有类的祖先,所以可以在这些集合中存放任何类而不受限制。当然这也带来了不便,这令使用集合成员之前必须对它重新造型。
Collection接口
Collection是一个基本的集合接口,Collection中可以容纳一组集合元素(Element),Collection有两个重要的子接口List和Set。List表达一个有序的集合,List中的每个元素都有索引,使用此接口能够准确的控制每个元素插入的位置。用户也能够使用索引来访问List中的元素,List类似于Java的数组。Set接口的特点是不能包含重复的元素。对Set中任意的两个元素element1和element2都有element1.equals(element2)==false。另外,Set最多有一个null元素。
Collection继承树
Collection的Api文档
boolean add(E e) 该方法用于向集合里添加一个元素。如果集合对象被添加操作改变了,则返回 true 。
boolean addAll(Collection<? extends E> c)该方法把集合 c 里的所有元素添加到指定集合里。 如果集合对象被添加操作改变了 , 则返回 true 。
void clear() 清除集合里的所有元素,将集合长度变为 0 。
boolean contains(Object o) 返回集合里是否包含指定元素 。
boolean containsAll(Collection<?> c) 返回集合里是否包含集合 c 里的所有元素。
boolean equals(Object o) 将指定的对象与此集合进行比较,以进行相等性
int hashCode() 返回此集合的哈希代码值。
boolean isEmpty() 返回集合是否为空。当集合长度为 0 时返回 true ,否则返回 false 。
Iterator<E> iterator() 返回此集合中的元素的迭代器。
default Stream<E> parallelStream() 返回一个可能并行 Stream与集合的来源。
boolean remove(Object o) 删除集合中的指定元素。当集合中包含了一个或多个元素时,该方法只删除第一个符合条件的元素 ,该方法将返回true。
boolean removeAll(Collection<?> c) 从集合中删除集合 c 里包含的所有元素( 相当于用调用该方法
的集合减集合 c) ,如果删除了一个或一个以上的元素,则该方法返回 true 。
default boolean removeIf(Predicate<? super E> filter) 删除满足给定谓词的这个集合的所有元素。
boolean retainAll(Collection<?> c) 从集合中删除集合 c 里包含的所有元素( 相当于用调用该方法的集合减集合 c) ,如果删除了一个或一个以上的元素,则该方法返回 true 。
int size() 返回此集合中的元素的数目。
default Spliterator<E> spliterator() 创建此集合中的元素的 Spliterator。
default Stream<E> stream() 返回一个序列 Stream与集合的来源。
Object[] toArray() 返回包含此集合中所有元素的数组。
< T > T[] toArray(T[] a) 返回包含此集合中所有元素的数组;返回数组的运行时类型是指定的数组的运行时类型。
实例代码
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
/**
* @Author: 随风飘的云
* @Description:
* @Date: 2022/3/18 0:26
* @Modified By:
*/
public class CollectionTest
{
public static void main(String[] args){
Collection c = new ArrayList();
// 添加元素
c.add("孙悟空");
// 虽然集合里不能放基本类型的值,但Java支持自动装箱
c.add(6);
System.out.println("c集合的元素个数为:" + c.size()); // 输出2
// 删除指定元素
c.remove(6);
System.out.println("c集合的元素个数为:" + c.size()); // 输出1
// 判断是否包含指定字符串
System.out.println("c集合的是否包含\"孙悟空\"字符串:"
+ c.contains("孙悟空")); // 输出true
c.add("轻量级Java EE企业应用实战");
System.out.println("c集合的元素:" + c);
Collection books = new HashSet();
books.add("轻量级Java EE企业应用实战");
books.add("疯狂Java讲义");
System.out.println("c集合是否完全包含books集合?"
+ c.containsAll(books)); // 输出false
// 用c集合减去books集合里的元素
c.removeAll(books);
System.out.println("c集合的元素:" + c);
// 删除c集合里所有元素
c.clear();
System.out.println("c集合的元素:" + c);
// 控制books集合里只剩下c集合里也包含的元素
books.retainAll(c);
System.out.println("books集合的元素:" + books);
}
}
结果
Map接口
Map没有继承Collection接口,与Collection是并列关系。Map提供键(key)到值(value)的映射。一个Map中不能包含相同的键,每个键只能映射一个值。
Map集合继承树
Map的Api接口
void clear() 从这个映射中移除所有的映射(可选操作)。
default V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 试图计算出指定键和当前的映射值的映射(或 null如果没有当前映射)。
default V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) 如果指定的键是不是已经与价值相关的(或映射到 null),尝试使用给定的映射功能,进入到这个Map除非 null计算其价值。
default V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V>remappingFunction) 如果指定键的值是存在和非空的,尝试计算一个新的映射,给出了键和它当前的映射值。
boolean containsKey(Object key) 返回 true如果这Map包含一个指定的键映射。
boolean containsValue(Object value) 返回 true如果映射到指定的值的一个或多个键。
Set<Map.Entry<K,V>> entrySet() 返回一个 Set视图的映射包含在这个Map。
boolean equals(Object o) 将指定的对象与此映射的相等性进行比较。
default void forEach(BiConsumer<? super K,? super V> action) 在该映射中的每个条目执行给定的操作,直到所有的条目被处理或操作抛出异常。
V get(Object key) 返回指定的键映射的值,或 null如果这个Map不包含的键映射。
default V getOrDefault(Object key, V defaultValue) 返回指定的键映射的值,或 defaultValue如果这个Map不包含的键映射。
int hashCode() 返回此映射的哈希代码值。
boolean isEmpty() 返回 true如果这个Map不包含键值的映射。
Set<K> keySet() 返回一个 Set的关键视图包含在这个Map。
default V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) 如果指定的键已与值相关联的值或与空值相关联的,则将其与给定的非空值关联。
V put(K key, V value) 将指定的值与此映射中的指定键关联(可选操作)。
void putAll(Map<? extends K,? extends V> m) 从指定的映射到这个Map(可选操作)复制所有的映射。
default V putIfAbsent(K key, V value) 如果指定的键是不是已经与价值相关的(或映射到 null)将其与给定的值并返回 null,否则返回当前值。
V remove(Object key) 如果存在(可选操作),则从该Map中移除一个键的映射。
default boolean remove(Object key, Object value) 仅当它当前映射到指定的值时,为指定的键移除条目。
default V replace(K key, V value) 仅当它当前映射到某一值时,替换指定的键的条目。
default boolean replace(K key, V oldValue, V newValue) 仅当当前映射到指定的值时,替换指定的键的条目。
default void replaceAll(BiFunction<? super K,? super V,? extends V> function) 将每个条目的值替换为在该项上调用给定函数的结果,直到所有的条目都被处理或函数抛出异常。
int size() 返回这个映射中的键值映射的数目。
Collection<V> values() 返回一个 Collection视图的值包含在这个Map。
集合的遍历
使用Lambda表达式遍历
Java 8 为Iterable 接口新增了 一个 forEach(Consumer action)默认方法 , 该方法所需参数的类型是一个函数式接口,而Iterable 接口是 Collection 接口的父接口 ,因此 Collection 集合也可直接调用该方法。当程序调用 Iterable 的 forEach(Consumer action)遍历集合元素 时, 程序会依次将集合元素传给
Consumer 的 accept(T t)方法(该接口中唯一 的抽 象方法)。正因Consumer 是函数式接口,因此可以使用 Lambda 表达式来遍历集合元素。
实例代码
import java.util.Collection;
import java.util.HashSet;
public class CollectionEach{
public static void main(String[] args){
// 创建一个集合
Collection books = new HashSet();
books.add("轻量级Java EE企业应用实战");
books.add("疯狂Java讲义");
books.add("疯狂Android讲义");
// 调用forEach()方法遍历集合
books.forEach(obj -> System.out.println("迭代集合元素:" + obj));
}
}
结果:
使用java8增强的Iterator遍历集合元素
Iterator 接口也是 Java 集合框架的成员,Iterator 则主要用于遍历( 即迭代访 问 ) Collection集合中的元素, Iterator 对象也被称为迭代器。
Iterator的API文档
boolean hasNext(): 如果被迭代的集合元素还没有被遍历完 ,则返回 true 。
Object next(): 返回集合里的 下一个元素。
void remove(): 删除集合里上一次next方法返回的元素。
void forEachRemaining(Consumer action) ,这是 Java 8 为Iterator新增的默认方法, 该方法可使用Lambda 表达式来遍历集合元素。
实例代码
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
public class IteratorTest {
public static void main(String[] args) {
// 创建集合、添加元素的代码与前一个程序相同
Collection books = new HashSet();
books.add("轻量级Java EE企业应用实战");
books.add("疯狂Java讲义");
books.add("疯狂Android讲义");
// 获取books集合对应的迭代器
Iterator it = books.iterator();
while(it.hasNext())
{
// it.next()方法返回的数据类型是Object类型,因此需要强制类型转换
String book = (String)it.next();
System.out.println(book);
if (book.equals("疯狂Java讲义"))
{
// 从集合中删除上一次next方法返回的元素
it.remove();
}
// 对book变量赋值,不会改变集合元素本身
book = "测试字符串"; //①
}
System.out.println(books);
}
}
结果:
当使用
Iterator 迭代访问 Collection 集合元素 时, Collection 集合里 的元素不能被改变 ,只有通过Iterator 的 remove()方法删 除 上 一 次 next()方法返回 的集合元素才可以;否则将会发生异常。
实例代码
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
public class IteratorErrorTest {
public static void main(String[] args)
{
// 创建集合、添加元素的代码与前一个程序相同
Collection books = new HashSet();
books.add("a");
books.add("c");
books.add("b");
System.out.println(books);
System.out.println();
// 获取books集合对应的迭代器
Iterator it = books.iterator();
while(it.hasNext())
{
String book = (String)it.next();
if (book.equals("a"))
{
// 使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常
books.remove(book);
System.out.println(book.hashCode());
}
}
}
}
结果
使用Lambda表达式遍历Iterator
实例代码
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
public class IteratorEach {
public static void main(String[] args) {
// 创建集合、添加元素的代码与前一个程序相同
Collection books = new HashSet();
books.add("我");
books.add("你");
books.add("他");
// 获取books集合对应的迭代器
Iterator it = books.iterator();
// 使用Lambda表达式(目标类型是Comsumer)来遍历集合元素
it.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj));
}
}
结果
Stream操作集合
java8新增了Stream, IntStream, LongStream, DoubleStream等流式Api,其中Stream是通用的流式APi,而其他的分别代表为int,long,double的流。
Stream的方法分类
1、中间方法: 中间操作允许流保持打开状态,并允许直接调用后续方法 。 2、末端方法: 末端方法是对流的最终操作 。 当对某个 Stream 执行末端方法后,该流将被"消耗"且不再可用
Stream的方法特征
1、状态的方法: 这种方法会给流增加一些新的属性,比如元素的唯一性、元素的最大数量、保证元素以排序的方式被处理等 。 有状态的方法往往需要更大的性能开销 。 2、短路方法 : 短路方法可以尽早结束对流的操作,不必检查所有的元素 。
Stream的Api方法
Stram的中间APi方法
filter(Predicate predicate): 过滤 Stream 中所有不符合 predicate 的元素 。
mapToXxx(ToXxxFunction m叩per): 使用 ToXxxFunction 对流中的元素执行一对一 的转换,该方
法返回的新流中包含了 ToXxxFunction 转换生成的所有元素 。
peek(Consumer action): 依次对每个元素执行一些操作,该方法返回的流与原有流包含相同的元素 。 该方法主要用于调试。
distinct(): 该方法用于排序流中所有重复的元素(判断元素重复的标准是使用 equals() 比较返回true) 。 这是一个有状态的方法 。
sorted(): 该方法用于保证流中的元素在后续的访问中处于有序状态 。 这是一个有状态的方法 。
limit(long maxSize): 该方法用于保证对该流的后续访
Stream的末端API方法
forEach(Consumer action): 遍历流中所有元素,对每个元素执行 action 。
toArray(): 将流中所有元素转换为一个数组 。
reduce(): 该方法有三个重载的版本,都用于通过某种操作来合并流中的元素 。
min(): 返回流中所有元素的最小值 。
max(): 返回流中所有元素的最大值 。
count(): 返回流中所有元素的数量。
anyMatch(Predicate predicate): 判断流中是否至少包含一个元素符合 Predicate 条件
allMatch(Predicate predicate): 判断流中是否每个元素都符合 Predicate 条件。
noneMatch(predicate predicate): 判断流中是否所有元素都不符合 Predicate 条件。
findFirst(): 返回流中的第一个元素。
findAny(): 返回流中的任意 一个元素。
实例代码
import java.util.Collection;
import java.util.HashSet;
/**
* @author: 随风飘的云
* @date 2022/03/18 22:20
*/
public class CollectionStream {
public static void main(String[] args) {
Collection collection = new HashSet();
collection.add("小红是一个三年级的学生");
collection.add("小红的爸爸是一个教师");
collection.add("语文,数学,英语是小红必须学的科目");
collection.add("小红是个男孩");
System.out.print("测试1:");
System.out.println(
// 设置Stream流
collection.stream()
// 过滤
.filter(count
// 判断Collection中包含有几个小红(4个)
-> ((String) count).contains("小红")).count());
// 输出1(判断语句中包含有语文的句子有多少个)
System.out.print("测试2:");
System.out.println(collection.stream().filter(
count -> ((String) count).contains("语文")).count());
// 输出2(判断语句长度大于10的有多少个)
System.out.print("测试3:");
System.out.println(collection.stream().filter(
str -> ((String) str).length() > 10).count());
//先调用Collection对象的stream()方法将集合转换为Stream
//再调用 Stream的 mapToInt ()方法获取原有的 Stream 对应的 IntStream
// 输出每一个句子的长度
System.out.println();
collection.stream().forEach(System.out::println);
collection.stream().mapToInt(len -> ((String) len).length()).forEach(System.out::println);
}
}
结果:
集合类的好处
使用Java提供的集合类有如下功能:
(1)降低编程难度:在编程中会经常需要链表、向量等集合类,如果自己动手写代码实现这些类,需要花费较多的时间和精力。调用Java中提供的这些接口和类,可以很容易的处理数据。 (2)提升程序的运行速度和质量:Java提供的集合类具有较高的质量,运行时速度也较快。使用这些集合类提供的数据结构,程序员可以从“重复造轮子”中解脱出来,将精力专注于提升程序的质量和性能。 (3)无需再学习新的APl:借助泛型,只要了解了这些类的使用方法,就可以将它们应用到很多数据类型中。如果知道了LinkedList的使用方法,也会知道LinkedList怎么用,则无需为每一种数据类型学习不同的API。 (4)增加代码重用性:也是借助泛型,就算对集合类中的元素类型进行了修改,集合类相关的代码也几乎不用修改。
Set集合
java的Set集合只能存放无序的,不能重复的数据, Set集合与Collection的用法基本类似,可以这么说,Set集合就是Collection(但Set集合不能存放相同的元素,如果使用add添加相同的元素,add会返回false,且添加的元素也没有添加进去)
HashSet类
HashSet 是 Set 接口的典型实现,HashSet 按 Hash算法来存储集合中的元素,因此具有很好的存取和查找性能 。
HashSet具备的特点
1、不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化 。 2、HashSet 不是同步的,如果多个线程同时访问 一个HashSet,假设有两个或者两个以上线程同时 修改了 HashSet 集合时,则必须通过代码来保证其同步 。 3、集合元素值可以是 null 。
HashSet的存储流程
当程序往HashSet集合中添加一个元素时,HashSet 会调用该对象的 hashCode()方法来得到该对象的hashCode 值,然后根据该 hashCode 值决定该对象在 HashSet 中的存储位置 。 如果有两个元素通过 equals()方法比较返回 true,但它们的 hashCode()方法返回值不相等 , HashSet 将会把它们存储在不同的位置,依然可以添加成功 。
HashSet 集合判断两个元素相等的标准是两个对象通过 equals()方法比较相等,并且两个对象的 hashCode()方法返回值也相等。
实例代码
import java.util.HashSet;
/**
* @author: 随风飘的云
* @date 2022/03/18 23:28
*/
class animal{
public boolean equals(Object obj){
return true;
}
}
class Cat{
public int hashCode(){
return 1;
}
}
class Dog{
public boolean equals(Object obj){
return true;
}
public int hashCode(){
return 2;
}
}
public class HashSetTest {
public static void main(String[] args) {
HashSet set = new HashSet();
set.add(new animal());
set.add(new animal());
set.add(new Cat());
set.add(new Cat());
set.add(new Dog());
set.add(new Dog());
set.stream().forEach(System.out::println);
}
}
结果:
结果分析
首先animal类重写的equals方法,该方法永远返回true,Cat类中重写了hasCode方法,它永远返回1,Dog重写了hashCode方法和equals方法,分别返回2和true,程序中一共分别添加了两个animal类,Cat类,Dog类,但是输出的除了只有一个Dog类,其他的都是两个输出的。只有一个解析:生成的两个animal和Cat都被HashSet集合当做两个不同的类了,而生成的两个Dog类只被当做一个类。因此可以得出HashSet集合判断两个不同的类是通过hashCode方法和equals方法同时进行的。
HashSet使用特点
当把一个对象放入 HashSet 中时, 如果需要重写该对象对应类的 equals()方法 ,则也应该重写其 hashCode()方法 。 规则是 : 如果两个对象通过 equals()方法比较返回 true,这两个对象的 hashCode 值也应该相同 。
不重写可能性后果一
如果说没有重写这两个方法,假如有两个对象通过equals方法判断得出返回true,但是他们的hashCode方法返回的hash值是不同的,HashSet集合认定这两个对象是不一样的,把他们存放进去,这就与Set集合的不可重复性相反。
不重写可能性后果二
两个对象的 hashCode()方法返 回 的 hashCode 值相同 , 但它们通过 equals()方法 比较返回 false
时将更麻烦 :因为两个对象的 hashCode 值相同, HashSet 将试图把它们保存在同一个位置 , 但又不行(否则将只剩下一个对象) ,所以实际上会在这个位置用链式结构来保存多个对象 ; 而 HashSet 访问集合元素时也是根据元素的 hashCode 值来快速定位的,如果 HashSet 中两个以上的元素具有相同的 hashCode值,将会导致性能下降 。
重写hashCode基本规则
1、在程序运行过程中,同一个对象多次调用 hashCode()方法应该返回相同的值 。 2、当两个对象通过 equals()方法比较返回true 时,这两个对象的 hashCode()方法应返回相等的值 3、对象中用作 equals()方法比较标准的实例变量,都应该用于计算hashCode 值 。
计算方式
1、把对象内每个有意义的实例变量( 即每个参与 equals()方法比较标准的实例变量) 计算出一个int 类型的 hashCode 值
2、用第 1 步计算出来的多个 hashCode 值组合计算出 一个 hashCode 值返回 。
return f1 . hashCode() + (int)f2;
3、为了避免直接相加产生偶然相等(两个对象的f1,f2实例变量并不相等,但它们的 hashCode 的和恰好相等),可以通过为各实例变量 的 hashCode 值乘以任意一个质数后再相加。
return f1.hashCode()*19 + (int)f2 * 31;
hashCode的内置的实例代码
// 来源于AbstractSet
public int hashCode() {
int h = 0;
Iterator<E> i = iterator();
while (i.hasNext()) {
E obj = i.next();
if (obj != null)
h += obj.hashCode();
}
return h;
}
实例代码
import java.util.HashSet;
import java.util.Iterator;
/**
* @author: 随风飘的云
* @date 2022/03/18 23:53
*/
class R
{
int count;
public R(int count)
{
this.count = count;
}
public String toString()
{
return "R[count:" + count + "]";
}
public boolean equals(Object obj)
{
if(this == obj)
return true;
if (obj != null && obj.getClass() == R.class)
{
R r = (R)obj;
return this.count == r.count;
}
return false;
}
public int hashCode()
{
return this.count;
}
}
public class HashSetTest2 {
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add(new R(5));
hs.add(new R(-3));
hs.add(new R(9));
hs.add(new R(-2));
// 打印HashSet集合,集合元素没有重复
System.out.println(hs);
// 取出第一个元素
Iterator it = hs.iterator();
R first = (R)it.next();
// 为第一个元素的count实例变量赋值
first.count = -3; // ①
// 再次输出HashSet集合,集合元素有重复元素
System.out.println(hs);
// 删除count为-3的R对象
hs.remove(new R(-3)); // ②
// 可以看到被删除了一个R元素
System.out.println(hs);
System.out.println("hs是否包含count为-3的R对象?"
+ hs.contains(new R(-3))); // 输出false
System.out.println("hs是否包含count为-2的R对象?"
+ hs.contains(new R(-2))); // 输出false
}
}
结果:
LinkedHashSet类
HashSet 还有 一个子类 LinkedHashSet(与HashSet一样,不允许相同的对象或数据存储到LinkedHashSet里) , LinkedHashSet 集合也是根据元素的 hashCode 值来决定元素的存储位置,但它同时使用链表维护元素的次序 ,这样使得元素看起来是以插入的顺序保存的 。 也就是说,当遍历 LinkedHashSet 集合里的元素时, LinkedHashSet 将会按元素的添加顺序来访问集合里的元素。
LinkedHashSet 需要维护元素的插入顺序 ,因此性能略低于 HashSet 的性能,但在迭代访问 Set 里的全部元素时将有很好的性能,因为它以链表来维护内部顺序 。
实例代码
import java.util.LinkedHashSet;
/**
* @author: 随风飘的云
* @date 2022/03/19 0:23
*/
public class LinkedHashSetTest {
public static void main(String[] args) {
LinkedHashSet books = new LinkedHashSet();
books.add("哈哈哈");
books.add("我你他");
System.out.println(books);
// 删除
books.remove("哈哈哈");
// 添加
books.add("哈哈哈");
System.out.println(books);
}
}
结果:
TreeSet 类
TreeSet 是 SortedSet 接口的实现类,可以对Set集合中的元素进行排序,是不同步的。TreeSet可以确保集合元素处于排序状态 。
TreeSet的Api方法
Comparator comparator(): 如 果 TreeSet 采用了定制排序,则该方法返回定制排序所使用的Comparator; 如 果 TreeSet 采用了自然排序,则返回 null 。
Object first(): 返回集合中的第一个元素 。
Object last(): 返回集合中的最后一个元素 。
Object lower(Object e) : 返回集合中位于指定元素之前的元素(即小于指定元素的最大元素,参 考元素不需要是 TreeSet 集合里的元素) 。
Object higher(Object e): 返回集合中位于指定元素之后的元素(即大于指定元素的最小元素, 参考元素不需要是 TreeSet 集合里的元素) 。
SortedSet subSet(Object fromElement, Object toElement): 返回此 Set 的子集合,范围从 fromElement (包含〉到 toElement (不包含) 。
SortedSet headSet(Object toElement): 返回此 Set 的子集,由小于 toElement 的元素组成 。
SortedSet tailSet(Object tomElement): 返回此 Set 的子集 ,由大于或等于tomElement 的元素组成 。
实例代码
import java.util.TreeSet;
/**
* @author: 随风飘的云
* @date 2022/03/19 1:05
*/
public class TreeSetTest {
public static void main(String[] args) {
TreeSet nums = new TreeSet();
// 向TreeSet中添加四个Integer对象
nums.add(5);
nums.add(2);
nums.add(10);
nums.add(-9);
// 输出集合元素,看到集合元素已经处于排序状态
System.out.println(nums);
// 输出集合里的第一个元素
System.out.println(nums.first()); // 输出-9
// 输出集合里的最后一个元素
System.out.println(nums.last()); // 输出10
// 返回小于4的子集,不包含4
System.out.println(nums.headSet(4)); // 输出[-9, 2]
// 返回大于5的子集,如果Set中包含5,子集中还包含5
System.out.println(nums.tailSet(5)); // 输出 [5, 10]
// 返回大于等于-3,小于4的子集。
System.out.println(nums.subSet(-3 , 4)); // 输出[2]
}
}
结果:
结果分析:
从上面的程序中可以看出,TreeSet不是根据元素的插入顺序来排序的,而是根据元素的实际大小来排序的。HashSet集合是根据hash算法来决定元素的存储位置不同,而TreeSet是根据红黑树的数据结构来存储集合元素。TreeSet集合支持两种排序规则,自然排序和定制排序,默认情况下TreeSet支持自然排序。
TreeSet的排序之自然排序
TreeSet 会调用集合元素的 compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列, 这种方式就是自然排序。
当一个对象调用该方法与另一个对象进行比较时,例如 obj1.compareTo(obj2) ,如果该方法返回 0 ,则表明这两个对象相等 ;如果该方法返回一个正整数 ,则表 明 obj1 大于obj2;如果该方法返回一个负整数 ,则表明 obj1小于 obj2 。(要想实现自然排序,必须要实现Comparable接口,否则就会引发运行时异常-ClassCastException)
实例代码:
import java.util.TreeSet;
/**
* @author: 随风飘的云
* @date 2022/03/19 1:14
*/
class Pet{
}
public class TreeSetErrTest {
public static void main(String[] args) {
TreeSet set = new TreeSet();
// set.add(new Pet());(都会报错)
set.add(new Dog());
set.add(new Pet());
}
}
结果:
注意:
类在实现 compareTo(Object obj)方法时,都需要将被 比较对象 obj 强制类型转换成相同类型,因为只有相同类的两个实例才会比较大小 。 否则也会报ClassCastException异常
实例代码:
import java.util.TreeSet;
/**
* @author: 随风飘的云
* @date 2022/03/19 1:14
*/
public class TreeSetErrTest {
public static void main(String[] args) {
TreeSet set = new TreeSet();
set.add(new String());
set.add(new int[]{0});
}
}
结果:
TreeSet集合添加元素流程
1、当把一个对象加入 TreeSet 集合中时, TreeSet 调用该对象的 compareTo(Object obj)方法与容器中的其他对象比较大小。 2、根据红黑树结构找到它的存储位置 。 如果两个对象通过 compareTo(Object obj)方法比较相等,新对象将无法添加到 TreeSet 集合中 。
实例代码:
import java.util.TreeSet;
/**
* @author: 随风飘的云
* @date 2022/03/19 1:24
*/
class Z implements Comparable {
int age;
public Z(int age)
{
this.age = age;
}
// 重写equals()方法,总是返回true
public boolean equals(Object obj)
{
return true;
}
// 重写了compareTo(Object obj)方法,总是返回1
public int compareTo(Object obj)
{
return 1;
}
}
public class TreeSetTest2 {
public static void main(String[] args) {
TreeSet set = new TreeSet();
Z z1 = new Z(6);
set.add(z1);
// 第二次添加同一个对象,输出true,表明添加成功
System.out.println(set.add(z1)); //①
// 下面输出set集合,将看到有两个元素
System.out.println(set);
// 修改set集合的第一个元素的age变量
((Z)(set.first())).age = 9;
// 输出set集合的最后一个元素的age变量,将看到也变成了9
System.out.println(((Z)(set.last())).age);
}
}
结果:
注意:
如果两个对象通过 equalsO方法比较返回 true 时,这两个对象通过 compareTo(Object obj)方法比较应返回 0 。
TreeSet的排序之定制排序
实现定制排序,则需要在创建 TreeSet 集合对象时,提供一个 Comparator 对象与该 TreeSet集合关联,由该 Comparator 对象负责集合元素的排序逻辑 。 由于 Comparator 是一个函数式接口,因此可使用 Lambda 表达式来代替 Comparator 对象 。
实例代码:
import java.util.TreeSet;
/**
* @author: 随风飘的云
* @date 2022/03/19 1:31
*/
class M{
int age;
public M(int age) {
this.age = age;
}
public String toString() {
return "M [age:" + age + "]";
}
}
public class TreeSetTest4 {
public static void main(String[] args) {
// 此处Lambda表达式的目标类型是Comparator
// 重写了Comparator中的compare方法。
TreeSet ts = new TreeSet((o1 , o2) ->{
M m1 = (M)o1;
M m2 = (M)o2;
// 根据M对象的age属性来决定大小,age越大,M对象反而越小
return m1.age > m2.age ? -1
: m1.age < m2.age ? 1 : 0;
});
ts.add(new M(5));
ts.add(new M(-3));
ts.add(new M(9));
System.out.println(ts);
}
}
结果:
EnumSet类
EnumSet 是一个专为枚举类设计的集合类, EnumSet 中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建 EnumSet 时显式或隐式地指定。 EnumSet 的集合元素也是有序的,EnumSet 以枚举值在 Enum 类内的定义顺序来决定集合元素的顺序。 EnumSet 在内部以位向量的形式存储,因此占用的内存很小,批量操作的执行速度非常快。EnumSet 集合不允许加入 null 元素,如果EnumSet集合想判断是否包含null元素或者是试图删除null元素都不会报出异常,而且删除null操作会返回false。
EnumSet类提供的API方法
EnumSet allOf(Class elementType):创建一个包含指定枚举类里所有枚举值的 EnumSet 集合 。
EnumSet complementOf(EnumSet s): 创建一个其元素类型与指定 EnumSet 里元素类型相同的
EnumSet 集合,新 Enu日lSet 集合包含原 EnumSet 集合所不包含的、此枚举类剩下的枚举值(即新 EnumSet 集合和原 EnumSet 集合的集合元素加起来就是该枚举类的所有枚举值)。
EnumSet copyOf(Collection c): 使用 一个普通集合来创建 EnumSet 集合 。
EnumSet copyOf(EnumSet s): 创建一个与指定 EnumSet 具有相同元素类型、相同集合元素的EnumSet 集合 。
EnumSet noneOf(Class elementType): 创建一个元素类型为指定枚举类型的空 EnumSet 。
EnumSet of(E first, E... rest): 创建一个包含一个或多个枚举值 的 EnumSet 集合,传入的多个枚举值必须属于同一个枚举类。
EnumSet range(E from, E to): 创建一个包含从 from 枚举值到 to 枚举值范围 内所有枚举值的EnumSet 集合 。
实例代码:
import java.util.EnumSet;
/**
* @author: 随风飘的云
* @date 2022/03/19 1:42
*/
enum TimeClass{
YEAR,
MONTH,
DAY,
HOUR,
MINUTE,
SECOND;
}
public class EnumSetTest {
public static void main(String[] args) {
//创建一个 EnumSet 集合 , 集合元素就是 Season 枚举类的全部枚举值
EnumSet set = EnumSet.allOf(TimeClass.class);
System.out.println(set);
// 创建空集合,指定其集合元素是 Season 类的枚举值
EnumSet set1 = EnumSet.noneOf(TimeClass.class);
System.out.println(set1);
set1.add(TimeClass.YEAR);
set1.add(TimeClass.MONTH);
set1.add(TimeClass.DAY);
System.out.println(set1);
//以指定枚举值创建 EnumSet 集合
EnumSet set2 = EnumSet.of(TimeClass.YEAR, TimeClass.MONTH, TimeClass.DAY);
System.out.println(set2);
// 创建一个包含从 from 枚举值到 to 枚举值范围 内所有枚举值的EnumSet 集合 。
EnumSet set3 = EnumSet.range(TimeClass.YEAR, TimeClass.SECOND);
System.out.println(set3);
// set2+set4 = 枚举类的所有元素
EnumSet set4 = EnumSet.complementOf(set2);
System.out.println(set4);
}
}
结果:
各种Set集合性能分析
HashSet 和 TreeSet 是 Set 的两个典型实现。 HashSet 的性能总是比 TreeSet 好(特别是最常用的添加 、 查询元素等操作) , 因为 TreeSet 需要额外的红黑树算法来维护集合元素的次序 。 只有当需要一个保持排序的 Set 时,才应该使用 TreeSet,否则都应该使用 HashSet 。HashSet 还有一个子类 : LinkedHashSet ,对于普通的插入、删除操作 , LinkedHashSet 比 HashSet要略微慢一点 , 这是由维护链表所带来的额外开销造成的 , 但由于有了链表,遍历 LinkedHashSet 会更快 。
EnumSet 是所有 Set 实现类中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。必须指出的是,Set 的三个实现类 HashSet 、 TreeSet 和 EnumSet 都是线程不安全 的 。如果有多个线程同时访问 一个 Set 集合,并且有超过一个线程修改了该 Set 集合 ,则必须手动保证 Set 集合的同步性。通常可 以通过 Collections 工具类的 syncbronizedSortedSet 方法来 "包装"该 Set 集合。 此操作最好在创建时进行 , 以防止对 Set 集合的意外非同步访问 。
List集合
List 集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引 。 List 集合允许使用重复元素,可以通过索引来访问指定位置的集合元素 。 List 集合默认按元素的添加顺序设置元素的索引。
import java.util.ArrayList;
import java.util.List;
/**
* @author: 随风飘的云
* @date 2022/03/19 10:43
*/
public class ListTest {
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add(6);
list.add(2);
list.add(3);
list.stream().forEach(System.out::println);
}
}
结果:
List接口和Listlterator 接口
List接口API方法
void add(int index, Object element): 将元素 element 插入到 List 集合的 index 处 。
boolean addAll(int index, Collection c): 将集合 c 所包含的所有元素都插入到 List 集合的 index处 。
Object get(int index): 返回 集合 index 索引处的元素。
int indexOf(Object 0): 返回对象 。 在 List 集合中第一次出现的位置索引 。
int lastlndexOf(Object 0): 返回对象 。 在 List 集合中最后一次出现的位置索引 。
Object remove(int index) : 删除并返回 index 索 引处的元素 。
Object set(int index, Object element): 将 index 索引处的元素替换成element对象 ,返回被替换的旧元素 。(不能超出索引范围,不能改变索引长度)
List subList(int fromIndex, int toIndex): 返回从索引 fromlndex (包含)到索引 toIndex (不包含)处所有集合元素组成的子集合 。
void replaceAll(UnaryOperator operator): 根据 operator 指定的计算规则重新设置 List 集合的所有
元素。
void sort(Comparator c): 根据 Comparator 参数对 List 集合的元素排序 。
List 集合可以根据位置索引来访问集合中的元素,因此 List 增加了 一种新的遍历集合元素的方法:使用普通的 for 循环来遍历集合元素 。List 判断两个对象相等的标准是只要通过 equals()方法比较返回 true 即可 。
实例代码:
import java.util.ArrayList;
import java.util.List;
/**
* @author: 随风飘的云
* @date 2022/03/19 11:24
*/
class student{
public boolean equals(Object obj){
return true;
}
}
public class ListTest2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add(new String("我是谁?"));
list.add(new String("你还是那个大爷"));
list.add(new String("我是大爷?"));
list.add(new String("哈哈哈哈"));
// 输出全部
System.out.println(list);
System.out.println();
// 删除第一个元素
list.remove(new student());
System.out.println(list);
System.out.println();
// 再次删除第一个元素
list.remove(new student());
System.out.println(list);
System.out.println();
// 在1的位置添加
list.add(1,new String("我知道我是谁了"));
// 循环输出
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println();
// 两种方式遍历(创建list的时候没有指明泛型,所以只能用Object接收)
for (Object str : list) {
System.out.println(str);
}
System.out.println();
// 删除位置1的数据并返回
System.out.println(list.remove(2));
// 输出第一个这个字符串的位置
System.out.println(list.indexOf(new String("我是大爷?")));
// 输出0到2位置的字符串
System.out.println(list.subList(0,2));
}
}
结果:
List 集合增加了 sort()和 replaceAll()两个常用的默认方法,其中 sort()方法需要一个Comparator 对象来控制元素排序,程序可使用 Lambda 表达式来作为参数:而 replaceAll()方法则需要UnaryOperator 来替换所有集合元素, UnaryOperator 也是一个函数式接口,因此程序也可使用 Lambda表达式作为参数。
实例代码
import java.util.ArrayList;
import java.util.List;
/**
* @author: 随风飘的云
* @date 2022/03/19 11:41
*/
public class ListTest3 {
public static void main(String[] args) {
List books = new ArrayList();
// 向books集合中添加4个元素
books.add(new String("很高兴认识你"));
books.add(new String("你谁啊"));
books.add(new String("我是你爸爸"));
books.add(new String("儿子"));
// 使用目标类型为Comparator的Lambda表达式对List集合排序
books.sort((o1, o2)->((String)o1).length() - ((String)o2).length());
System.out.println(books);
// 使用目标类型为UnaryOperator的Lambda表达式来替换集合中所有元素
// 该Lambda表达式控制使用每个字符串的长度作为新的集合元素
books.replaceAll(ele->((String)ele).length());
System.out.println(books);
}
}
结果:
Listlterator 接口的API方法
List额外提供了一个listIteratorO方法,该方法返回一个Listlterator 对象,ListIterator接口继承了Iterator 接口,提供了专门操作 List 的方法 ,ListIterator 接口在Iterator 接口基础上增加了如下API方法 :
boolean hasPrevious(): 返回该迭代器关联的集合是否还有上一个元素 。
Object previous(): 返回该迭代器的上一个元素。
void add(Object 0): 在指定位置插入一个元素 。
Listlterator接口与Iterator接口对比
1、 Listlterator接口增加了向前迭代的功能。 2、Iterator接口只能向后迭代 3、ListIterator接口可以通过add方法向list集合中添加元素 4、Iterator接口只能删除元素
实例代码:
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/**
* @author: 随风飘的云
* @date 2022/03/19 11:51
*/
public class ListIteratorTest {
public static void main(String[] args) {
String[] books = {
"12", "234",
"3456"
};
List bookList = new ArrayList();
for (int i = 0; i < books.length ; i++ )
{
bookList.add(books[i]);
}
ListIterator lit = bookList.listIterator();
while (lit.hasNext())
{
System.out.println(lit.next());
lit.add("-------分隔符-------");
}
System.out.println("=======下面开始反向迭代=======");
while(lit.hasPrevious())
{
System.out.println(lit.previous());
}
}
}
结果:
ArrayList 和 Vector 实现类
ArrayList 和 Vector 类都是基于数组实现的 List 类,所以 ArrayList 和 Vector 类封装了 一个动态的、允许再分配的 Object[]数组 。 ArrayList 或 Vector 对象使用 initialCapacity 参数来设置该数组的长度, 当向 ArrayList 或 Vector 中添加元素超出了该数组的长度 时,它们的 initialCapacity 会自动增加 。如果向ArrayList或 Vector 集合中添加大量元素时,可使用 ensureCapacity(int minCapacity) 方法 一次性地增加initialCapacity 。这可以减少重分配的次数,从而提高性能 。(ArrayList和Vector最显著的区别是ArrayList是线程不安全的,程序使用ArrayList类时,如果有超过一个线程改变了ArrayList,那就必须手动保证该集合的同步性。Vector类是线程安全的)
1、void ensureCapacity(int minCapacity): 将 ArrayList 或 Vector 集合的Object[]数组长度增加大于或等于 minCapacity 值。 2、 void trimToSize(): 调整 ArrayList 或Vector 集合 的 Object[]数组长度为当前元素 的个数 。 调用该方法可减少 ArrayList 或 Vector集合对象占用的存储空间 。
固定List长度(Arrays工具类)
一个操作数组的工具类: Arrays,该工具类里提供了 asList(Object... a)方法,该方法可以把一个数组或指定个数的对象转换成一个 List 集合,这个 List 集合既不是 ArrayList 实现类的实例,也不是 Vector 实现类的实例,而是Arrays 的内部类 ArrayList 的实例 。Arrays.ArrayList 是 一个固定长度的 List 集合,程序只能遍历访问该集合里的元素,不可增加、删除该集合里的元素 。
实例代码
import java.util.Arrays;
import java.util.List;
/**
* @author: 随风飘的云
* @date 2022/03/19 13:44
*/
public class FixedSizeList {
public static void main(String[] args){
List fixedList = Arrays.asList("小红"
, "小明是小红的同学");
// 获取fixedList的实现类,将输出Arrays$ArrayList
System.out.println(fixedList.getClass());
// 使用方法引用遍历集合元素
fixedList.forEach(System.out::println);
// 试图增加、删除元素都会引发UnsupportedOperationException异常
fixedList.add("老师");
fixedList.remove("小红");
}
}
结果:
Queue集合
Queue 用于模拟队列这种数据结构,队列通常是指"先进先出" (FIFO) 的容器 。 队列的头部保存在队列中存放时间最长的元素 , 队列的尾部保存在队列中存放时间最短的元素 。 新元素插入 (offer ) 到 队列的尾部,访问元素 (poll ) 操作会返回队列头部的元素 。 通常 , 队列不允许随机访问队列中的元素。 Queue集合提供的API方法
void add(Object e): 将指定元素加入此队列的尾部。
Object element() : 获取队列头部的元素 ,但是不删除该元素 。
boolean offer(Object e) : 将指定元素加入此队列的尾部 。 当使用有容量限制的队列时,此方法通常比 add(Object e)方法更好 。
Object peek(): 获取队列头部的元素 , 但是不删除该元素 。 如果此队列为空,则返回 null 。
Object poll(): 获取队列头部的元素 , 并删除该元素。如果此队列为空 , 则返回 null 。
Object remove() : 获取队列头部的元素 , 并删除该元素 。
PriorityQueue 实现类
PriorityQueue 保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素 的大小进行重新排序 。 因此当调用 peek()方法或者 poll()方法取出队列中的元素时,并不是取出最先进入队列的元素 , 而是取出队列中最小的元素。
实例代码:
import java.util.PriorityQueue;
/**
* @author: 随风飘的云
* @date 2022/03/19 14:51
*/
public class PriorityQueueTest {
public static void main(String[] args) {
PriorityQueue<Integer> queue = new PriorityQueue<>();
queue.add(2);
queue.add(1);
queue.add(-1);
queue.add(10);
System.out.println(queue);
System.out.println(queue.poll());
System.out.println(queue.poll());
}
}
结果:
Deque 接口与 ArrayDeque 实现类
Deque接口
Deque接口是 Queue接口的子接口 , 它代表一个双端队列, Deque 接口里定义了 一些双端队列的方法,这些方法允许从两端来操作队列的元素 。
void addFirst(Object e): 将指定元素插入该双端队列的开头。
void addLast(Object e): 将指定元素插入该双端队列的末尾 。
Iterator descendingIterator(): 返回该双端队列对应的迭代器,该法代器将以逆向顺序来迭代队列中的元素
Object getFirst(): 获取但不删除双端队列的第一个元素 。
Object getLast(): 获取但不删除双端队列的最后一个元素 。
boolean offerFirst(Object e): 将指定元素插入该双端队列的开头 。
boolean offerLast(Object e): 将指定元素插入该双端队列的末尾。
Object peekFirst(): 获取但不删除该双端队列的第 一个元素:如果此双端队列为空 , 则返回 null 。
Object peekLast(): 获取但不删除该双端队列的最后 一个元素:如果此双端队列为空,则返回 null 。
Object pollFirst(): 获取并删除该双端队列的第 一个元素;如果此双端队列为空,则返回 null 。
Object pollLast(): 获取并删除该双端队列的最后 一个元素;如果此双端队列为空, 则 返回 null 。
Object pop() (栈方法) : pop 出该双端队列所表示的梭的技顶元素 。 相当于 removeFirst() 。
void push(Object e) (战方法) : 将 一 个元素 push 进该双端队列所表示的拢的技顶 。 相当于addFirst(e) 。
Object removeFirst(): 获取并删除该双端队列的第一个元素。
Object removeFirstOccurrence(Object 0): 删除该双端队列的第 一 次出现的元素 。
Object removeLast(): 获取并删除该双端队列的最后一个元素 。
boolean removeLastOccurrence(Object 0): 删除该双端队列的最后 一 次出现的元素 。
ArrayDeque类
基于数组实现的双端队列,创建 Deque 时同样可指定一个 numElements 参数 , 该参数用于指定 Object[]数组的长度 ;如果不指定 numElements 参数, Deque 底层数组的长度为 16 。
实例代码
import java.util.ArrayDeque;
/**
* @author: 随风飘的云
* @date 2022/03/19 15:15
*/
public class ArrayDequeStack {
public static void main(String[] args) {
ArrayDeque stack = new ArrayDeque();
// 依次将三个元素push入"栈"
stack.push("我要疯狂了。");
stack.push("JVM结构");
stack.push("疯狂成人");
// 输出
System.out.println(stack);
// 访问第一个元素,但并不将其pop出"栈"
System.out.println(stack.peek());
// 测试结果输出
System.out.println(stack);
// pop出第一个元素,
System.out.println(stack.pop());
System.out.println(stack);
}
}
结果:
LinkedList 实现类
LinkedList是一 个List 集合 ,可以根据索引来随机访问集合中的元素 。 除此之外, LinkedList 还实现了 Deque 接口,可以被当成双端队列来使用,因此既可以被当成栈来使用 ,也可以当成队列使用 。
实例代码:
import java.util.LinkedList;
/**
* @author: 随风飘的云
* @date 2022/03/19 15:20
*/
public class LinkedListTest {
public static void main(String[] args) {
LinkedList books = new LinkedList();
// 将字符串元素加入队列的尾部
books.offer("疯狂Java讲义");
// 将一个字符串元素加入栈的顶部
books.push("轻量级Java EE企业应用实战");
// 将字符串元素添加到队列的头部(相当于栈的顶部)
books.offerFirst("疯狂Android讲义");
// 以List的方式(按索引访问的方式)来遍历集合元素
for (int i = 0; i < books.size() ; i++ )
{
System.out.println("遍历中:" + books.get(i));
}
// 访问、并不删除栈顶的元素
System.out.println(books.peekFirst());
// 访问、并不删除队列的最后一个元素
System.out.println(books.peekLast());
// 将栈顶的元素弹出“栈”
System.out.println(books.pop());
// 下面输出将看到队列中第一个元素被删除
System.out.println(books);
// 访问、并删除队列的最后一个元素
System.out.println(books.pollLast());
// 下面输出:[轻量级Java EE企业应用实战]
System.out.println(books);
}
}
结果:
线性表的性能分析
Java 提供的 List 就是一个线性表接口,而 ArrayList 、 LinkedList 又是线性表的两种典型实现 : 基于数组的线性表和基于链的线性表。 Queue 代表了队列, Deque 代表了双端队列(既可作为队列使用, 也可作为找使用) ,接下来对各种实现类的性能进行分析 。
一般来说,由于数组以一块连续内存区来保存所有的数组元素,所以数组在随机访问时性能最好,所有的内部以数组作为底层实现的集合在随机访问时性能都比较好;而内部以链表作为底层实现的集合在执行插入、删除操作时有较好的性能。但总体来说, ArrayList 的性能比 LinkedList 的性能要好,因此大部分时候都应该考虑使用ArrayList 。
使用List集合建议:
1、 如果需要遍历 List 集合元素,对于 ArrayList 、 Vector 集合,应该使用随机访问方法 (get) 来遍历集合元素,这样性能更好;对于 LinkedList 集合,则应该采用法代器 (lterator) 来遍历集合元素 。
2、 如果需要经常执行插入、删除操作来改变包含大量数据 的 List 集合的大小,可考虑使用LinkedList 集合 。 使用 ArrayList 、 Vector 集合可能需要经常重新分配内部数组的大小, 效果可能较差 。
3、 如果有多个线程需要同 时访问 List 集合中的元素,开发者可考虑使用Collections 将集合包装成线程安全的集合 。
Map集合
Map 用于保存具有映射关系的数据,因此Map集合里保存着两组值 ,一组值用于保存 Map 里的 key ,另外一组值用于保存 Map 里的 value , key 和 value 都可以是任何引用类型的数据 。 Map 的 key 不允许重复 ,即同一个 Map 对象的任何两个 key 通过 equals 方法比较总是返回 false 。
Map集合的示例图
Map集合子类或者实现类示例图
Map集合常用的API方法:
void clear(): 删除该 Map 对象中的所有 key - value 对 。
boolean containsKey(Object key): 查询 Map 中 是否包含指定的 key ,如果包含则返回 true 。
boolean containsValue(Object value):查询 Map 中 是否包含一个或多个 value ,如果包含则返回 true .
Set entrySet(): 返回 Map 中包含 的 key-value 对所组成的 Set 集合,每个集合元素都是 Map.Entry(Entry 是 Map 的内部类)对象 。
Object get(Object key): 返 回 指定 key 所对应的 value; 如果此 Map 中不包含该 key ,则返回 null 。
boolean isEmpty():查询该 Map 是否为空(即不包含任何 key-value 对 ) ,如 果为空则返回 true 。
Set keySet(): 返回该 Map 中所有 key 组成的 Set 集合 。
Object put(Object key, Object value): 添加 一个 key-value 对,如果当前 Map 中己有一个与该 key相等的 key-value 对,则新的 key-value 对会覆盖原来的 key-value 对 。
void putAll(Map m): 将指定 Map 中的 key-value 对复制到本 Map 中 。
Object remove(Object key): 删除指定 key 所对应的 key-value 对,返回被删除 key 所关联的 value ,如果该 key 不存在,则返回 null 。
boolean remove(Object key, Object value): 这是 Java 8 新增的方法,删除指定 key 、 value 所对应的 key-value 对 。 如果从该 Map 中成功地删除该 key-value 对,该方法返回 true ,否则返回 false 。
int size(): 返回该 Map 里的 key-value 对的个数 。
Collection values(): 返回该 Map 里所有 value 组成的 Collection 。
示例代码:
import java.util.HashMap;
import java.util.Map;
/**
* @author: 随风飘的云
* @date 2022/03/20 23:06
*/
public class MapTest {
public static void main(String[] args) {
Map map = new HashMap();
//成对放入多个 key-value 对
map.put("你",100);
map.put("我", 1000);
map.put("他",10000);
//多次放入的 key - value 对中 value 可以重复
map.put("A", 100);
//放入重复的 key 时 ,新的value会覆盖原有的 value
//如果新的value覆盖了原有的 value ,该方法返回被覆盖的value
System.out.println(map.put("你", -1));//输出100
// 输出Map集合
System.out.println(map);
System.out.println(map.containsKey("我"));
System.out.println(map.containsValue(100));
for (Object key:map.keySet()) {
System.out.println("Key: "+ key + " map: "+ map.get(key));
}
map.remove("A");
System.out.println(map);
}
}
结果:
Entry内部类:
Map 中包括一个内部类 Entry,该类封装了 一个 key-value 对。 Entry 包含如下三个方法 。
Object getKey(): 返回该 En町里包含的 key 值 。 Object getValue(): 返回该 Entry 里包含的value 值 。 Object setValue(V value): 设置该 Entry 里包含的 value 值,并返回新设置的 value 值 。
Map集合新增的API的方法
1、Object compute(Object key, BiFunction remappingFunction): 该方法使用remappingFunction 根据 原 key-value 对计算一个新 value 。 只要新 value 不为 null,就使用新value 覆盖原 value; 如果原 value 不为 null,但新 value 为 null ,则删除原 key-value对;如果原 value 、新 value 同时为 null , 那么该方法不改变任何 key-value 对,直接返回 null 。
2、Object computeIfAbsent(Object key, Function mappingFunction):如果传给该方法的 key 参数在 Map 中对应的 value 为 null ,则使用 mappingFunction 根据 key计算一个新的结果,如果计算结 果不为 null,则用计算结果覆盖原有的 value 。 如果原 Map 原来不包括该 key,那么该方法可能会添加一组 key-value 对 。
3、Object computeIfPresent(Object key, BiFunction remappingFunction):如果传给该方法的 key 参数 在 Map 中对应的 value 不为 null,该方法将使用 remappingFunction 根据原key、 value 计算一个 新的结果,如果计算结果不为 null,则使用该结果覆盖原来的 value; 如果计算结果为 null,则删除原 key-value 对 。
4、void forEach(BiConsumer action): 该方法是 Java 8 为 Map 新增的一个遍历 key-value 对的方法,通过该方法可以更简洁地遍历 Map 的 key-value 对 。
5、Object getOrDefault(Object key, V defaultValue): 获取指定 key 对应的 value 。 如果该 key 不存在,则返回 defaultValue。
6、Object merge(Object key, Object value, BiFunction remappingFunction): 该方法会先根据 key 参数获取该 Map 中对应的 value 。 如果获取的 value 为 null,则直接用传入的 value 覆盖原有的 value (在这种情况下,可能要添加一组 key-value 对 ) ;如果获取的value 不为 null ,则使用remappingFunction 函数根据原 value 、新 value 计算一个新的结果,并用得到的结果去覆盖原有 的 value 。
7、Object putlfAbsent(Object key, Object value): 该方法会自动检测指定 key 对应的value 是否为 null,如果该 key 对应的 value 为 null,该方法将会用新 value 代替原来的 null 值 。
8、Object replace(Object key, Object value): 将 Map 中指定 key 对应的 value替换成新 value 。与传统put()方法不同的是,该方法不可能添加新的 key-value 对 。 如果尝试替换的 key 在原 Map 中不存在,该方法不会添加 key-value 对,而是返回 null 。
9、boolean replace(K key, V oldValue, V newValue): 将 Map 中指定 key-value对的原 value 替换成新value 。如果在 Map 中找到指定的 key-value 对,则执行替换并返回 true ,否则返回false 。
10、replaceAll(BiFunction function): 该方法使用 BiFunction 对原 key-value 对执行计算,并将计算结果作为该 key-value 对的 value 值 。
Map总体方法
HashMap和HashTable的区别
1、线程安全不同: HashTable是一个线程安全的Map实现类,但是HashMap是线程不安全的实现类。因为线程安全问题,HashMap比HashTable性能高。 2、内部方法不同: HashTable内部的方法基本都经过synchronized 修饰。 例如:
3、存储值不同: HashTable实现类不可以使用null的key值或者是value值,如果在程序中添加null值,将会引发空指针异常(NUllPointerException)。但是HashMap可以使用null值作为key值或者是value值。 4、初始容量不同和每次扩容大小容量不同。 ① 如果创建时不指定容量的大小,那么HashTable默认初始容量大小为11,每次扩充变为原来的2n+1倍。HashMap实现类初始化容量大小为16,每一次扩容变为原来的两倍。② 如果创建时指定了容量大小,HashTable实现类直接使用指定的容量大小,而HashMap会扩充为原来的2的幂次方大小。 6、底层数据结构不同: HashMap实现类,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
LinkedHashMap 实现类
基本原理
LinkedHashMap 使用双向链表来维护 key-value 对的次序(其实只需要考虑 key 的次序) ,该链表负责维护 Map 的迭代顺序,迭代顺序与 key-value 对的插入顺序保持一致。
LinkedHashMap 可以避免对 HashMap 、 Hashtable 里 的 key-value 对进行排序(只要插入 key-value对时保持顺序即可),同时又可避免使用 TreeMap 所增加的成本 。LinkedHashMap 需要维护元素的插入顺序,因此性能略低于 HashMap 的性能;但因为它以链表来维护内部顺序,所以在迭代访问 Map 里的全部元素时将有较好的性能 。
实例代码:
import java.util.LinkedHashMap;
/**
* @author: 随风飘的云
* @describe:
* @date 2022/03/22 22:28
*/
public class LinkedHashMapTest {
public static void main(String[] args) {
LinkedHashMap scores = new LinkedHashMap();
scores.put("语文" , 80);
scores.put("英文" , 82);
scores.put("数学" , 76);
// 调用forEach方法遍历scores里的所有key-value对
scores.forEach((key, value) -> System.out.println(key + "-->" + value));
}
}
结果:
SortedMap 接口和 TreeMap 实现类
基本原理
SortMap接口有一个TreeMap实现类,TreeMap 就是 一个红黑树数据结构 , 每个 key-value 对即作为红黑树的一个节点 。 TreeMap 存储key-value 对(节点)时, 需要根据 key 对节点进行排序。 TreeMap 可以保证所有的 key-value 对处于有序状态。 TreeMap 也有两种排序方式。
1、自然排序: TreeMap实现类的所有Key必须要实现Comparable接口,而且所有的key必须要是同一个类对象的,否则会抛出ClassCastException. 2、定制排序: 创建 TreeMap 时,传入一个 Comparator 对象, 该对象负责对 TreeMap 中的所有key 进行排序 。采用定制排序时不要求 Map的 key 实现 Comparable 接口。
TreeMap中判断两个key相等的方法是两个key通过compareTo()方法返回0, 如果使用自定义类作为TreeMap实现类的key时,应当重写equals()方法和compareTo()方法,使得两个方法的返回结果一致。
TreeMap实现类提供一系列的访问Key-Value方法如下:
Map.Entry fIrstEntry(): 返回该 Map 中 最小 key 所对应的 key-value 对,如果该 Map 为 空, 则返回 null 。
Object fIrstKey(): 返回该 Map 中的最小 key 值 , 如果该 Map 为 空, 则返回 null 。
Map.Entry lastEntry(): 返回该 Map 中 最大 key 所对应的 key-value 对,如果该 Map 为空或不存在这样的key-value对,则都返回 null 。
Object lastKey(): 返回该 Map中的最大 key 值,如果该 Map为空或不存在这样的 key. 则都返回 null 。
Map.Entry higherEntry(Object key): 返回 该 Map 中位于 key 后 一位 的 key-value 对 ( 即大于指定key 的最小 key 所对应的 key-value 对 )。如果该 Map 为空 ,则 返回 null 。
Map.Entry lowerEntry(Object key): 返回该 Map 中 位于 key 前一位的 key-value 对(即 小于指定key 的最大 key 所对应的 key-value 对) 。 如果该 Map 为 空或不存在这样 的 key-value 对,则都返回 null
Object lowerKey(Object key): 返回该 Map 中位于 key 前一位的 key 值(即小于指定 key 的最大key 值) 。 如果 该 Map 为 空或不存在这样的 key. 则都返回 null 。
NavigableMap subMap(Object fromKey, boolean fromInclusive, Object toKey, boolean toInclusive):
返回该 Map 的子 Map. 其 key 的范围是从 fromKey (是否包括取决于第二个参数) 到 toKey (是否包括取决于第四个参数) 。
SortedMap subMap(Object 仕omKey, Object toKey): 返回该 Map 的子 Map. 其 key 的 范围是从
fromKey (包括 〉 到 toKey (不包括) 。
SortedMap tailMap(Object 企omKey): 返回该 Map 的子 Map. 其 key 的 范围是大于 fromKey (包括)的所有 key 。
NavigableMap tailMap(Object fromKey, boolean inclusive): 返回该 Map 的子 Map. 其 key 的范围是大于 fromKey (是否包括取决于第二个参数〉的所有 key 。
SortedMap headMap(Object toKey): 返回该 Map 的子 Map. 其 key 的范围是小于 toKey (不包括)的所有 key 。
NavigableMap headMap(Object toKey, boolean inclusive): 返回 该 Map 的子 Map. 其 key 的范围
是小于 toKey (是否包括取决于第二个参数) 的所有 key 。
实例代码:
import java.util.TreeMap;
/**
* @author: 随风飘的云
* @describe:
* @date 2022/03/22 22:47
*/
class R implements Comparable
{
int count;
public R(int count)
{
this.count = count;
}
public String toString()
{
return "R[count:" + count + "]";
}
// 根据count来判断两个对象是否相等。
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj != null && obj.getClass() == R.class)
{
R r = (R)obj;
return r.count == this.count;
}
return false;
}
// 根据count属性值来判断两个对象的大小。
public int compareTo(Object obj)
{
R r = (R)obj;
return count > r.count ? 1 :
count < r.count ? -1 : 0;
}
}
public class TreeMapTest {
public static void main(String[] args) {
TreeMap tm = new TreeMap();
tm.put(new R(3) , "123456");
tm.put(new R(-5) , "abcde");
tm.put(new R(9) , "a1b2c3d4e5");
System.out.println(tm);
// 返回该TreeMap的第一个Entry对象
System.out.println(tm.firstEntry());
// 返回该TreeMap的最后一个key值
System.out.println(tm.lastKey());
// 返回该TreeMap的比new R(2)大的最小key值。
System.out.println(tm.higherKey(new R(2)));
// 返回该TreeMap的比new R(2)小的最大的key-value对。
System.out.println(tm.lowerEntry(new R(2)));
// 返回该TreeMap的子TreeMap
System.out.println(tm.subMap(new R(-1) , new R(4)));
}
}
结果:
WeakHashMap 实现类
基本原理
WeakHashMap 与 HashMap 的用法基本相似 。 与 HashMap 的 区别在于,HashMap 的 key 保留了对实际对象的强引用,这意味着只要该 HashMap 对象不被销毁,该 HashMap 的所有 key 所引用的对象就不会被垃圾回收,HashMap 也不会自动删除这些 key 所对应的 key-value 对 ;但 WeakHashMap 的 key只保留了对实际对象的弱引用,这意味着如果 WeakHashMap 对象的 key 所引用的对象没有被其他强引用变量所引用,则这些 key 所引用的对象可能被垃圾回收, WeakHashMap 也可能自动删除这些 key 所对应的 key-value 对 。
实例源码:
import java.util.WeakHashMap;
/**
* @author: 随风飘的云
* @describe:
* @date 2022/03/22 23:05
*/
public class WeakHashMapTest {
public static void main(String[] args) {
WeakHashMap<String, Object> map = new WeakHashMap<>();
map.put(new String("A"), new String("1"));
map.put(new String("B"), new String("12"));
map.put(new String("C"), new String("13"));
System.out.println(map);
map.put("D", new String("45"));
System.gc();
System.out.println(map);
}
}
结果:
IdentityHashMap 实现类
基本原理
IdentityHashMap 实现类的实现机制与 HashMap 基本相似,但它在处理两个 key 相等时比较独特:在IdentityHashMap 中,当且仅当 两个 key 严格相等 (key1 == key2) 时, IdentityHashMap 才认为两个 key相等;对于普通的 HashMap 而言,只要 key1 和 key2 通过 equals()方法比较返回 true ,且它们的 hashCode值相等即可 。
实例源码:
import java.util.IdentityHashMap;
/**
* @author: 随风飘的云
* @describe:
* @date 2022/03/22 23:11
*/
public class IdentityHashMapTest {
public static void main(String[] args) {
IdentityHashMap map = new IdentityHashMap();
// 通过比较 == 不等,
map.put(new String("java语言"),100);
map.put(new String("java语言"), 120);
// 常量,只能添加一个。
map.put("a", 100);
map.put("a", 110);
System.out.println(map);
}
}
结果:
EnumMap 实现类
基本原理
EnumMap 是一个与枚举类一起使用的 Map实现, EnumMap 中的所有 key 都必须是单个枚举类的枚举值 。 创建 EnumMap 时必须显式或隐式指定它对应的枚举类。 EnumMap 具有如下特征 。
1、EnumMap在内部以数组形式保存,所以这种实现形式非常紧凑、高效 。 2、EnumMap 根据 key 的自然顺序(即枚举值在枚举类中的 定义顺序〉来维护 key-value 对的顺序 ,当程序通过 keySet()、 entrySet() 、 values()等方法遍历EnumMap 时可以看到这种顺序 。 3、EnumMap 不允许使用 null 作为 key ,但允许使用 null 作为 value 。如果试图使用 null 作为 key时将抛出 NullPointerException 异常 。 如果只是查询是否包含值为 null 的key,或只是删除值为null 的key ,都不会抛出异常 。
与创建普通的 Map 有所区别的是,创建 EnumMap 时必须指定一个枚举类,从而将该 EnurnMap 和 指定枚举类关联起来 。
实例源码:
import java.util.EnumMap;
/**
* @author: 随风飘的云
* @describe:
* @date 2022/03/22 23:19
*/
enum Season {
SPRING,SUMMER,FALL,WINTER
}
public class EnumMapTest {
public static void main(String[] args) {
// 创建EnumMap对象,该EnumMap的所有key都是Season枚举类的枚举值
EnumMap enumMap = new EnumMap(Season.class);
enumMap.put(Season.SUMMER , "夏日炎炎");
enumMap.put(Season.SPRING , "春暖花开");
System.out.println(enumMap);
}
}
结果:
各种Map集合类的性能分析
对于 Map 的常用实现类而言,虽然 HashMap 和 Hashtable 的实现机制几乎一样,但由于 Hashtable 是一个古老的、线程安全的集合,因此 HashMap 通常比 Hashtable 要快 。
TreeMap 通常比 HashMap 、 Hashtable 要慢( 尤其在插入、删除 key-value 对时更慢),TreeMap 底层采用红黑树来管理 key-value 对(红黑树的每个节点就是一个 key-value 对) 。
使用 TreeMap 有一个好处::TreeMap 中的 key-value 对总是处于有序状态 ,无须专 门进行排序操作 。当 TreeMap 被填充之后,就可以调用 keySet(),取得由 key 组成的 Set,然后使用 toArray()方法生成 key的数组,接下来使用Arrays 的 binarySearch()方法在己排序的数组中快速地查询对象。
对于 一般的应用场景 , 程序应该多考虑使用 HashMap ,因为 HashMap 正是为快速查询设计的(HashMap 底层其实也是采用数组来存储 key-value 对 〉 。 但如果程序需要一个总是排好序的 Map 时,则可以考虑使用 TreeMap 。
LinkedHashMap 比 HashMap 慢一 点,因为它需要维护链表来保持 Map 中 key-value 时的添加顺序。IdentityHashMap 性能没有特别出色之处 ,因为它采用与 HashMap 基本相似的实现 , 只是它使用 == 而不是 equals()方法来判断元素相等 。 EnumMap 的性能最好 ,但它只能使用同一个枚举类的枚举值作为 key 。
Collections工具类
排序
Collections 提供了如下常用的类方法用于对 List 集合元素进行排序 。
void reverse(List list): 反转指定 List 集合中元素的顺序 。
void shuffie(List list): 对 List 集合元素进行随机排序 (shuffie 方法模拟了"洗牌"动作) 。
void sort(List list): 根据元素的自然顺序对指定 List 集合的元素按升序进行排序。
void sort(List list, Comparator c): 根据指定 Comparator 产生的顺序对 List 集合元素进行排序 。
void swap(List list, int i, int j): 将指定 List 集合中的 i 处元素和 j 处元素进行交换。
void rotate(List list , int distance): 当 distance 为正数时,将 list 集合的后 distance 个元素"整体"移到前面:当 distance 为负数时,将 !ist 集合的前 distance 个元素"整体"移到后面 。 该方法不会改变集合的长度 。
替换
Collection s 还提供了如下常用的用于查找 、 替换集合元素的类万法 。
int binarySearch(List list, Object key): 使用 二分搜索法搜索指定的 List 集合,以获得指定对象在 List
集合中的索引。如果要使该方法可 以正常工作,则必须保证 List 中的元素己经处于有序状态。
Object max(Collection coll): 根据元素的自然顺序,返 回给定集合中的最大元素。
Object max(Collection coll, Comparator comp): 根据 Comparator 指定的顺序 ,返回给定集合中的最大元素 。
Object min(Collection coll) : 根据元素的自然顺序,返回给定集合中的最小元素 。
Object min(Collection coll, Comparator comp): 根据 Comparator 指 定的顺序,返回给定集合中的
最小元素 。
void fill(List list, Object obj): 使用指定元素 obj 替换指定 List 集合中的所有元素 。
int frequency(Collection c, Object o): 返回指定集合中指定元素的出现次数 。
int indexOfSubList(List source, List target) : 返回子 List 对象在父 List 对象中第一次出现的位置索引:如果父 List 中没有出现这样的子 List,则返回-1。
int lastIndexOfSubList(List source, List target): 返回子 List 对象在父 Li st 对象中最后一 次出现 的
位置索引 ;如果父 List 中没有出现这样的子 List,则返回-1 。
boolean replaceAll(List list, Object o ldVal, Object newVal) : 使用 一个新值 newVal 替换 List 对象的所有旧值oldVal 。
ArrayList底层类继承实现图
首先来看一下ArrayList实现类的底层继承图。
Iterable接口: Iterable从英语翻译的角度来说是可迭代的。可以知道实现了这个接口的集合类是可以支持迭代的。
Iterator接口: Iterator从英语翻译的角度来说是迭代器。它就是提供迭代机制的对象,具体如何迭代,都是Iterator接口规范的。
ArrayList实现类的全局变量解析 ArrayList继承于抽象AbstractList类 ,实现了 List接口,RandomAccess接口,Cloneable接口,java.io.Serializable接口。
1、RandomAccess 接口: 表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList中,我们即可以通过元素的序号快速获取元素对象。 2、Cloneable接口: 覆盖重写了clone()函数,即支持复制克隆操作 3、java.io.Serializable接口: 支持序列化,可以通过序列化后进行传输。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
// 序列化
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认初始化容量,如果超过了这个容量,会自动扩展为原来的1.5倍。
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用于空实例的共享空数组实例。
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 用于默认大小的空实例的共享空数组实例。
* 我们将其与 EMPTY_ELEMENTDATA区分开来,
* 以了解添加第一个元素时要膨胀多少。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 保存数据的数组
* ArrayList 的容量就是这个数组缓冲区的长度。
* 非私有以简化嵌套类访问
*/
transient Object[] elementData;
/**
* ArrayList 的大小(它包含的元素数量)。
*/
private int size;
}
ArrayList实现类的构造方法
第一种(无参构造方法):
// 构造一个默认初始容量为10的空列表
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
第二种(有参数构造方法:指定初始容量):
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);
}
}
第三种(根据传入的Collection对象进行数据存储):
/**
* 构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回
* 如果指定的集合为null,throws NullPointerException。
*/
public ArrayList(Collection<? extends E> c) {
// 将Collection对象转换为Object数组(调用的是Collection接口的方法)
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
/**
* 复制指定的数组,截断或填充空值(如有必要),使副本具有指定的长度。
* 对于在原始数组和副本中都有效的所有索引,这两个数组将包含相同的值。
*/
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// 替换为空数组
elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList实现类常用方法解析
add()方法:
// 将指定元素附加到此列表的末尾。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
addAll()方法:
public boolean addAll(Collection<? extends E> c) {
// 将Collection对象转换为Object数组(调用的是Collection接口的方法)
Object[] a = c.toArray();
int numNew = a.length;
// 计算列表容量。确保内部容量大小足够
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
// 返回添加成功
return numNew != 0;
}
在添加元素之前,需要进行剩余空间检查,如果需要则自动扩容。扩容操作最终是通过grow()方法完成的。
set()方法:
由于ArrayList实现类的底层是一个数组,ArrayList的set()方法也就变得非常简单,直接对数组的指定位置赋值即可。
public E set(int index, E element) {
//下标越界检查
rangeCheck(index);
E oldValue = elementData(index);
//赋值到指定位置,复制的仅仅是引用
elementData[index] = element;
return oldValue;
}
get()方法
代码简单注意类型转换。
public E get(int index) {
// 下标越界检测
rangeCheck(index);
//返回元素数据 ,注意类型转换
return (E) elementData[index];
}
remove()方法
删除操作是add()操作的逆过程,需要将删除点之后的元素向前移动一个位置。需要注意的是为了让GC(垃圾回收)起作用,必须显式的为最后一个位置赋null值。
第一个方法(删除指定位置元素):
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
第二个方法(删除指定元素数据):
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
trimToSize()方法:
这个方法提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
index()方法
获取元素第一次出现的位置
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
获取元素最后一次出现的位置:
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
ArrayList实现类的自动扩容机制
前面提及到ArrayList实现类当没有指定初始容量的时候,它的默认容量为10,当列表中的存储的数据元素个数超过了初始容量,那么它就会自动扩容为原来的1.5倍。(由于底层代码结构把返回容量的函数设置为私有,尝试使用反射的方法获取到ArrayList实现类的私有属性和私有方法,但是貌似私有方法可以访问,私有属性访问为空。所以无法打印查看实际的容量大小。)
实例代码验证(有bug,还请大佬改正一下):
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
/**
* @author: 随风飘的云
* @date 2022/03/19 10:43
*/
public class ListTest {
public static void main(String[] args) throws Exception{
// 第一行代码,
ArrayList<Integer> list = new ArrayList<>();
// 第二行代码,下面以此类推。
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
list.add(8);
list.add(9);
list.add(10);
System.out.println(list.size());
list.add(11);
System.out.println(list.size());
Class classList = Class.forName("java.util.ArrayList");
ArrayList obj = (ArrayList) classList.newInstance();
Method[] method = classList.getDeclaredMethods();
// 报错,没有这个属性
Field fields = classList.getField("elementData");
fields.setAccessible(true);
// 打印为空
System.out.println(fields);
for (Method meth:method) {
meth.setAccessible(true);
//System.out.println(meth);
}
Object[] data = new Object[]{1};
System.out.println(method[41].invoke(obj, data, 1));
}
}
代码分析:
首先执行第一行代码,也就是调用ArrayList类的无参构造方法。这个时候的elementData被赋值为默认容量空元素数据DEFAULTCAPACITY_EMPTY_ELEMENTDATA然后执行第二行代码,及添加整形数1,这个时候调用add(E e)方法。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
然后来到这三个方法里执行
1、calculateCapacity(Object[] elementData, int minCapacity) 2、ensureCapacityInternal(int minCapacity)
3、ensureExplicitCapacity(int minCapacity)
// 计算容量方法
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 当elementData为默认容量空元素数据,设置为默认的长度10.
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// 确保内部容量(add函数之后的最先调用)
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 确保显式容量
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
代码执行流程:
经过上面的分析,可以看出大概的流程是这样的。首先创建ArrayList实现类对象,然后往列表里面添加元素。执行add(E e)方法过程中。程序首先调用ensureCapacityInternal方法,这个方法需要传入一个最小容量参数minCapacity,在add()方法内传入的最小容量参数是每次添加元素的长度加一(size+1)。执行ensureCapacityInternal方法,首先是计算容量,具体可看上面三个方法的源码分析。计算完容量就是确保显示容量,显示容量里面有一个属性modCount,这个属性是属于AbstractList抽象类的。当最小容量减去元素数据长度大于0(minCapacity - elementData.length > 0)时,进行列表容量扩容。当列表中添加了整数1,此时最小容量参数minCapacity为10,当列表中添加了整数2,这个时候的元素数据elementData不等于默认容量空元素数据DEFAULTCAPACITY_EMPTY_ELEMENTDATA,这个时候返回的最下容量参数minCapacity为2,因为添加整数2时执行add()方法时传入的最下容量参数是size+1 等于2,此时elementData有数据,不可以执行Math.max(DEFAULT_CAPACITY, minCapacity)这行代码。
一句话总结: 未添加原始数据时,可以理解列表为空,这个时候添加的第一个元素会导致初始化最小容量为10,当后面元素添加的时候,返回的最小容量参数minCapacity 是数据长度,这个时候的最小容量减去元素数据长度大于0(minCapacity - elementData.length > 0)总是不成立,当元素长度添加到11的时候,最小容量减去元素数据长度大于0(minCapacity - elementData.length > 0)成立,执行扩容函数grow()(一句话有点长了)
/**
* 增加容量以确保它至少可以容纳最小容量参数指定的元素数量。
*/
private void grow(int minCapacity) {
// 旧的数据长度为当前元素数据长度
int oldCapacity = elementData.length;
// 新的数据长度为旧的数据长度加旧的数据长度右移位,相当于除以2.(>>为有符号的位移动)
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 调用Arrays.copyOf()方法
elementData = Arrays.copyOf(elementData, newCapacity);
}
Arrays.copyOf()方法
public static int[] copyOf(int[] original, int newLength) {
int[] copy = new int[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
Arrays.copyOf()方法使用实例:
import java.util.Arrays;
/**
* @author: 随风飘的云
* @date 2022/03/19 10:43
*/
public class ListTest {
public static void main(String[] args) throws Exception{
int[] list = new int[]{1,2,3};
System.out.println(list.length);
// 可以理解为扩容
System.out.println(Arrays.copyOf(list, 10).length);
}
}
结果:
ArrayList实现类源码解读(来自JavaGuide):
package java.util;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认初始容量大小
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空数组(用于空实例)。
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
//用于默认大小空实例的共享空数组实例。
//我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 保存ArrayList数据的数组
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* ArrayList 所包含的元素个数
*/
private int size;
/**
* 带初始容量参数的构造函数(用户可以在创建ArrayList对象时自己指定集合的初始大小)
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//如果传入的参数大于0,创建initialCapacity大小的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果传入的参数等于0,创建空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
//其他情况,抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
*默认无参构造函数
*DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
*/
public ArrayList(Collection<? extends E> c) {
//将指定集合转换为数组
elementData = c.toArray();
//如果elementData数组的长度不为0
if ((size = elementData.length) != 0) {
// 如果elementData不是Object类型数据(c.toArray可能返回的不是Object类型的数组所以加上下面的语句用于判断)
if (elementData.getClass() != Object[].class)
//将原来不是Object类型的elementData数组的内容,赋值给新的Object类型的elementData数组
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 其他情况,用空数组代替
this.elementData = EMPTY_ELEMENTDATA;
}
}
/**
* 修改这个ArrayList实例的容量是列表的当前大小。 应用程序可以使用此操作来最小化ArrayList实例的存储。
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
//下面是ArrayList的扩容机制
//ArrayList的扩容机制提高了性能,如果每次只扩充一个,
//那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。
/**
* 如有必要,增加此ArrayList实例的容量,以确保它至少能容纳元素的数量
* @param minCapacity 所需的最小容量
*/
public void ensureCapacity(int minCapacity) {
//如果是true,minExpand的值为0,如果是false,minExpand的值为10
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
//如果最小容量大于已有的最大容量
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
//1.得到最小扩容量
//2.通过最小容量扩容
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 获取“默认的容量”和“传入参数”两者之间的最大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//调用grow方法进行扩容,调用此方法代表已经开始扩容了
grow(minCapacity);
}
/**
* 要分配的最大数组大小
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* ArrayList扩容的核心方法。
*/
private void grow(int minCapacity) {
// oldCapacity为旧容量,newCapacity为新容量
int oldCapacity = elementData.length;
//将oldCapacity 右移一位,其效果相当于oldCapacity /2,
//我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
int newCapacity = oldCapacity + (oldCapacity >> 1);
//然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//再检查新容量是否超出了ArrayList所定义的最大容量,
//若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,
//如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
//比较minCapacity和 MAX_ARRAY_SIZE
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
/**
*返回此列表中的元素数。
*/
public int size() {
return size;
}
/**
* 如果此列表不包含元素,则返回 true 。
*/
public boolean isEmpty() {
//注意=和==的区别
return size == 0;
}
/**
* 如果此列表包含指定的元素,则返回true 。
*/
public boolean contains(Object o) {
//indexOf()方法:返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1
return indexOf(o) >= 0;
}
/**
*返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1
*/
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
//equals()方法比较
if (o.equals(elementData[i]))
return i;
}
return -1;
}
/**
* 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。.
*/
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
/**
* 返回此ArrayList实例的浅拷贝。 (元素本身不被复制。)
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
//Arrays.copyOf功能是实现数组的复制,返回复制后的数组。参数是被复制的数组和复制的长度
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// 这不应该发生,因为我们是可以克隆的
throw new InternalError(e);
}
}
/**
*以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。
*返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。
*因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
/**
* 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素);
*返回的数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组,则返回其中。
*否则,将为指定数组的运行时类型和此列表的大小分配一个新数组。
*如果列表适用于指定的数组,其余空间(即数组的列表数量多于此元素),则紧跟在集合结束后的数组中的元素设置为null 。
*(这仅在调用者知道列表不包含任何空元素的情况下才能确定列表的长度。)
*/
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// 新建一个运行时类型的数组,但是ArrayList数组的内容
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
//调用System提供的arraycopy()方法实现数组之间的复制
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
// Positional Access Operations
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
/**
* 返回此列表中指定位置的元素。
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
/**
* 用指定的元素替换此列表中指定位置的元素。
*/
public E set(int index, E element) {
//对index进行界限检查
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
//返回原来在这个位置的元素
return oldValue;
}
/**
* 将指定的元素追加到此列表的末尾。
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
//这里看到ArrayList添加元素的实质就相当于为数组赋值
elementData[size++] = e;
return true;
}
/**
* 在此列表中的指定位置插入指定的元素。
*先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大;
*再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
//arraycopy()这个实现数组之间复制的方法一定要看一下,下面就用到了arraycopy()方法实现数组自己复制自己
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
/**
* 删除该列表中指定位置的元素。 将任何后续元素移动到左侧(从其索引中减去一个元素)。
*/
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
//从列表中删除的元素
return oldValue;
}
/**
* 从列表中删除指定元素的第一个出现(如果存在)。 如果列表不包含该元素,则它不会更改。
*返回true,如果此列表包含指定的元素
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
/**
* 从列表中删除所有元素。
*/
public void clear() {
modCount++;
// 把数组中所有的元素的值设为null
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
/**
* 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。
*/
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
/**
* 将指定集合中的所有元素插入到此列表中,从指定的位置开始。
*/
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
/**
* 从此列表中删除所有索引为fromIndex (含)和toIndex之间的元素。
*将任何后续元素移动到左侧(减少其索引)。
*/
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
/**
* 检查给定的索引是否在范围内。
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* add和addAll使用的rangeCheck的一个版本
*/
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 返回IndexOutOfBoundsException细节信息
*/
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
/**
* 从此列表中删除指定集合中包含的所有元素。
*/
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
//如果此列表被修改则返回true
return batchRemove(c, false);
}
/**
* 仅保留此列表中包含在指定集合中的元素。
*换句话说,从此列表中删除其中不包含在指定集合中的所有元素。
*/
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
/**
* 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。
*指定的索引表示初始调用将返回的第一个元素为next 。 初始调用previous将返回指定索引减1的元素。
*返回的列表迭代器是fail-fast 。
*/
public ListIterator<E> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
/**
*返回列表中的列表迭代器(按适当的顺序)。
*返回的列表迭代器是fail-fast 。
*/
public ListIterator<E> listIterator() {
return new ListItr(0);
}
/**
*以正确的顺序返回该列表中的元素的迭代器。
*返回的迭代器是fail-fast 。
*/
public Iterator<E> iterator() {
return new Itr();
}