注意:作者之前的Java基础没有打牢,有一些知识点没有记住,所以在学习中出现了许多零散的问题。现在特地写一篇笔记总结一下,所以有些知识点不是很齐全。
集合中各类名词的关系
Collection集合为单列集合。

集合存储数据类型的特点
单列集合
集合可以存储引用数据类型,无法存储基本数据类型。如果想要存储基本数据类型就要使用对应的包装类。
ArrayList的特点
//创建集合的对象
//泛型:限定集合中存储数据的类型,只支持引用数据类型
//泛型的格式:<数据类型>
ArrayList<String> list =new ArrayList<String>();
ArrayList<String> list =new ArrayList<>();ArrayList 是 Java 集合框架中的一个重要类,它实现了 List 接口,用于存储元素的有序集合(也称为序列)。ArrayList 的特点包括:
- 动态数组:
ArrayList内部使用动态数组来存储元素。这意味着当添加元素时,如果内部数组已满,ArrayList会自动创建一个更大的数组,并将现有数组的内容复制到新数组中,然后添加新元素。- 由于使用了动态数组,
ArrayList允许我们存储任意数量的元素(受限于可用内存)。- 有序性:
ArrayList中的元素是有序的,它们按照被添加到列表中的顺序进行存储。我们可以通过索引(位置)来访问和修改元素。- 允许重复元素:
ArrayList不检查元素的唯一性,因此它允许存储重复的元素。- 非同步:
ArrayList不是线程安全的。如果多个线程同时访问和修改ArrayList,可能会导致数据不一致或其他并发问题。如果需要在多线程环境中使用,应该考虑使用Collections.synchronizedList()方法或CopyOnWriteArrayList类。- 自动扩容:
- 当向
ArrayList添加元素并超出其当前容量时,它会自动扩容。扩容通常涉及到创建一个新的更大的数组,并将现有数组的内容复制到新数组中。扩容的算法可能会导致额外的性能开销,因此在知道大致的元素数量时,可以通过构造方法或ensureCapacity()方法预先设置容量。- 随机访问效率高:
- 由于
ArrayList使用数组作为底层数据结构,因此它提供了基于索引的高效随机访问能力。通过索引访问元素的时间复杂度是 O(1)。- 基于索引的插入和删除效率低:
- 虽然
ArrayList提供了基于索引的插入和删除方法(如add(int index, E element)和remove(int index)),但这些操作的时间复杂度是 O(n),因为可能需要移动数组中的其他元素。如果需要频繁地在列表中间进行插入和删除操作,LinkedList可能是更好的选择。- 内存占用:
ArrayList需要额外的空间来存储数组,这可能会导致它比某些其他集合类型(如LinkedList)占用更多的内存。但是,由于ArrayList提供了高效的随机访问能力,这种内存开销在某些情况下可能是值得的。
LinkedList的特点
LinkedList 是 Java 集合框架中的一部分,它实现了 List 接口,用于存储元素的列表。与 ArrayList 不同的是,LinkedList 底层基于双向链表实现,这给了它一些独特的特性和性能优势。以下是 LinkedList 的一些主要特点:
- 基于链表实现:
LinkedList使用双向链表存储元素,这意味着每个元素都包含对前一个元素和后一个元素的引用。- 这种结构使得在列表的任何位置添加或删除元素都非常快,因为不需要移动其他元素。
- 添加和删除操作的高效性:
- 在
LinkedList的头部或尾部添加或删除元素的时间复杂度是 O(1),因为可以直接访问这些位置并修改指针。- 而在列表的中间位置添加或删除元素时,需要遍历列表以找到正确的位置,但即使这样,其性能也通常优于
ArrayList,因为ArrayList需要移动所有后续元素。- 空间开销:
- 由于每个元素都需要存储对前一个元素和后一个元素的引用,因此
LinkedList的空间开销略大于ArrayList。- 然而,这种开销通常可以忽略不计,除非在非常紧凑的内存环境中工作。
- 迭代性能:
- 遍历
LinkedList通常比遍历ArrayList慢,因为链表中的元素在内存中可能不是连续存储的,这可能导致更多的缓存未命中和更慢的访问速度。- 但是,如果你需要频繁地在列表的头部或尾部添加或删除元素,并且很少遍历整个列表,那么
LinkedList的性能可能会更好。- 线程不安全性:
- 与
ArrayList一样,LinkedList也不是线程安全的。如果多个线程同时修改列表,可能会导致数据不一致。- 如果你需要线程安全的列表,可以使用
Collections.synchronizedList()方法包装LinkedList,或者使用并发集合(如CopyOnWriteArrayList或ConcurrentLinkedQueue)。- 支持队列操作:
- 由于
LinkedList实现了Deque接口(双端队列),它支持从两端添加和删除元素的操作。- 这使得
LinkedList可以用作队列(使用addFirst()和removeFirst()方法)或栈(使用push()和pop()方法)。- 容量和大小:
- 与
ArrayList不同的是,LinkedList没有初始容量或固定容量的概念。它可以根据需要动态增长。- 但是,请注意,链表中的元素在内存中可能是分散的,这可能导致内存碎片问题。
- 其他操作:
LinkedList还提供了其他有用的方法,如getFirst(),getLast(),indexOf(),lastIndexOf(),element(),offer(),peek(),poll()等。
在选择使用 LinkedList 还是 ArrayList 时,你应该考虑你的具体需求,特别是你如何频繁地添加、删除和遍历列表中的元素。
Vector的特点
在Java中,Vector集合的特点主要包括以下几点:
- 线程安全:
Vector是线程安全的,这意味着多个线程可以同时访问和修改Vector的内容。这是通过在每个方法上添加synchronized关键字来实现的,但这也可能导致在高并发场景下性能下降。- 动态数组:与传统的数组不同,
Vector可以根据需要动态地增加或减小其大小。当需要增加或减少元素的数量时,Vector会自动调整其底层数组的大小。- 可以存储任意类型的元素:
Vector可以存储任意类型的对象,包括基本类型的包装类对象。这使得Vector具有很高的灵活性。- 有序性:
Vector中的元素是按照插入顺序进行存储的,可以根据索引位置来访问和修改元素。这使得Vector在需要保持元素顺序的场景下非常有用。- 访问速度快:由于
Vector内部包含一个存储元素的数组,所有的元素都被存储在这个数组中,因此其访问速度非常快。只需要简单的访问数组的索引即可获取或修改元素。- 容量和大小:
Vector的大小是指其包含的元素数量,而容量是指Vector底层数组的大小。当Vector中的元素数量超过其容量时,Vector会自动扩容以容纳更多的元素。扩容时,Vector的增长率通常为当前容量的100%,这意味着在大量数据的情况下,使用Vector可能有一定的优势。- 使用场景:由于
Vector的动态数组特性、线程安全特性以及高效的访问速度,它适用于许多应用场景,如需要在多线程环境下安全地访问和操作数据,或者需要动态调整数据大小并保持元素顺序的场景。
需要注意的是,虽然Vector是线程安全的,但在高并发场景下,由于其同步机制可能导致性能下降。因此,在不需要线程安全性的情况下,通常建议使用ArrayList来提高性能。
HashSet的特点
Java中的HashSet集合具有以下几个主要特点:
不包含重复元素:
HashSet基于HashMap实现,它不允许存储重复的元素。当尝试向HashSet中添加一个已经存在的元素时,该操作不会有任何效果,即元素不会被添加进去,并且add方法会返回false。
无序性:
HashSet不保证元素的迭代顺序与插入顺序相同。这是因为HashSet内部使用哈希表来存储元素,而哈希表并不保证元素的顺序。
非同步:
HashSet不是线程安全的,即多个线程可以同时访问和修改一个HashSet对象。在多线程环境下,如果需要对HashSet进行同步访问,则需要使用额外的同步机制,如Collections.synchronizedSet()方法或ConcurrentHashSet(Java标准库中没有直接提供ConcurrentHashSet,但可以使用ConcurrentHashMap的键集合来模拟)。
元素存储的唯一性依赖于对象的hashCode()和equals()方法:在判断两个元素是否相等时,
HashSet首先会比较它们的哈希值(通过调用hashCode()方法),如果哈希值相同,则会进一步调用equals()方法来判断它们是否相等。因此,要正确使用HashSet存储元素,必须保证存储的元素正确重写了hashCode()和equals()方法。
快速查找:由于
HashSet基于哈希表实现,因此它提供了非常快的查找速度。无论是通过contains()方法检查元素是否存在,还是通过迭代器遍历元素,通常都能在常数时间内完成。
支持null元素:
HashSet允许存储一个null元素。但是,由于HashSet不允许存储重复元素,因此只能存储一个null元素。
非泛型集合:虽然
HashSet在实际使用时通常会与泛型一起使用(如HashSet<String>),但HashSet本身并不是泛型集合。它是Java 1.2中引入的,而泛型是在Java 5中引入的。不过,从Java 5开始,推荐使用泛型化的HashSet来避免类型转换和类型不安全的操作。
TreeSet的特点
Java中的TreeSet集合具有以下几个主要特点:
- 元素有序:
TreeSet中的元素是有序的,默认按照元素的自然顺序进行排序。如果元素实现了Comparable接口,则按照元素的自然顺序进行排序;如果没有实现Comparable接口,则必须提供一个Comparator接口的实现来指定排序顺序。也可以在创建TreeSet时传入自定义的比较器来进行排序。- 无重复元素:
TreeSet不允许存储重复的元素。当尝试添加重复元素时,新元素不会被添加到集合中。- 基于红黑树实现:
TreeSet的底层数据结构是红黑树,这是一种自平衡的二叉搜索树。红黑树的自平衡性使得TreeSet能够实现快速的查找、插入和删除操作。因此,TreeSet在需要有序集合且频繁进行查找操作的场景中非常适用。- 查询效率高:由于底层红黑树的特性,
TreeSet的查询效率非常高。可以在O(log n)的时间复杂度内完成查找操作,其中n是TreeSet中元素的个数。- 非线程安全:
TreeSet不是线程安全的,如果多个线程同时访问一个TreeSet,并且至少有一个线程对其进行了修改,则必须通过外部同步手段来保证线程安全。- 支持迭代器:
TreeSet支持迭代器,可以通过迭代器遍历集合中的元素。
总的来说,TreeSet是一种基于红黑树实现的有序集合,适用于需要有序集合且频繁进行查找操作的场景。同时,由于它的非线程安全特性,在使用时需要注意线程同步问题。
双列集合(待补充)
集合的创建
单列集合
Array List集合的创建
package 集合;import java.util.ArrayList;
import java.util.Collection;public class CollectionTest {public static void main(String[] args){ArrayList<String> c=new ArrayList<>();   <--//添加元素c.add("zangng");c.add("hua");String i=c.get(0);//取出特定元素System.out.println(i);System.out.println("c的集合元素个数:"+c.size());c.remove("zangng");//删除特定元素System.out.println("c的集合元素个数:"+c.size());}
}--------------------------------------------------------------package 集合;import java.util.ArrayList;
import java.util.List;public class CollectionTest2 {public static void main(String[] args){List<String> c=new ArrayList<>();    <--//添加元素c.add("zangng");c.add("hua");String i=c.get(0);//取出特定元素System.out.println(i);System.out.println("c的集合元素个数:"+c.size());c.remove("zangng");//删除特定元素System.out.println("c的集合元素个数:"+c.size());}
}两种方式的区别和联系
在Java中,List<String> list = new ArrayList<String>(); 和 ArrayList<String> c = new ArrayList<>(); 这两行代码在功能上是等效的,但它们在类型引用和代码简洁性方面有所不同。
- 类型引用: -  List<String> list = new ArrayList<String>();:这里,我们创建了一个ArrayList的实例,但将其引用赋值给一个List类型的变量list。这意味着我们只能使用List接口中定义的方法,而不能直接使用ArrayList类中可能提供的任何特定方法(除非我们稍后将list强制转换回ArrayList类型)。这是一种好的编程实践,因为它提高了代码的可读性和可维护性,同时也使得代码更加灵活,因为我们可以在不修改代码其他部分的情况下,将list引用指向ArrayList的其他实现(如LinkedList)。
-  ArrayList<String> c = new ArrayList<>();:这里,我们创建了一个ArrayList的实例,并直接将其引用赋值给一个ArrayList类型的变量c。这意味着我们可以直接使用ArrayList类中定义的所有方法,但这也限制了我们在不修改代码其他部分的情况下,将c引用指向List的其他实现。
 
-  
- 代码简洁性(从Java 7开始): - 从Java 7开始,引入了菱形操作符(diamond operator)<>,用于简化泛型代码的编写。因此,new ArrayList<String>()可以简写为new ArrayList<>()。所以,List<String> list = new ArrayList<>();是List<String> list = new ArrayList<String>();的简写形式,并且它们是完全等效的。
 
- 从Java 7开始,引入了菱形操作符(diamond operator)
在实际编程中,推荐的做法是使用接口作为类型引用(如List<String>),而不是具体的实现类(如ArrayList<String>),除非我们有特定的理由需要直接使用实现类的方法。这样做的好处是提高了代码的灵活性和可维护性。如果我们确定代码只会使用ArrayList并且不会更改实现,那么使用ArrayList<String>作为类型引用也是可以的,但通常这不是一个好的做法。
拓展(仅供参考)
除了List<String> list = new ArrayList<String>();和ArrayList<String> c=new ArrayList<>();还有其他方式实现Array List吗?
在Java中,虽然直接实例化ArrayList是创建ArrayList实例的最常见方式,但还有一些其他间接或变种的方式来实现类似的功能。以下是一些可能的方法:
使用Java 8的Stream API:
 虽然这不是直接创建ArrayList的方式,但你可以使用Stream API来生成一个ArrayList。
List<String> list = Stream.of("a", "b", "c").collect(Collectors.toList());这里,Stream.of("a", "b", "c")创建了一个包含字符串的Stream,然后collect(Collectors.toList())将流中的元素收集到一个新的List中,这通常是一个ArrayList的实例(尽管不是明确地指定为ArrayList类型)。
使用Arrays.asList():
 虽然Arrays.asList()返回的是一个固定大小的列表(它实际上是Arrays类的内部类的一个实例,并不是ArrayList),但你可以将这个列表传递给ArrayList的构造器来创建一个新的可变列表。
List<String> fixedList = Arrays.asList("a", "b", "c");  
ArrayList<String> arrayList = new ArrayList<>(fixedList);使用CopyOnWriteArrayList:CopyOnWriteArrayList是ArrayList的一个线程安全变体。它在修改操作(如添加、设置)时创建底层数组的新副本,因此读取操作(如get)是无锁的,从而提高了并发性能。但是,请注意,由于它在修改时复制底层数组,因此它可能不适合在需要频繁修改的大型列表上使用。
List<String> threadSafeList = new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c"));使用Collections.synchronizedList():
 你可以使用Collections.synchronizedList()方法来包装任何实现了List接口的集合(包括ArrayList),以提供线程安全的列表。但是,这种同步是基于方法的,并且可能不如CopyOnWriteArrayList在并发读取时高效。
List<String> syncList = Collections.synchronizedList(new ArrayList<>(Arrays.asList("a", "b", "c")));使用第三方库:
 除了Java标准库中的集合类之外,还有一些第三方库提供了额外的集合实现,这些实现可能具有不同的性能特征或额外的功能。例如,Google Guava库提供了许多有用的集合类,包括ImmutableList(不可变列表)、Multimap(键值对可以重复的映射)等。
使用Java 9的List.of():
 从Java 9开始,你可以使用List.of()方法来创建一个不可变的列表,这在你只需要一个固定大小的列表并且不需要修改它时很有用。
List<String> immutableList = List.of("a", "b", "c");但请注意,List.of()返回的列表是不可变的,因此它不能替代需要可变性的ArrayList。
同类型其他集合创建
LinkedList集合的创建
使用LinkedList和List的区别其实在上文提过,与ArrayList有异曲同工之妙。
//创建方法一
LinkedList<String> c= new LinkedList<>();
//创建方法二
List<String> c= new LinkedList<>();Vector集合的创建
//创建方法一
Vector<String> vector = new Vector<>();
//创建方法二
List<String> c=new Vector<>();HashSet集合的创建
//创建方法一
HashSet<String> c = new HashSet<>();
//创建方法二
Set<String> c = new HashSet<>();TreeSet集合的创建
//创建方法一
TreeSet<String> c = new TreeSet<>();
//创建方法二
Set<String> c = new TreeSet<>();双列集合(待补充)
集合的遍历格式(待补充)
Lambda表达式
Lambda表达式(Lambda Expression)是Java 8及以后版本中引入的一个新特性,它允许我们以更简洁的方式表示匿名函数(即没有名称的函数)。Lambda表达式提供了一种新的语法来定义函数式接口(Functional Interface,即只包含一个抽象方法的接口)的实例。
Lambda表达式的基本语法如下:
- parameters:这是Lambda表达式的参数列表。参数的类型可以省略(即类型推断),但如果Lambda表达式的参数超过一个或者参数的类型需要明确指定时,则必须加上括号。
- ->:这是Lambda操作符,用于分隔参数列表和Lambda体。
- expression或- { statements; }:这是Lambda体,可以是单个表达式(其值就是Lambda表达式的返回值)或者一个语句块(如果有多条语句)。
Lambda表达式常用于与函数式接口一起使用,作为这些接口的实例。Java 8在java.util.function包中引入了许多新的函数式接口,如Function、Predicate、Consumer等,它们都可以使用Lambda表达式来实例化。
下面是一些Lambda表达式的例子:
package Lambda表达式;
//无参
public class Test01 {public static void main(String[] args){//使用lambda表达式实现接口Test test1=()->{System.out.println("test");};test1.test();}
}//lambda表达式,只能实现函数式接口。
//如果说,⼀个接口中,要求实现类必须实现的抽象方法,有且只有⼀个!这样的接口,就是函数式接口。
//@FunctionalInterface是⼀个注解,用在接口之前,判断这个接口是否是⼀个函数式接口。
// 如果是函数式接口,没有任何问题。如果不是函数式接口,则会报错。功能类似于 @Override。
@FunctionalInterface
interface Test{//有且只有一个实现类必须要实现的抽象方法,所以是函数式接口public void test();
}
 Lambda表达式极大地简化了匿名内部类的编写,使代码更加简洁易读。同时,它也为Java带来了函数式编程的能力,使得Java能够更好地支持并发编程和流式数据处理等现代编程范式。