list:[小明, 卖托儿索的小火柴]
Process finished with exit code 0
我这里初始化的时候创建了一个无参构造,所以数组的初始大小为:10,添加两个元素的时候并不会触发扩容机制。
第二种:add(int index, E element)
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
故名思意,看参数就应该能大致的猜出这个方法是干什么的,没错,他就是插入指定位置元素,他的插入和第一个差不多,唯一的区别就是第一个是往后添加,这里是按index添加到这指定下表位置,然后将其他的元素往后移,也就是System.arraycopy(elementData, index, elementData, index + 1, size - index)。
ArrayList list = new ArrayList(10);
list.add(“小明”);
list.add(“卖托儿索的小火柴”);
list.add(“海阔天空”);
list.add(5,“逆天而行”);
System.out.println(“list:” + list);
你们可以猜到执行的结果吗?执行结果就是报错,为什么呢?源码里面有这么一个方法
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
判断插入的位置是否大于elementData的数组长度或者是否小于0,由于我这里只添加了两个元素,所以size应该是3,我们添加的下标却是5,所以就会抛出异常
Exception in thread “main” java.lang.IndexOutOfBoundsException: Index: 5, Size: 2
at java.util.ArrayList.rangeCheckForAdd(ArrayList.java:661)
at java.util.ArrayList.add(ArrayList.java:473)
at com.ymy.list.MyArrayList.main(MyArrayList.java:18)
Process finished with exit code 1
我们修改一下代码,将下表修改成1
ArrayList list = new ArrayList(10);
list.add(“小明”);
list.add(“卖托儿索的小火柴”);
list.add(“海阔天空”);
list.add(1,“逆天而行”);
System.out.println(“list:” + list);
这个时候我们再来看运行结果
list:[小明, 逆天而行, 卖托儿索的小火柴, 海阔天空]
Process finished with exit code 0
我们发现逆天而行被添加到了下标为1的位置,而卖托儿索的小火柴,和海阔天空相应的往后移了一位。
之前没有说清楚size与elementData的关系,size表示的是elementData数组中已经存放了多少元素,而elementData.length表示ArrayList的初始数组大小,请不要搞混.。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
-
? EMPTY_ELEMENTDATA
- Arrays.copyOf(elementData, size);
}
}
这个方法就是判断已经存在数组中的元素个数(size)和数组初始化的大小(elementData.length)做对比,如果小于初始化值就去掉多余的,返回一个elementData大小等于size,实现的方式就是通过拷贝的形式。
ArrayList list = new ArrayList(10);
list.add(“小明”);
list.add(“卖托儿索的小火柴”);
list.trimToSize();
System.out.println(“list:” + list);
为了能看到我说的,我们断点调试走一波
我们发现走到断点的那一行size=2,elementData.length= 10,下面就是判断了,很明显2<10,所以这里会执行数据拷贝,拷贝完成之后我们在看结果
清除了多余没用的元素下标,但是这个方法大家在使用的时候还是慎重比较好,如果你清除完成之后又想添加数据,这个时候ArrayList就会执行扩容操作了,这是需要进行数据拷贝的,慎重哦。
ensureCapacity(int minCapacity)
源码如下
public void ensureCapacity(int minCapacity) {
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);
}
}
大致意思:当你初始化了一个大小为10的初始数组之后,并添加了5条数据,这个时候你发现10可能不够,要是数组大小在大一点就好了,ensureCapacity就是解决这个问题的,他会扩大你指定的大小,但是扩大之后数组的大小是不是你指定的大小这个是不确定的,因为ensureExplicitCapacity(minCapacity);的源码在上面也看到了,他会现在原来的数组大小的基础上扩大1.5倍,然后在和你传入的数值做对比,如果大于你传入的,那么使用旧数组(elementData)大小的1.5倍作为新数组的大小,如果小于你传入的数值,这个时候就会以你传入的大小作为数组(elementData)的大小,这点一定要搞清楚哦,不然的话,你会发现你明明设置了值,但是最后数组的大小却和你设置的不一样,就会感觉是你的代码写的有问题。
我们先来看一个扩容小于原数组大小1.5倍的数值:12
ArrayList list = new ArrayList(10);
list.add(“小明”);
list.add(“卖托儿索的小火柴”);
list.ensureCapacity(12);
System.out.println(“list:” + list);
我们发现elementData的大小并不是我们传入的12,而是15,要注意哦
我们再来看看扩容大于原始数组大小1.5倍的数值:20
ArrayList list = new ArrayList(10);
list.add(“小明”);
list.add(“卖托儿索的小火柴”);
list.ensureCapacity(20);
System.out.println(“list:” + list);
在这里我在贴出一下导致这两种原因的代码在哪里
返回当前ArrayList已经添加了多少条元素,这个不用多说,相信大家都知道。
public boolean isEmpty() {
return size == 0;
}
判断ArrayList是否添加了数据,但是这点需要注意一下,这里只能判断是否存在元素,不能判断ArrayList是否为空,这点需要注意,如果使用这个方法判断空的话就报错哦。
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
判断ArrayList所有元素中是否存在当前元素,如果是对象,判断的就是引用地址了,这里需要注意,如果我们的ArrayList的泛型是对象,那么最好重写一下equals和hashcode方法,举个例子
没有重写equals()与 hashCode()
ArrayList list = new ArrayList(10);
//用户插入集合的数据
User user1 = new User();
user1.setUserName(“小明”);
user1.setSex(1);
user1.setAge(18);
//用于对比的数据
User user2 = new User();
user2.setUserName(“小明”);
user2.setSex(1);
user2.setAge(18);
list.add(user1);
System.out.println(“是否包含user1:” + list.contains(user1));
System.out.println(“是否包含user2:” + list.contains(user2));
是否包含user1:true
是否包含user2:false
Process finished with exit code 0
看到输出结果了吧,判断是否包含user1结果为:true;判断是否包含user2的结果为:false,那是因为往list中添加的是user1,所以比较user1的时候他们都是同一个引用地址,所以返回true,而user2是新new出来的,他们是两个完全不相同的对象,内存地址肯定也不相同,所以这个时候肯定就返回false,因为user1和user2里面存放的数据都是一样的,有时候我们只需要判断内容是否相等,并不需要判断内存地址是否相等的时候需要怎么做呢?
重写equals()与 hashCode()
改造一下我们的User对象
package com.ymy.entity;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.Objects;
@Getter
@Setter
@ToString
public class User {
private String userName;
private Integer age;
private Integer sex;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(userName, user.userName) &&
Objects.equals(age, user.age) &&
Objects.equals(sex, user.sex);
}
@Override
public int hashCode() {
return Objects.hash(userName, age, sex);
}
}
测试代码还是不变,我们在运行查看结果
是否包含user1:true
是否包含user2:true
Process finished with exit code 0
总结就是一句话,ArrayList引用对象的时候如果没有重写equals()与 hashCode()对比的就是内存地址,如果重写了equals()与 hashCode(),对比的就是实实在在的数据。请拿小本本记好,这个要考。
查找元素所在的下标,如果查找的是对象,默认比较的是内存地址这点和contains(Object o)一样。
源码如下
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;
}
如果查找的内容为空,那么这个就会返回第一个元素为空的下标,否者返回数组中第一次出现查找元素的下标。
没有重写equals()与 hashCode()
ArrayList list = new ArrayList(10);
//用户插入集合的数据
User user1 = new User();
user1.setUserName(“小明”);
user1.setSex(1);
user1.setAge(18);
//用于对比的数据
User user2 = new User();
user2.setUserName(“小明”);
user2.setSex(1);
user2.setAge(18);
list.add(user1);
System.out.println(“是否包含user1:” + list.indexOf(user1));
System.out.println(“是否包含user2:” + list.indexOf(user2));
是否包含user1:0
是否包含user2:-1
Process finished with exit code 0
很明显查找user1的时候是同一个内存地址,所以返回了对应的下标,而user2与user1不是同一个内存地址,所以返回了-1。
重写equals()与 hashCode()
重写的方法和contains()一样,我们直接看结果即可
是否包含user1:0
是否包含user2:0
Process finished with exit code 0
所以一定要区分你需要查找的是值相同还是地址相同,不然就会导致bug哦。
与indexOf()效果一样,都是查找元素所在的下标,但是又有一点区别,那就是lastIndexOf()返回的是最后一次出现的下标位置。
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;
}
这个使用和indexOf一样,这里就不做demo展示了。
克隆一个ArrayList
源码如下
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn’t happen, since we are Cloneable
throw new InternalError(e);
}
}
使用方式
ArrayList list = new ArrayList(10);
//用户插入集合的数据
User user1 = new User();
user1.setUserName(“小明”);
user1.setSex(1);
user1.setAge(18);
list.add(user1);
System.out.println(“list1:”+list);
ArrayList list2 = (ArrayList) list.clone();
System.out.println(“list2:”+list2);
运行结果
list1:[User(userName=小明, age=18, sex=1)]
list2:[User(userName=小明, age=18, sex=1)]
Process finished with exit code 0
将list拷贝到list2,但是这里需要注意一点,这里的拷贝属于浅拷贝,list2和list1共享一个User对象,这是需要特别注意的。
将ArrayList转换成数组
源码
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
使用方式
ArrayList list = new ArrayList(10);
list.add(“小明”);
list.add(“逆天而行”);
System.out.println(“list1:” + list);
Object[] array = list.toArray();
System.out.println(“array:” + array);
array[2] = “海阔天空”;
运行结果
list1:[小明, 逆天而行]
array:[Ljava.lang.Object;@3a71f4dd
Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException: 2
at com.ymy.list.MyArrayList.main(MyArrayList.java:17)
Process finished with exit code 1
ArrayList转数组没有问题,但是在数组赋值的时候却报错了,这一点需要注意,这里的数组长度就是ArrayList的数组实际长度,ArrayList的长度是2,下标最大为1,但是我们赋值的时候给的下标是2,所以就会抛出数组越界的错误。
根据下表获取元素信息
源码
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
第一步校验下标是否越界,然后返回对应下标元素信息。
使用方法
ArrayList list = new ArrayList(10);
list.add(“小明”);
list.add(“逆天而行”);
System.out.println(“list1:” + list);
String name = list.get(1);
System.out.println(“name:”+name);
运行结果
Connected to the target VM, address: ‘127.0.0.1:62855’, transport: ‘socket’
list1:[小明, 逆天而行]
name:逆天而行
Disconnected from the target VM, address: ‘127.0.0.1:62855’, transport: ‘socket’
Process finished with exit code 0
这里面的下标一定不能大于elementData的size,否者就会抛出数组越界。
在指定下标添加元素
源码
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
添加的下标不能越界,他会将你的元素添加到数组的指定下标,并且返回被替换的元素,这里是替换哦,被替换的元素不会往后移,这点需要特别注意。
使用方法
ArrayList list = new ArrayList(10);
list.add(“小明”);
list.add(“逆天而行”);
System.out.println(“list:” + list);
String name = list.set(1,“海阔天空”);
System.out.println(“name:”+name);
System.out.println(“修改后的list:”+list);
结果
list:[小明, 逆天而行]
name:逆天而行
修改后的list:[小明, 海阔天空]
Process finished with exit code 0
删除指定下标的元素,并返回被删除的元素值
源码
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;
}
他在删除了指定下标之后,那这个下标的位置就会处于空缺,这个时候ArrayList做了一件事,那就是将数组进行重新排序,实现的方式就是数据拷贝,使用一个新的数组接受两段数据,一段是删除下标之前的数据,一段是删除下表之后的数据,整合到一个新的数组,然后赋值到原数组中。这里只需要了解一下即可,最后返回了被删除的元素值。
使用方法
ArrayList list = new ArrayList(10);
list.add(“小明”);
list.add(“逆天而行”);
System.out.println(“list:” + list);
String name = list.remove(1);
System.out.println(“name:”+name);
System.out.println(“删除后的list:”+list);
运行结果
list:[小明, 逆天而行]
name:逆天而行
删除后的list:[小明]
Process finished with exit code 0
通过元素值删除数组中存在的元素,这种删除比较耗时间,为什么这么说呢?请看源码
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 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
}
首先,他会判断删除的元素是否为空,如果是空,那么它将删除数组中第一个空元素,然后直接返回,如果你要删除的元素不为空,那这个时候就会循环数组,找到你要删除的第一个元素进行删除,但是删除的时候有需要做数据拷贝,如果不做的话,数组下标就会错乱,最后返回删除结果。
使用方法
ArrayList list = new ArrayList(10);
list.add(“小明”);
list.add(“逆天而行”);
System.out.println(“list:” + list);
boolean remove = list.remove(“逆天而行”);
System.out.println(“是否删除成功:”+remove);
System.out.println(“删除后的list:”+list);
运行结果
list:[小明, 逆天而行]
是否删除成功:true
删除后的list:[小明]
Process finished with exit code 0
这个方法比较简单,就是将数组中所有的元素都设置为null,然后将size设置为0。
源码
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
使用方法
ArrayList list = new ArrayList(10);
list.add(“小明”);
list.add(“逆天而行”);
System.out.println(“list:” + list);
list.clear();
System.out.println(“删除后的list:”+list);
运行结果
list:[小明, 逆天而行]
删除后的list:[]
Process finished with exit code 0
addAll(Collection<? extends E> c)
将其他的集合添加到当前集合,这里添加方式是通过拷贝实现的。
源码如下
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;
}
ensureCapacityInternal(size + numNew);这行代码是不是经常看到,不用我多说想必大家也知道了,没错,就是判断当前的集合是否可以装下这些数据,是否需要扩容,接下来就是数据添加了,添加的方式就是通过数据拷贝,这里的拷贝同样属于浅拷贝。
使用方法
ArrayList list = new ArrayList(10);
list.add(“小明”);
list.add(“逆天而行”);
System.out.println(“list:” + list);
ArrayList list2 = new ArrayList();
list2.add(“随风起舞”);
lis2t.add(“穷凶极恶”);
list.addAll(list2);
System.out.println(“添加之后的lsit:”+list);
运行结果
list:[小明, 逆天而行]
添加之后的lsit:[小明, 逆天而行, 随风起舞, 穷凶极恶]
Process finished with exit code 0
addAll(int index, Collection<? extends E> c)
这个方法其实和上面那个也是大同小异,就是添加集合,但是这里的添加方式有点区别,这里可以指定下标。
源码如下
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;
}
它会往你指定的下标添加集合元素,原本属于当前下标的元素向后移动,移动方式也是通过数据拷贝事项的。
使用方法
ArrayList list = new ArrayList(10);
list.add(“小明”);
list.add(“逆天而行”);
list.add(“铁血无双”);
System.out.println(“list:” + list);
ArrayList list2 = new ArrayList();
list2.add(“随风起舞”);
list2.add(“穷凶极恶”);
list.addAll(1,list2);
System.out.println(“添加之后的lsit:”+list);
运行结果
list:[小明, 逆天而行, 铁血无双]
添加之后的lsit:[小明, 随风起舞, 穷凶极恶, 逆天而行, 铁血无双]
Process finished with exit code 0
我们插入的下标位置为1,这个时候ArrayList就将list2这两个元素从下标1开始往后田间,冲突的元素就往后移,直到没有冲突为止。