删除类问题
有一个 ArrayList,数据是 2、3、3、3、4,中间有四个 3,现在我通过for (int i = 0; i < list.size(); i++)
的方式,想把值是 3 的元素删除,请问可以删除干净么?最终删除的结果是什么,为什么?删除代码如下:
以下三个问题对于 LinkedList 结果相同。
@Test
public void test18() {
List<String> list = new ArrayList<String>() {{
add("2");
add("3");
add("3");
add("3");
add("3");
add("4");
}};
// remove() 删除后,打印的结果为:[2, 3, 3, 4]
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals("3")) {
list.remove(i);
i--;
}
}
// iterator.remove() 删除后,打印结果:[2, 4]
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
if (s.equals("3")) {
iterator.remove();
}
}
// 方案3
while(list.remove("3"));
}
答:
用 remove()
不能删干净,最终的结果为 [2, 3, 3, 4],因为:每次 remove() 删除一个元素后,该元素后面的元素就会往前移动,而此时循环的 i 在不断地增长,最终会使每次删除 3 的后一个 3 被遗漏没有进行判断,导致删除不掉。 解决办法,在 remove() 后 i—
用 Iterator.remove()
可以删干净,因为它的底层源码实现在删除之后 cursor(下一个要返回的索引)仍为删除前索引值,不存在删除后遗漏判断问题。
用 while(list.remove("3"));
就可以删干净。不过性能较差。
ArrayList 和 LinkedList 应用场景不同
答:
ArrayList 更适合于快速的查找匹配,不适合频繁新增删除。
LinkedList 更适合于经常新增和删除,对查询反而很少的场景。
如果是顺序插入的话,ArrayList 比 LinkedList 快,即调用无参的 add()
LinkedList 只有随机插入的时候可能快于 ArrayList,即调用 add(int index, E element)
插入,这个可能因素决定于 ArrayList 移动的元素个数。
一般情况下,都是使用无参的 add(),所以 ArrayList 使用的多。
ArrayList 和 LinkedList 两者的最大容量
答:
ArrayList 有最大容量,为 Integer 的最大值,大于这个值 JVM 是不会为数组分配内存空间的。
LinkedList 底层是双向链表,理论上可以无限大。但源码中,LinkedList 实际大小用的是 int 类型,这也说明了 LinkedList 不能超过 Integer 的最大值,不然会溢出。
ArrayList 和 LinkedList 如何处理 null 值
答:
ArrayList 和 LinkedList 都允许新增和删除 null 值,且删除时,都是找到第一个值为 null 的元素删除。
ArrayList 和 LinedList 线程不安全,为什么
答:
当两者是非共享变量时,比如说仅仅是在方法里面的局部变量时,是没有线程安全问题的。
只有当两者是共享变量时,才会有线程安全问题。
共享变量的主要问题在于多线程环境下,所有线程任何时刻都可对数组和链表进行操作,这会导致值被覆盖,甚至混乱的情况。
如果有线程安全问题,在迭代的过程中,会频繁报 ConcurrentModificationException 的错误,意思是在我当前循环的过程中,数组或链表的结构被其它线程改变了。
ArrayList 和 LinkedList 的线程安全问题
答:
Java 源码中推荐使用 Collections.synchronizedList() 进行解决 ,Collections.synchronizedList 的返回值是 List 的,每个方法都加了 synchronized 锁,保证了在同一时刻,数组和链表只会被一个线程所修改。
或者采用 CopyOnWriteArrayList 并发 List 来解决线程安全问题。
List 集合 -> 数组
List 集合转化成数组,我们通常使用 toArray() 的有参构造,该方法有一个坑,当数组初始化大小不够时,方法内部会构造一个长度为 length 的新数组 copyOf() 并返回,而没有用给参数列表中给定的数组 copyOf。
以下 demo 用于演示坑:
@Test
public void test18() {
List<Integer> list = new ArrayList<Integer>() {{
add(1);
add(2);
add(3);
add(4);
}};
// 无参 toArray 返回的是 Object[],无法向下转化成 List<Integer>,编译都无法通过
// List<Integer> list2 = list.toArray();
Integer[] integers = new Integer[2];
Integer[] array0 = list.toArray(integers);
// 打印结果为:[null, null]
System.out.println(Arrays.toString(integers));
// 打印结果为:[1, 2, 3, 4]
System.out.println(Arrays.toString(array0));
Integer[] array1 = list.toArray(new Integer[list.size()]);
// 打印结果为:[1, 2, 3, 4]
System.out.println(Arrays.toString(array1));
Integer[] array2 = list.toArray(new Integer[list.size() + 2]);
// 打印结果为:[1, 2, 3, 4, null, null]
System.out.println(Arrays.toString(array2));
}
toArray()
的底层源码实现如下:
public <T> T[] toArray(T[] a) {
// 如果数组长度不够,按照 List 的大小进行拷贝,return 的时候返回的都是正确的数组
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
// 创建一个新的数组的运行时类型,但我的内容:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}