1、Java 中 Set 与 List 有什么不同?
List跟Set都是继承Collection接口,都是用来存储一组相同类型的元素;
List:有序,可重复。
Set:无序,不可重复。有些场景下,可以用来去重。
2、List的主要实现ArrayList,LinkedList,Vector
1)ArrayList:是一个可以改变大小的数组。当更多元素加入时,其大小可以动态增长。内部方法可以通过get与set方法进行访问。查询快,增删慢,线程不安全,效率高。
2)LinkedList:是一个双链表,在数据量很大或操作频繁的情况下,在删除跟添加元素时比Arraylist要好,但是get跟set方面弱于ArrayList。实现了Queue接口,该接口还提供更多方法,比如offer(),peek(),poll()等。查询慢,增删快,线程不安全,效率高。
offer跟add方法区别:
在一些队列有长度限制,给一个满的队列中添加元素,如果用add会报unchecked 异常,但是用offer返回false。
poll跟remove区别:
都是删除队列中的第一个元素,poll方法如果在空集合调用时不是抛出异常,只是返回null。
peck跟element区别:
都是查询出队列中的头部查询元素。在队列为空的时候,element抛出异常,peek返回null。
3)Vector:跟ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的,没有多个线程之间共享一个集合对象,那使用ArrayList是好的选择。线程安全,效率低。
Vector跟ArrayList在更多元素添加进来时需要更多的空间,Vector每次请求其大小的双倍空间,ArrayList扩容为1.5倍。
注意:默认情况下,ArrayList初始容量非常小,容器大小会扩容为初始大小10,所以如果可以预估数据量的话,分配一个较大的初始值,可以减少调整大小的开销。
3、什么是synchronzedList?跟Vector有何区别?
vector是java.util的一个类,而synchronizedList是java.util.Collections中的一个静态内部类。在多线程情景中,可以使用Vector类,也可以用Collections.synchronzedList(List list)方法返回一个线程安全的list。
List list1 = new ArrayList();
List list2 = Collections.synchronizedList(list1);
Vector list3 = new Vector();
1)add方法
Vector的实现:
public void add(int index, E element) {
insertElementAt(element, index);
}
public synchronized void insertElementAt(E obj, int index) {
modCount++;
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index
+ " > " + elementCount);
}
ensureCapacityHelper(elementCount + 1);
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
elementData[index] = obj;
elementCount++;
}
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
synchronizedList:
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
使用同步代码块调用ArrayList的add方法:
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++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
比较remove等其他方法,发现Vector使用同步方法实现,synchronizedList使用同步代码块实现。
但是在listIterator没有同步处理,但是Vector对该方法加了同步锁,所以synchronizedList来进行遍历的时候手动进行同步处理。
同步代码块跟同步方法区别:
1、同步代码块锁定范围小,锁的范围大小跟性能成反比
2、同步块可以更加准确的控制锁的作用域(锁的作用域:从锁被获取到其被释放的时间),同步方法锁的作用域是整个方法。
3、静态代码块可以选择对哪个对象加锁,但是静态方法只能给this对象加锁。
区别:
①synchronizedList很好的扩展和兼容功能,可以将所有list的子类转换为线程安全的类。
②synchronizedList来进行遍历的时候手动进行同步处理
③synchronizedList可以指定锁的对象。
4、Array.asList获取List的特点
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
asList接收泛型参数,构造一个 ArrayList,采用基本类型的数组转化后是将数组放入了构造的ArrayList中,长度是1,得到的只是一个 Arrays 的内部类,因此如果对它进行增删操作会报错。
public class Demo{
public void method(){
int[] a = new int[]{1,2,3};
List list1 = Arrays.asList(a);
Integer[] b = new Integer[]{1,2,3};
List list2 = Arrays.asList(b);
// 运行结果
System.out.println(list1.size()); // 结果:1
System.out.println(list2.size()); // 结果:3
}
如果想要对其进行add或者remove,则使用ArrayList<Integer> arrayList = new ArrayList<>(Arrays.asList(a));
5、fail-fast是什么? fail-safe又是什么? 他们之间有什么区别?
fail-fast:“快速失败”,它是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
fail-safe:安全失败,相对于fail-fast来说fail-safe采用了安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
区别:fail-safe允许在遍历的过程中对容器中的数据进行修改,而fail-fast则不允许。
6、如何在遍历的同时删除ArrayList中的元素
遍历List元素有以下三种方式:
1)使用普通for循环遍历
public class Demo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 3; i++)
list.add(i);
// list {0, 1, 2}
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
if (list.get(i) % 2 == 0) {
System.out.println(list.get(i)+" delete");
list.remove(list.get(i));
i--; //索引改变。如果索引不变,比如i=2,remove的是list[2],list[3]获取的是原来list[4]上的值。
}
}
}
}
结果:
0
0 delete
1
2
2 delete
但是这种方法由于删除的时候会改变list的index索引和size大小,可能会在遍历时导致一些访问越界的问题,因此不是特别推荐。
2)使用增强型for循环遍历
public class Demo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 3; i++)
list.add(i);
// list {0, 1, 2}
for (int num: list) {
if (num % 2 == 0) {
System.out.println(num+" delete");
list.remove(num);
}
}
}
}
结果:
可以看到删除第一个元素时是没有问题的,但删除后继续执行遍历过程的话就会抛出ConcurrentModificationException的异常。
3)使用iterator遍历
public class Demo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 3; i++)
list.add(i);
// list {0, 1, 2}
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
int num = iterator.next();
System.out.println("---"+num);
if (num % 2 == 0) {
iterator.remove();
System.out.println(num+" delete");
}
}
}
}
结果:
---0
0 delete
---1
---2
2 delete
对于推荐使用iterator执行遍历删除操作。
但是如果改为线程安全的CopyOnWriteArrayList
使用普通for循环遍历
public class Main {
public static void main(String[] args) throws Exception {
List<Integer> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 5; i++)
list.add(i);
// list {0, 1, 2, 3, 4}
for (int i = 0; i < list.size(); i++) {
// index and number
System.out.print(i + " " + list.get(i));
if (list.get(i) % 2 == 0) {
list.remove(list.get(i));
System.out.print(" delete");
i--; // 索引改变!
}
System.out.println();
}
}
}
结果:可以看到遍历删除是成功的,但是这种方法由于删除的时候会改变list的index索引和size大小,可能会在遍历时导致一些访问越界的问题,因此不是特别推荐。
使用增强型for循环遍历
public class Main {
public static void main(String[] args) throws Exception {
List<Integer> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 5; i++)
list.add(i);
// list {0, 1, 2, 3, 4}
for (Integer num : list) {
// index and number
System.out.print(num);
if (num % 2 == 0) {
list.remove(num);
System.out.print(" delete");
}
System.out.println();
}
}
}
结果:与ArrayList遍历删除时情况不同,CopyOnWriteArrayList是允许使用增强型for进行循环遍历删除的。
使用iterator遍历
public class Main {
public static void main(String[] args) throws Exception {
List<Integer> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 5; i++)
list.add(i);
// list {0, 1, 2, 3, 4}
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
// index and number
int num = it.next();
System.out.print(num);
if (num % 2 == 0) {
it.remove();
System.out.print(" delete");
}
System.out.println();
}
}
}
由于CopyOnWriteArrayList的iterator是对其List的一个“快照”,因此是不可改变的,所以无法使用iterator遍历删除。
结论:
当使用ArrayList时,我们可以使用iterator实现遍历删除;而当我们使用CopyOnWriteArrayList时,我们直接使用增强型for循环遍历删除即可,此时使用iterator遍历删除反而会出现问题。
参考资料:
SynchronizedList和Vector的区别
Java提高篇(三四)-----fail-fast机制
正确在遍历中删除List元素