【JavaSE-07】:集合
一、概述
- 数组在内存存储方面的特点:
- 数组一旦初始化以后,长度就确定了。
- 数组声明的类型,就决定了进行元素初始化时的类型,保存和操作的必须为同一类型的元素。
- 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
- 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用。
- 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
- 集合:
- 可以动态保存任意多个对象,使用比较方便;
- 提供了一系列方便的操作对象的方法:如add、remove、set、get等;
- 使用集合添加、删除元素的代码更简洁。
Java 集合可分为 Collection 和 Map 两种体系
1.单列集合:
2.双列集合:
二、Collection接口和常用方法
1. Collection常用方法
常用方法 | 方法名 |
---|---|
1、添加 | add(Object obj) addAll(Collection coll) |
2、获取有效元素的个数 | int size() |
3、清空集合 | void clear() |
4、是否是空集合 | boolean isEmpty() |
5、是否包含某个元素 | boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象; boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。 |
6、删除 | boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素 boolean removeAll(Collection coll):取当前集合的差集 |
7、取两个集合的交集 | boolean retainAll(Collection c):把交集的结果存在当前集合中,不 影响c |
8、集合是否相等 | boolean equals(Object obj) |
9、转成对象数组 | Object[ ] toArray() |
10、获取集合对象的哈希值 | hashCode() |
11、遍历 | iterator():返回迭代器对象,用于集合遍历 |
2. Collection接口遍历元素的方式1——使用Iterator(迭代器)
Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了 Iterator 接口的对象。
Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建 Iterator 对象,则必须有一个被迭代的集合。
Iterator iterator = coll.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
注意:在调用 iterator.next() 方法之前必须要调用 iterator.hasNext()进行检测。若不调用,且下一条记录无效,直接调用 iterator.next()会抛出NoSuchElementException异常。
● 在调用循环结束后,iterator迭代器指向最后一个元素,再次使用 iterator.next( ) 方法会报错。
Iterator iterator = coll.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
iterator.next();//报异常:java.util.NoSuchElementException
如果希望再次遍历,Collection集合需要重新调用 iterator() 获取一个新的迭代器对象,重置迭代器,集合对象每次调用 iterator() 方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
Iterator iterator = coll.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
if(obj.equals("Tom")){
iterator.remove();
}
}
注意:
- Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的 remove 方法,不是集合对象的 remove 方法。
- 如果还未调用 next() 或在上一次调用 next 方法之后已经调用了 remove 方法, 再调用 remove 都会报异常 IllegalStateException。
3. Collection接口遍历元素的方式2——增强型循环for
- 遍历操作不需获取Collection或数组的长度,无需使用索引访问元素。
- 遍历集合的底层调用Iterator完成操作。可以理解为简化版的 iterator 迭代器遍历。
- 它也可以用来遍历数组。
三、 Collection子接口之一:List接口
由于数组的一些局限性,常采用List替代数组。
● List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
● List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
● JDK API中List接口的实现类常用的有:ArrayList、LinkedList 和 Vector。
1. List接口方法
List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来操作集合元素的方法。
序号 | 方法 |
---|---|
1 | void add(int index, Object ele):在index位置插入ele元素 |
2 | boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来 |
3 | Object get(int index):获取指定index位置的元素 |
4 | int indexOf(Object obj):返回obj在集合中首次出现的位置 |
5 | int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置 |
6 | Object remove(int index):移除指定index位置的元素,并返回此元素 |
7 | Object set(int index, Object ele):设置指定index位置的元素为ele |
8 | List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex 位置的子集合 |
2. List实现类之一:ArrayList
● ArrayList 是 List 接口的典型实现类、主要实现类
● 本质上,ArrayList是对象引用的一个"变长"数组
ArrayList的底层操作机制源码分析:(重点)
扩容机制:
- ArrayList中维护了一个Object类型的数组elementData
transient Object[] elementData;// transient 表示瞬间,短暂的,表示该属性不会被序列化
- 当创建ArrayList对象时,如果使用的是无参构造器,则初始 elementData 容量为0,在第1次添加元素时,则扩容 elementData 为10,如此后需要再次扩容,则扩容 elementData 为1.5倍。
- 如果使用的是指定大小的有参构造器,则初始elementData的容量为指定大小,如果需要扩容,则直接扩容为elementData的1.5倍。
private int size;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
//ArrayList的无参构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //创建了一个空的elementData数组
}
//ArrayList的有参构造器
public ArrayList(Collection<? extends E> c) {
this.elementData = c.toArray();
if ((this.size = this.elementData.length) != 0) {
if (this.elementData.getClass() != Object[].class) {
this.elementData = Arrays.copyOf(this.elementData, this.size, Object[].class);
}
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
第1次添加元素时:
public boolean add(E e) {
++this.modCount;
this.add(e, this.elementData, this.size);// 初始默认值 this.size:0
return true;
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) {
elementData = this.grow();
}
elementData[s] = e; //先确定是否需要扩容,再赋值
this.size = s + 1;
}
● 初始状态时,elementData长度默认为0的空数组;
● 第一次赋值时,先使用modCount 来记录集合被修改的次数;
然后调用add方法:
1)先判断是否需要扩容(元素数量等于数组长度);需要扩容则调用 grow() 方法扩容,第一次扩容后新数组长度为 10,第二次及之后扩容为为旧数组的1.5倍;
判断规则:
若第一次赋值的元素数量少于10,( minCapacity=元素个数+1<=10),利用扩容机制,设置新数组长度为10;
若第一次赋值的元素数量大于10,( minCapacity=元素个数+1>10),利用扩容机制,设置新数组长度为当前添加元素的数量长度;
2)再利用copeof()方法拷贝原来的数据,再赋值
● 第二次及之后赋值时,先记录修改次数,然后调用add方法:
先判断是否需要扩容;
1)不需要扩容时,直接赋值;
2)需要扩容则调用grow()方法扩容,扩容为为旧数组的1.5倍;再拷贝原来的数组元素,再赋值。
(此处有检查最大容量的判断)
扩容机制实现源码:
private Object[] grow() {
return this.grow(this.size + 1);
}
private Object[] grow(int minCapacity) {
return this.elementData = Arrays.copyOf(this.elementData, this<