前言
集合的背景
在没有集合类之前,实际上在Java语言里已经有一种方法可以存储对象,那就是数组。数组不仅可以存放基本数据类型也可以容纳属于同一种类型的对象。数组的操作是高效率的,但也有缺点。比如数组的长度是不可以变的,数组只能存放同一种类型的对象(或者说对象的引用)。
另外,在程序设计过程中,程序员肯定会经常构建一些特殊的数据结构以正确的描述或者表达现实情况。比如描述火车进站出站,他们会用到“栈”这个数据结构,常用的数据结构还有:队列、链接表、树和散列表等等。这些数据结构几乎在每一段程序设计过程中都会使用到,但是如果每次编程都要重新构建这些数据结构显然违背了软件组件化的思想。因此Java的设计者考虑把这些通用的数据结构做成API供程序员调用。
基于以上几点必须解决的问题。Java提供了对象的数种保存方式,除了内置的数组以外,其余的称为集合类。为了使程序方便地存储和操纵数目不固定的一组数据,JDK中提供了Java集合类,所有Java集合类都位于Java.util包中,与Java数组不同,Java集合不能存放基本数据类型数据,而只能存放对象的引用。
🏠个人主页: 黑洞晓威
🧑个人简介:大家好,我是晓威,一名普普通通的大二在校生,希望在CSDN中与大家一起成长。
🎁如果你也在正在学习Java,欢迎各位大佬来到我的博客查漏补缺呀,如果有哪里写的不对的地方也欢迎诸佬指正啊。
@[toc]
一:集合
1 集合框架的概述
数组在存储多个数据方面的特点:
- 一旦初始化后,其长度就确定了
- 数组一旦定义好,其元素类型也确定了。
数组在存储多个数据方面的缺点:
- 一旦初始化后,其长度就确定了
- 数组中提供的方法非常有限,对于添加、删除、插入数据等操作非常不便,同时效率不高。
- 获取数组中实际元素个数的需求,数组没有现成的属性或方法可用。
- 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求不能满足。
2 Collection接口中方法的使用
方法 | 作用 |
---|---|
add(object e) | 将元素e添加到集合中 |
addAll(Collection coll) | 将coll集合中的元素添加到当前集合中 |
clear() | 清空集合元素 |
size() | 获取集合中元素的个数 |
isEmpty() | 判断当前集合是否为空 |
代码实例:
public static void main(String[] args) {
Collection coll = new ArrayList();//new一个对象
coll.add(123);//向集合中添加元素
coll.add(456);
coll.add("aa");
coll.add(new Student("张三",18));
System.out.println(coll.size());//输出集合中元素的个数,此时为4
Collection coll1 = new ArrayList();
coll1.add(789);
coll1.add("bb");
coll.addAll(coll1);//将coll1中的所有元素添加到coll中
System.out.println(coll.size());//6
coll.clear();//清空集合中的元素
System.out.println(coll.isEmpty());//判断集合是否为空,此时为true
}
方法 | 作用 |
---|---|
remove(Object obj) | 从当前集合中移除obj元素 |
removeAll(Collection coll) | 从当前集合中移除coll中的元素(即为求差集) |
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("Tom"));
coll.add(new Student("张三",18));
Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add("Tom");
coll.remove(123);
System.out.println(coll);//[456, Tom, Student{name='张三', age=18}]
coll.removeAll(coll1);
System.out.println(coll);//[Student{name='张三', age=18}]
方法 | 作用 |
---|---|
contains(Object obj) | 判断集合中是否含有obj |
containsAll(Colltction coll) | 判断集合中是否包含coll中的所有元素 |
代码实例:
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("Tom"));
coll.add(new Student("张三",18));
Collection coll1 = new ArrayList();
coll1.add(123);
//contains本质上调用的是equals,String重写过equals
System.out.println(coll.contains(new String("Tom")));
//如果Student类没有重写equals方法,则调用Object中的equals,则结果为false
//重写equals后结果为true
System.out.println(coll.contains(new Student("张三",18)));
//判断集合coll是否包含coll1中的所有元素
System.out.println(coll.containsAll(coll1));
}
补充:
equals 方法中的源代码
public boolean equals(Objects obj){
return (this == obj);
}
以上这个方法是 Object 类的默认实现。
判断两个基本数据类型是否相等直接用“ == ”就行
判断两个Java对象是否相等,不能使用“ == ”,因为“ == ”比较的是两个对象的内存地址。
方法 | 作用 |
---|---|
toArray() | 将集合转化为数组 |
拓展:如何将数组转化为集合?
调用Arrays类中的静态方法asList
代码实例:
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("Tom"));
coll.add(new Student("张三",18));
//集合-->数组
Object[] arr= coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
System.out.println("*******************************");
//数组-->集合
List coll1 = Arrays.asList(arr);
System.out.println(coll1);
}
方法 | 作用 |
---|---|
iterator() | 返回Iterator接口的实例,用于遍历集合元素 |
代码实例:
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("Tom"));
coll.add(new Student("张三",18));
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
二:集合之List
1. List接口:List实现类的对比
- ArrayList : 作为List的主要实现类;线程不安全,效率高;底层使用Object[] elementDatec存储
- LinkedList : 作为频繁的插入,删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
- Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementDatec存储
2. ArrayList : 底层结构和源码分析
1、注意事项:
- 允许存储所有元素,包括null,ArrayList可以加入一个或者多个null
- ArrayList是由数组来实现数据存储的
- ArrayList基本等同于Vector,除了ArrayList是线程不安全的(源码中没有synchronized关键字,执行效率高)
2、ArrayList底层操作机制源码分析
ArrayList中维护了一个Object类型的数组elementData:
transient Object[] elementData;//transient关键字表示,该属性不会被序列化
- 无参构造器:当创建ArrayList对象的时候,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容elementData为10,如需再次扩容, 则扩容elementData1.5倍
- 有参构造器:如果使用的是指定大小的构造器,则初始化elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
3. LinkedList底层结构与源码分析
LinkedList说明:
- LinkedList底层实现了双向链表和双端队列的特点
- 可以添加任意元素(可以重复), 包括null
- 线程不安全,没有实现同步
LinkedList底层机制
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性first和last分别指向首节点和尾节点
- 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中prev指向前一个,通过next指向后一个节点,最终实现双向链表
- 所以LinkedList的元素添加和删除,不是通过数组实现的,相对来说效率较高
4 . List中的常用方法
方法 | 作用 |
---|---|
add(int index,Object obj) | 在index位置插入元素 |
allAdd(int index ,Collection coll) | 从index位置开始将coll总所有元素添加进来 |
get(int index) | 获取指定index位置的元素 |
remove(int index) | 移除指定index位置的元素并返回此元素 |
set(int index , Object obj) | 设置指定index位置的元素为obj |
subList(int fromIndex,int toIndex) | 返回从fromIndex到 toIndex位置的子集合 |
indexOf(Object obj) | 返回obj在集合中首次出现的位置 |
LastIndexof(Object obj) | 返回obj在当前集合中末次出现的位置 |
List中的增删改查插
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vriFyeNx-1667535884003)(C:\Users\sp815\AppData\Roaming\Typora\typora-user-images\image-20220930170403352.png)]
public static void main(String[] args) {
ArrayList list = new ArrayList();
//增
list.add(123);
list.add(456);
list.add(new String("AA"));
list.add(new Student("张三",18));
System.out.println(list);
//删
list.remove(0);
list.remove(new Integer(123));
System.out.println(list);
//改:
list.set(0,007);
//查:
list.get(0);
//插:
list.add(1,234);
}
5. 遍历的两种方法:
ArrayList list = new ArrayList();
//增
list.add(123);
list.add(456);
list.add(new String("AA"));
list.add(new Student("张三",18));
System.out.println(list);
//方式一:
//Iterator迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//方式二:
//增强for循环
for(Object obj:list){
System.out.println(obj);
}
一道面试题
区分List中remove(int index)和remove(Object obj)
remove(int index)通过下标删除remove(Object obj)通过元素删除
6. 如何选择ArrayList和LinkedList:
1)如果改查操作多,选择ArrayList
2)如果增删操作多,选择LinkedList
3)一般来说,在程序中,80%-90%都是查询,因此大部分情况下都会选择ArrayList
4)在一个项目中,根据业务灵活选择。
三:集合之Set
1. Set接口:Set实现类的对比
Set接口:存储无序、不可重复的数据 (类似于高中讲的集合)
- HashSet:作为Set接口的主要实现类;线程不安全;可以存储null值
- LinkHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序输出
- TreeSet:可以按照添加对象的指定属性进行排序
Set存储数据的特点:存储无序的、不可重复的数据
- 无序性:不等于随机性。存储的数据在底层数组中并非按照数组的索引顺序添加,而是根据数据的哈希值对应数组的位置添加
- 不可重复性:保证添加的元素按照equals()判断时,不能返回true。即相同的元素只能添加一个。
保证元素不可重复需要让元素重写两个方法:一个是 hashCode(),另一个是 equals()。HashSet在存储元素的过程中首先会去调用元素的hashCode()值,看其哈希值与已经存入HashSet的元素的哈希值是否相同。
如果不同 :就直接添加到集合;
如果相同 :则继续调用元素的equals() 和哈希值相同的这些元素依次去比较。如果说有返回true的,那就重复不添加;如果说比较结果都说false,那就是不重复就添加。
2. HashSet
代码示例
public static void main(String[] args) {
HashSet set = new HashSet();
set.add(123);
set.add(456);
set.add("AA");
set.add(new Student("张三",18));
System.out.println(set);
}
结果:(无序性)[AA, Student{name='张三', age=18}, 456, 123]
3. LinkedHashSet
LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录数据的前一个数据与后一个数据
public static void main(String[] args) {
HashSet set = new LinkedHashSet();
set.add(123);
set.add(456);
set.add("AA");
set.add(new Student("张三",18));
System.out.println(set);
}
结果:[123, 456, AA, Student{name='张三', age=18}]
可以看到LinkHashSet可以按顺序输出
4. TreeSet
可以按照添加对象的指定属性进行排序
方式一:自然排序
让元素所在的类实现Comparable接口,并重写CompareTo() 方法,并根据CompareTo()的返回值来进行添加元素
//所在类实现Comparable接口,并重写CompareTo() 方法
@Override
public int compareTo(Object o) {
if (o instanceof Student){//判断类型是否为Student类的实例
Student student =(Student) o;//转化为Student类型
if(Integer.compare(this.age,student.age)!=0) {
return Integer.compare(this.age,student.age);//年龄由小到大
}else return this.name.compareTo(student.name);//名字由小到大
}
}
方式二: 定制排序
使用TreeSet的有参构造方法创建TreeSet对象的时候, 传入一个比较器 Comparator 进去, TreeSet在添加元素的时候, 根据比较器的compare()方法的返回值来添加元素。
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add(new Student("张三",18));
treeSet.add(new Student("李四",17));
treeSet.add(new Student("王五",18));
System.out.println(treeSet);
Iterator iterator = treeSet.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//实例化Comparator
Comparator comparator = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Student && o2 instanceof Student){
Student s1 = (Student) o1;
Student s2 = (Student) o2;
int age = Integer.compare(s1.getAge(),s2.getAge());
if(age!=0){
return -age;//按年龄从大到小
}else return s1.getName().compareTo(s2.getName());
}else throw new RuntimeException();
}
@Override
public boolean equals(Object obj) {
return false;
}
};
TreeSet treeSet1 = new TreeSet(com);
treeSet1.add(new Student("张三",18));
treeSet1.add(new Student("李四",17));
treeSet1.add(new Student("王五",16));
System.out.println(treeSet1);
};
四:集合之Map
1. Map多个实现类对比
Map:双列数据,存储key-value对的数据 ------类似于高中的函数 y=f(x)
HashMap:作为Map的主要实现类;线程不安全,效率高;可以存储null的key和value
- LinkedHashMap:保证在遍历Map元素时,添加了一对指针,指向前一个元素与后一个元素,对于频繁的遍历操作,此类执行效率高于HashMap。
- TreeMap:能够实现对添加的key-value排序,可以使用自然排顺序与定制排序。底层使用红黑树。
Hashtable:作为古老的实现类;线程安全,效率低;不能存储null的key和value
- Properties:常用来处理配置文件。key-value都是String类型
2. Map集合的特点:
- Map是一个双列集合,一个元素包含两个值(一个key,一个value)
- Map集合中的元素,key和value的数据类型可以相同,也可以不同
- Map中的元素,key不允许重复,key所在的类要重写equals()和hashCode()方法
- Map中的元素,value可以重复,value所在类要重写equals()方法
- Map里的key和value是一一对应的,构成了一个Entry对象,Entry无序的不可重复的。
3. HashMap的底层实现原理
1,底层的存储结构
底层开始时没有创建数组,当首次调用put时创建一个长度为16的一维数组Entry[] table,数组中每一项是个单向链表,即数组和链表的结合体;当链表长度大于一定阈值(8)时且当前的数组长度大于64时,链表转换为红黑树(在Jdk1.8的优化),这样减少链表查询时间。
2,底层的元素添加过程
HashMap在底层将key-value当成一个整体进行处理,这个整体就是一个Node对象。HashMap底层采用一个Node[]数组来保存所有的key-value对。当需要存储一个Node对象时,会根据key的hash算法来决定其在数组中的存储位置,再根据equals方法判断与该位置上的其他元素是否相同;
3,底层的扩容
当数组中的元素个数达到数组长度的0.75时,达成扩容的条件,此时会扩容为原来容量的两倍,并将原来的数据复制过来。
4. TreeMap
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
因为要对key进行排序:自然排序、定制排序(与TreeSet相同)
public static void main(String[] args) {
//定制排序
Comparator com = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Student && o2 instanceof Student){
Student s1 = (Student) o1;
Student s2 = (Student) o2;
int age = Integer.compare(s1.getAge(),s2.getAge());
if(age!=0){
return age;
}else return s1.getName().compareTo(s2.getName());
}else throw new RuntimeException();
}
@Override
public boolean equals(Object obj) {
return false;
}
};
TreeMap treeset = new TreeMap(com);
Student s1 = new Student("张三",18);
Student s2 = new Student("李四",19);
Student s3 = new Student("王五",17);
Student s4 = new Student("赵六",20);
treeset.put(s1,23);
treeset.put(s2,28);
treeset.put(s3,17);
treeset.put(s4,76);
System.out.println(treeset);
}
5. Map常用方法
方法 | 作用 |
---|---|
Object put(Object key,Object value) | 将指定key-value添加到当前map对象中(若此时key值已经存在则修改对应的value值) |
Object get(Object key) | 获取指定key对应的value值 |
Object remove(Object key) | 移除指定key的key-value对,并返回value |
void clear() | 清空当前所有数据 |
void putAll(Map m) | 将m中的所有key-value对存放到当前map中 |
boolean containKey(Object key) | 是否包含指定的key |
boolean containKey(Object value) | 是否包含指定的value |
int size() | 返回map中keyy-entry对的个数 |
代码示例:
public static void main(String[] args) {
HashMap map = new HashMap();
map.put(new Student("张三",18),1);
map.put("AA",2);
map.put(123,3);
map.put(456,4);
System.out.println(map);
map.remove(123);
map.put(456,5);//此时会修改将kay值为"456"的value改为5
System.out.println(map.get(456));
map.containsKey(456);
map.containsValue(1);
System.out.println(map.size());
map.clear();//清空map中的所有数据
map.isEmpty();
}
6. Map中的遍历
元视图操作方法
- Set keySet():返回key构成的Set集合
- Collection values():返回所有value构成的Collection集合
- Set entrySet():返回所有返回所有value构成的Collection集合
代码示例:
public static void main(String[] args) {
HashMap map = new HashMap();
map.put(new Student("张三",18),1);
map.put("AA",2);
map.put(123,3);
map.put(456,4);
Set set = map.keySet();//返回key构成的Set集合
Iterator iterator = set.iterator();
/*while (iterator.hasNext()){
System.out.println(iterator.next());
}*/
Collection values = map.values();//返回所有value构成的Collection集合
Iterator iterator1 = values.iterator();
/*while (iterator1.hasNext()){
System.out.println(iterator1.next());
}*/
//利用kaySet()与values()组合输出key--value
while (iterator.hasNext()&&iterator1.hasNext()){
Object obj = iterator.next();
Object obj1 = iterator1.next();
System.out.println(obj+"==="+obj1);
}
//利用entrySet输出key=value
Set entry = map.entrySet();//返回所有value构成的Collection集合
Iterator iterator2 = entry.iterator();
while (iterator2.hasNext()){
System.out.println(iterator2.next());
//增强for循环输出key=value
for (Object o : map.entrySet()) {
System.out.println(o);
}
}
}
🎉文章到这里就结束了,感谢诸佬的阅读。🎉
💕欢迎诸佬对文章加以指正,也望诸佬不吝点赞、评论、收藏加关注呀😘