目录
一、Java集合简介
1.什么是集合?
什么是集合?集合就是“由若干个确定的元素所构成的整体”,在程序中,一般代表保存若干个元素(数据)的某种容器类。在数学中,我们经常遇到集合的概念。例如:
有限集合:
一个班所有的同学构成的集合;
一个网站所有的商品构成的集合;
...
无限集合:
全体自然数集合:1,2,3,……
有理数集合;
实数集合;
...
为什么要在计算机中引入集合呢?这是为了便于处理一组类似的数据,例如:
计算所有同学的总成绩和平均成绩;
列举所有的商品名称和价格;
……
在Java
中,如果一个Java
对象可以在内部持有(保存)若干其他Java
对象,并对外提供访问接口,我们把这种Java
对象的容器称为集合。很显然,Java
的数组也可以看作是一种集合:
String[] ss = new String[10]; // 可以持有10个String对象
ss[0] = "Hello"; // 可以放入String对象
String first = ss[0]; // 可以获取String
既然Java
提供了数组这种数据类型,可以充当集合,那么,我们为什么还需要其他集合类?这是因为数组有如下限制:
数组初始化后大小不可变;
数组只能按索引顺序存取;
因此,我们需要各种不同类型的集合类来处理不同的数据,例如:
可变大小的顺序链表;
保证无重复元素的集合;
...
2.集合接口
Java
标准库自带的java.util
包提供了集合相关的接口和实现类:Collection接口
,它是除Map接口
外所有其他集合类的根接口。
collection特点:
(1)collection接口是单列集合的根接口,英文含义集中,收集。
(2)单列集合是将数据进行一个一个的存储,存储的是值
Java
的java.util
包主要提供了以下三种类型的集合:
List
:一种有序列表的集合;
Set
:一种保证没有重复元素的集合;
Map
:一种通过键值(key-value)查找的映射表集合,例如,根据Student
的name
查找对应Student
的Map
。
Java
集合的设计有几个特点:首先实现了接口和实现类相分离,例如,有序表的接口是List
,具体的实现类有ArrayList
,LinkedList
等;其次支持泛型,我们可以限制在一个集合中只能放入同一种数据类型的元素,例如:
List<String> list = new ArrayList<>(); // 只能放入String类型
最后,Java访问集合总是通过统一的方式——迭代器(Iterator
)来实现,它最明显的好处在于无需知道集合内部元素是按什么方式存储的。
另外,由于Java
的集合设计非常久远,中间经历过大规模改进,我们要注意到有一小部分集合类是遗留类,不应该继续使用:
Hashtable
:一种线程安全的Map
实现;
Vector
:一种线程安全的List
实现;
Stack
:基于Vector
实现的LIFO
的栈;
3.小结
(1)Java的集合类定义在java.util
包中;
(2)支持泛型,主要提供了3种集合类,包括List
,Set
和Map
;
(3)Java集合使用统一的Iterator
遍历,尽量不要使用遗留接口;
二、List集合
1.List集合简介
在集合类中,List
是最基础的一种集合:它是一种有序列表。List
的行为和数组几乎完全相同:List
内部按照放入元素的先后顺序存放,每个元素都可以通过索引确定自己的位置,List
的索引和数组一样,从0
开始。
List
和数组类似,也是有序结构。还可重复。但是,如果我们使用数组,在添加和删除元素的时候,会非常不方便。例如,从一个已有的数组{'A', 'B', 'C', 'D', 'E'}
中删除索引为2
的元素:这个“删除”操作实际上是把'C'
后面的元素依次往前挪一个位置,而“添加”操作实际上是把指定位置以后的元素都依次向后挪一个位置,腾出来的位置给新加的元素。这两种操作,用数组实现非常麻烦。
┌───┬───┬───┬───┬───┬───┐
│ A │ B │ C │ D │ E │ │
└───┴───┴───┴───┴───┴───┘
│ │
┌───┘ │
│ ┌───┘
│ │
▼ ▼
┌───┬───┬───┬───┬───┬───┐
│ A │ B │ D │ E │ │ │
└───┴───┴───┴───┴───┴───┘
因此,在实际应用中,需要增删元素的有序列表,我们使用最多的是ArrayList
。实际上,ArrayList
在内部使用了数组来存储所有元素。例如,一个ArrayList
拥有5个元素,实际数组大小为10
(即有5个空位):
size=5
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ C │ D │ E │ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
0 1 2 3 4 5 6 7 8 9
当需要在指定索引位置(索引=2)添加一个元素(X)到ArrayList
时,ArrayList
自动移动需要移动的元素:
size=5
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ │ C │ D │ E │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
0 1 2 3 4 5 6 7 8 9
然后,往内部指定索引的数组位置添加一个元素,然后把size
加1
:
size=6
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ X │ C │ D │ E │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
0 1 2 3 4 5 6 7 8 9
当继续添加元素,但是数组已满,没有空闲位置的时候,ArrayList
先创建一个更大的新数组(新数组长度为原数组的1.5倍),然后把旧数组的所有元素复制到新数组,紧接着用新数组取代旧数组:
size=10
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ X │ C │ D │ E │ F │ G │ H │ I │ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
现在,新数组就有了空位,可以继续添加一个元素到数组末尾,同时size
加1
:
size=11
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ X │ C │ D │ E │ F │ G │ H │ I │ J │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
可见,ArrayList
把添加和删除的操作封装起来,让我们操作List
类似于操作数组,却不用关心内部元素如何移动。我们观察List<E>
接口,可以看到几个主要的接口方法:
(1)在末尾添加一个元素:boolean add(E e)
(2)在指定索引添加一个元素:boolean add(int index, E e)
(3)删除指定索引的元素:E remove(int index)
(4)删除某个元素:boolean remove(Object e)
(5)获取指定索引的元素:E get(int index)
(6)获取链表大小(包含元素的个数):int size()
三、ArrayList容器类
ArrayList是List接口的实现类。
1.初始化
初始化分为无参初始化和有参初始化两种。
1.1无参初始化
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
底层elementData数组指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组,默认为空数组时,第一次添加元素,扩容为10。
扩充:泛型只是在编译的时候进行规则校验,不将代码只有转换为对应的数据类型。
//泛型类型:一定是引用数据类型 //泛型使用好处:帮助我们建立类型安全的结合,使用时传入具体的类型 //代码的可读性增强,程序更安全
1.2有参初始化
(1)ArrayList (int initialCapacity)
初始化时根据数字,如果>0则是对应数字内存;
== 0 是EMPTY_ELEMENTDATA数组;
< 0 时报错;
可以根据预估规模,设置初始值,可以减少扩容频次。
(2)ArrayList (Collection <? extends E >c)
2.数据结构
底层是基于数组实现的,随着元素的增加而动态扩容;
使用场景:适合数据连续性遍历,读多写少的场景。
3.常用方法
3.1增加元素
(1)boolean add(E e) 添加指定元素到集合尾部
ArrayList<String> arrayList=new ArrayList<>();
//添加元素boolean add(E,e)添加指定元素到集合尾部
boolean b1=arrayList.add("g关羽");
boolean b2=arrayList.add("z张飞");
System.out.println(arrayList);
(2)void add(int index, E element)添加新元素到集合指定的下标位置
arrayList.add(2,"rosie");
System.out.println(arrayList);
(3)boolean addAll(Collection<? extends E> c) 添加集合C 内所有元素到当前集合
List<String> list=Arrays.asList("lisa","jisoo","rosie","jennie");
arrayList.addAll(list);
System.out.println("集合内容为:"+arrayList);
(4)boolean addAll(int index,Collection<? extends E> c) 从指定的位置开始,将指定 collection 中的所有元素插入到此列表中
//使用Arrays快速生成一个List类型的集合
List<String> list= Arrays.asList("a阿拉善","b北京","d丹东");
//boolean addAll(int index,Collection<? extends E> c)
//从指定的位置开始,将指定Collection中的所有元素插入到此列表中
boolean b6=arrayList.addAll(0,list);
System.out.println("添加集合是否成功:"+b6);
System.out.println(arrayList);
3.2查找元素
(1)int size()--查集合的长度,具体的元素个数
System.out.println("长度: " + arrayList.size());
(2)E get(int index)--获取下标中的元素
String item = arrayList.get(0);
System.out.println("首元素: " + item);
System.out.println("尾元素: " + arrayList.get(arrayList.size() - 1));
(3)int indexOf(Object c)--查找找到指定元素的下标位置,如果不存在,则返回数组-1
int index = arrayList.indexOf("rosie");
System.out.println("元素下标为: " + index);
(4)boolean contains(Object c)--判断集合中是否存在元素
boolean b = arrayList.contains("rosie");
System.out.println("元素是否存在: " + b);
(5)boolean isEmpty()--判断集合是否为空
boolean b1 = arrayList.isEmpty();
System.out.println("是否为空: " + b1);
(6)List<E> SubList(int fromindex,int toIndex)--按照指定下标区间截取子集合 如果下标越界,则IndexOutOfBoundsException
不要强制类型转化为ArrayList,可能会出现类型转换异常;
对父类集合元素的增加或者删除,均会导致列表的遍历、增加、删除产生ConcurrentModificationException异常;
List<String> subArrayList = arrayList.subList(0, arrayList.size());
System.out.println("截取后的集合: " + subArrayList);
(7)boolean equals(Object o)--判断两个集合中的元素是否相同
boolean b2 = arrayList.equals(subArrayList);
System.out.println("集合和截取后的集合是否相等: " + b2);
(8)使用for循环遍历
普通for循环和增强for循环。
ArrayList<String> arrayList=new ArrayList<>();
arrayList.addAll(Arrays.asList("rosie","lisa","jisoo","rosie","jennie"));
System.out.println("集合内容为:"+arrayList);
//遍历集合
//1.for
for (int i = 0; i <arrayList.size() ; i++) {
System.out.println(arrayList.get(i)+" ");
}
System.out.println();
//2.foreach
for (String str:arrayList) {
System.out.println(str+"__");
}System.out.println();
(9)使用迭代器进行遍历
Iterator<E> iterator{}--该迭代器仅提供向后遍历
ListIterator{}
//3.迭代器
//3.1获取迭代器对象
Iterator<String> itor=arrayList.iterator();
//判断是否有下一个元素
while(itor.hasNext()){
//获取下一个
String item=itor.next();
System.out.println(item+"**");
}
System.out.println();
//3.2获取list迭代器对象
ListIterator<String> listIterator=arrayList.listIterator(arrayList.size());
//判断是否有上一个元素
while(listIterator.hasPrevious()){
//获取上一个
String item=listIterator.previous();
System.out.println(item);
}
迭代器详细解释:
定义:是访问数据的模型,主要是用来遍历Collection集合。
Iterator接口详细方法:
(1)hasNext():判断当前指针位置的下一个位置是否有元素,有则返回true,没有则返回false。
(2)next():移动指针到下一个位置,并返回该位置的元素。
(3)remove():①在使用迭代器的remove()操作时,会将更新后的modCount给expectedModCount,两者会得到同步,但是在调用集合的remove()方法后,两个不会进行同步,进而导致在checkForComodification()校验时不通过,抛出java.util.ConcurrentModificationException异常。
②所以,在单线程下使用迭代器是没有问题的,但是在多线程下同时操作集合就不允许了,可以通过fail-fast快速失败机制,快速判断是否存在同时操作问题。因此,集合在多线程下使用是不安全的。
迭代器总结:
(1)对任何集合都采用同一种访问模型
(2)调用者对集合内部结构可以不清楚
(3)迭代器类返回的迭代器对象清楚要怎么进行集合内部访问
3.3 修改元素
(1)oldE set(int index, E element)--用指定的元素替代此列表中指定位置上的元素。
ArrayList<String> arrayList=new ArrayList<>();
arrayList.addAll(Arrays.asList("朱元璋","朱祁镇","朱棣","朱棣","朱高炽"));
System.out.println("集合内容为:"+arrayList);
//1.oldE set(int index, E element):用指定的元素替代此列表中指定位置上的元素。
String str=arrayList.set(2,"乌萨奇");
int index=arrayList.indexOf("朱元璋");
if(index>=0){
arrayList.set(index,"郑和");
}
System.out.println("修改后的元素为:"+str);
3.4 删除元素
(1)E remove(int index)--根据指定索引删除元素,并把删除的元素返回
如果下标超过集合的最大下标,输出IndexOutOfBoundsException
//E remove(int index):根据指定索引删除元素,并把删除的元素返回
String item =arrayList.remove(0);
System.out.println("删除的元素为:"+item);
(2)boolean remove(Object o):从集合中删除指定的元素,删除一个就返回(有多个就只删除一个)
如果查到或者删除1个就返回,并不是删除所有相同的集合元素
//boolean remove(Object o):从集合中删除指定的元素,删除一个就返回
boolean b=arrayList.remove("朱棣");
System.out.println("删除的元素是否成功:"+b);
(3)void clear():删除集合中的所有元素,此集合仍旧存在,集合元素长度变0。
//void clear():删除集合中的所有元素,此集合仍旧存在,集合元素长度变0
arrayList.clear();
System.out.println("操作后的集合为:"+arrayList);
(4)boolean removeAll(Collection<?> c)--- 差集
从集合中删除一个指定的集合元素: 删除A集合中存在B集合中的所有相同的元素,如果有删除返回True。
ArrayList<String> arrayList1=new ArrayList<>();
ArrayList<String> arrayList2=new ArrayList<>();
arrayList1.addAll(Arrays.asList("朱元璋","朱祁镇","朱棣","朱棣","朱高炽"));
arrayList2.addAll(Arrays.asList("孙皇后","朱祁镇","朱棣","李时珍","朱高炽"));
System.out.println("arraylist1:"+arrayList1);
System.out.println("arraylist2:"+arrayList2);
//boolean removeAll(Collection<?> c)--- 差集 谁调用修改谁
boolean b1=arrayList1.removeAll(arrayList2);
System.out.println(b1);
System.out.println("arrayList1和arrayList2的差集为:"+arrayList1);
(5)boolean retainAll(Collection<?> c) --交集
保留集合A和集合B中相同的元素,删除不同的元素,谁调用操作的是就是谁
//boolean retainAll(Collection<?> c) --交集 谁调用修改谁
boolean b=arrayList1.retainAll(arrayList2);
System.out.println(b);
System.out.println("arrayList1和arrayList2的交集为:"+arrayList1);
3.5 其他方法
(1)list.clone()克隆一个集合,得到的一个长度,个数,内容,顺序完全一致的集合,单是复制了一份。
//Object clone() 克隆一个集合,得到一个长度,个数,内容,顺序完全一致的集合,单是复制了一份
ArrayList<String> list1=new ArrayList<>();
list1.addAll(Arrays.asList("zyz朱元璋","zqz朱祁镇","zd朱棣","lsz李时珍","zgc朱高炽","shh孙皇后"));
//克隆
Object obj=list1.clone();
if (obj instanceof ArrayList){
ArrayList<String> cloneList=(ArrayList<String>)obj;
System.out.println(cloneList);
}
(2)list.sort() 对list中的内容进行排序,需要自定义排序规则。
//list.sort() 对list中的内容进行排序,需要自定义排序规则
list1.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//先按照字符串的长度排,长度相同按照内容排序
if (o1.length()==o2.length()){
return o1.compareTo(o2);
}else{
return o1.length()-o2.length();
}
}
});
System.out.println("排序后的结果为:"+list1);
(3)转换--Object[] toArray()--T[] toArray(T[] a)
//Object[] toArray()
Object[] objs=list1.toArray();
System.out.println("转数组后:"+Arrays.toString(objs));
//T[] toArray(T[] a),返回的数组长度以集合对象或者传入的参数的长度较长的那个为准
String[] strs=list1.toArray(new String[10]);
System.out.println(Arrays.toString(strs));
4.扩容机制
(1)无参构造的对象初始容量为0,第一次添加元素时,扩容内存大小容量为10。
(2)有参构造时,根据参数设置内存大小
在进行 arrayList1.add()时,如果数组容量不足时,按照原容量的1.5倍进行扩容增长。
(3)最大容量为:Integer.MAX_VALUE - 8 到 Inter.Max_Value之间,如果超出。则输出OutMemoryError错误。
5.总结特点
(1)在内存中分配连续的空间,实现了长度可变的动态数组,有序集合(插入的顺序==输出的顺序)。
(2)优点:查效率高,增加和删除的效率比较低。
(3)缺点:添加和删除需要大量移动元素效率,按照内容查询效率低,线程不安全。
四、LinkedList类
1.初始化
(1)无参初始化--LinkedList() 构造一个空列表。
(2)有参初始化--LinkedList(Collection<? extends E> c)。
2.数据结构
(1)是双向链表:链表由若干个Node对象组成,在每个节点里存了上一个节点和下一个节点的地址。
(2)扩容:由于采用链表结构,每次添加元素,都在创建新的Node节点并进行分配空间,所以不存在扩容。
3.常用方法
3.1增加元素
(1)boolean add(E,e) --添加到列表的尾部
(2)add(int index, E element) --在此列表中的指定位置插入指定的元素。
(3)void addFirst(E,e)--添加到列表的头部。
(4)void addlast(E,e)--添加到列表的尾部。
(5)boolean addAll(Collection<? extends E> c)--按照指定集合的迭代器返回的顺序将指定集合中的所有元素追加到此列表的末尾。
(6)boolean addAll(int index, Collection<? extends E> c)--将指定集合中的所有元素插入到此列表中,从指定的位置开始。
3.2查找元素
(1)int size()--查集合的长度,具体的元素个数。
(2)E get(int index)--查询某个下标对应的元素。
(3)E getLast()获取列表的尾元素。
(4)E getFirst() 获取列表的头元素。
(5)int indexOf(Object c) 查询列表中的指定元素的下标,如果不存在,则删除-1。
(6)boolean contains(Object o) 如果此列表包含指定的元素,则返回 true。
3.3修改元素
(1)set(int index, E element):用指定的元素替代此列表中指定位置上的元素。
3.4删除元素
(1)E remove():删除链表的头元素。
(2)E remove(int index) :删除链表中的指定下表的元素。
(3)boolean remove(Object o):删除指定内容的元素。
(4)removeFirst() :从此列表中删除并返回第一个元素。
(5)E removeLast() : 删除链表中的尾元素。
3.5转换为数组
(1)T[] toArray(T[] a)
3.6元素的遍历
(1)for循环:普通、foreach。
(2)迭代器:iterator、listIterator。
//底层:双链表
public class Demo01 {
public static void main(String[] args) {
//创建LinkedList的集合对象
LinkedList<String> list=new LinkedList<>();
//1.添加元素
list.add("rosie");
list.add(0,"lisa");
list.addAll(Arrays.asList("jisoo","jennie"));
list.addAll(1,Arrays.asList("hank","black"));
list.addFirst("blackpink");
list.addLast("pink");
System.out.println(list);
//2.获取元素
String item=list.get(4);
System.out.println("获取元素为:"+item);
System.out.println("首元素为:"+list.getFirst());
System.out.println("尾元素为:"+list.getLast());
System.out.println(list);
//3.删除
String item1=list.remove();
System.out.println("删除元素为:"+item1);
System.out.println("删除首元素为:"+list.removeFirst());
System.out.println("删除尾元素为:"+list.removeLast());
System.out.println(list);
// //1.遍历1--不推荐
// for (int i = 0; i <list.size() ; i++) {
// System.out.println(list.get(i));
// }
//
// //2.遍历 增强for
// for (String str: list) {
// System.out.println(str);
// }
//3.普通迭代器
// Iterator<String> itor = list.iterator();
// while(itor.hasNext()){
// System.out.println(itor.next()+"-");
// }
//3.list迭代器
// ListIterator<String> itor1 = list.listIterator(3);
// while(itor1.hasPrevious()){
// System.out.println(itor1.previous()+"*");
// }
}
}
4.总结特点
(1)采用双向链表存储方式
(2)优点:插入、删除元素效率高
(3)缺点:遍历和随机访问效率低下
(4)适用场景:适合数据频繁的添加和删除操作,写多读少的场景