第12期:Java集合类—— List

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tuantuanyua/article/details/82313156

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()等。查询慢,增删快,线程不安全,效率高。

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大小,可能会在遍历时导致一些访问越界的问题,因此不是特别推荐。

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 (Integer num : list) {
			// index and number
			System.out.print(num);
			if (num % 2 == 0) {
				list.remove(num);
				System.out.print(" delete");
			}
			System.out.println();
		}
	}
}

CopyOnWriteArrayList增强for遍历删除

结果:与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遍历删除

由于CopyOnWriteArrayList的iterator是对其List的一个“快照”,因此是不可改变的,所以无法使用iterator遍历删除。当使用ArrayList时,我们可以使用iterator实现遍历删除;而当我们使用CopyOnWriteArrayList时,我们直接使用增强型for循环遍历删除即可,此时使用iterator遍历删除反而会出现问题。

参考资料:

SynchronizedList和Vector的区别

Java提高篇(三四)-----fail-fast机制

正确在遍历中删除List元素

猜你喜欢

转载自blog.csdn.net/tuantuanyua/article/details/82313156