本章是整理知识内容,为强化知识长期更新。
List 是是一个有序的集合,是Collection的实现类之一。
概述
list接口是Java Collection Framework的成员。
列队允许添加重复的元素。
列队允许null存在。
列队是从0开始,也就是列队头元素的下标是0。
列队支持泛型,这样可以避免ClassCastException
异常。
数组与数组列表
Array(数组)它是在内存中划分出一块连续的地址空间进行元素的存储,由于它直接操作内存,所以性能比其他的集合要高一点点。但是数组也有一个严重的缺陷,就是需要指定初始化大小,并且在后续的操作中不能修改数组的结构,这很不友好。
ArrayList可以很好的进行动态扩容,它会随之元素的不断增加而调整数组大小。它的底层是基于数据实现的,所以也集成了数组的一些特性。比如查找、修改很快,但是新增和删除比较慢。
Array数组
ArrayList底层是由数组实现的,那么就回顾下Java中数组的特性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Array1Test { @Test public void test1 () { String[] arr = new String [5 ]; System.out.println("数组的长度" + arr.length); for (int i = 0 ; i < arr.length ; i++) { arr[i] = String.valueOf(i); System.out.print(" " ); } System.out.println(arr[0 ].toString()); arr[6 ] = null ; } }
输出:
1 2 3 数组的长度5 0 java.lang.ArrayIndexOutOfBoundsException: 6
List列表 Java的List是一个有序的集合,List是Collection接口的扩展接口。
List特性
Java List接口是Java Collection Framework的成员。
列表允许添加重复的元素。
列表允许有null元素。
列表索引是从0开始,和数组一样的。
列表接受泛型,尽量使用它。以免出现ClassCastException
源码分析 ArrayList中的常量以及变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private static final int DEFAULT_CAPACITY = 10 ;private static final Object[] EMPTY_ELEMENTDATA = {};private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};transient Object[] elementData; private int size;private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ;
创建ArrayList的实例。 1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void test3 () { List list1 = new ArrayList (); List list2 = new ArrayList (3 ); String[] vowels = {"a" ,"e" ,"i" ,"o" ,"u" }; List<String> vowelsList = Arrays.asList(vowels); List list3 = new ArrayList (vowelsList); System.out.println(list1.toString()); System.out.println(list2.toString()); System.out.println(list3.toString()); }
这分别使用了ArrayList三个构造方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public ArrayList () { this .elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ArrayList (int initialCapacity) { if (initialCapacity > 0 ) { this .elementData = new Object [initialCapacity]; } else if (initialCapacity == 0 ) { this .elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException ("Illegal Capacity: " + initialCapacity); } } public ArrayList (Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0 ) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this .elementData = EMPTY_ELEMENTDATA; } }
这让我想起了面试题目。
1 2 3 4 @Test public void test4 () { List list = new ArrayList (20 ); }
题目是:生成list实例需要扩容几次。实际上是0次。因为创建的时候就指定了大小。
add方法 1 2 3 4 5 6 7 @Test public void test4 () { List list = new ArrayList (); System.out.println(list.add(1 )); list.add(0 ,2 ); System.out.println(list.toString()); }
输出
带着上面的疑问我们好好看看,顺便看看经常说ArrayList到底是如何扩容的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public boolean add (E e) { ensureCapacityInternal(size + 1 ); elementData[size++] = e; return true ; } public void add (int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1 ); System.arraycopy(elementData, index, elementData, index + 1 , size - index); elementData[index] = element; size++; }
System.arraycopy 是一个本地静态方法,使用它可以对数组之间进行复制。
1 2 3 public static native void arraycopy (Object src, int srcPos, Object dest, int destPos, int length) ;
src:源数组;
srcPos:源数组要复制的起始位置; dest:目的数组; destPos:目的数组放置的起始位置;
length:复制的长度。
上面的操作结果就是将数组中的元素位置进行了移动,当然神奇的地方是这个方法可以自己复制自己。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 private void ensureCapacityInternal (int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity (int minCapacity) { modCount++; if (minCapacity - elementData.length > 0 ) grow(minCapacity); } private void grow (int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1 ); if (newCapacity - minCapacity < 0 ) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0 ) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity (int minCapacity) { if (minCapacity < 0 ) throw new OutOfMemoryError (); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
每次扩容都是通过Arrays.copyOf(elementData, newCapacity)的来实现的。
set方法 这个方法其实就是更新方法。
1 2 3 4 5 6 7 @Test public void test5 () { List list = new ArrayList (); list.add(1 ); System.out.println(list.set(0 ,1 )); System.out.println(list.toString()); }
输出
看下源代码。
1 2 3 4 5 6 7 public E set (int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
remove方法 1 2 3 4 5 6 7 8 9 10 11 12 @Test public void test6 () { List list = new ArrayList (); list.add("a" ); list.add("b" ); list.add("b" ); list.add("c" ); list.add("d" ); System.out.println(list.toString()); list.remove("b" ); System.out.println(list.toString()); }
输出
1 2 [a, b, b, c, d] [a, b, c, d]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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 ; return oldValue; } 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 ; }
这两个删除方法可以看出,动作都比较大。这种操作会慢很多。
get方法。 相比上面3种方法确实轻量级太多了。
1 2 3 4 5 public E get (int index) { rangeCheck(index); return elementData(index); }
平时也就听大家说新增修改删除慢,也就访问速度比较快。看了源码之后才清晰很多。
contains方法 这也是一个重量级的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public boolean contains (Object o) { return indexOf(o) >= 0 ; } 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 ; }
安全删除 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test public void test2 () { String[] vowels = {"a" ,"e" ,"i" ,"o" ,"u" }; Collection<String> collection = new ArrayList (Arrays.asList(vowels)); Iterator<String> itr = collection.iterator(); while (itr.hasNext()){ String var = itr.next(); if (var == "a" ){ itr.remove(); }else { System.out.print(var ); } } }
线程安全
由于所有的方法都没有进行同步。所以很直观的认为有线程安全的问题,还是亲自测试下避免迷迷糊糊的。
场景一:多线程对一个ArrayList进行操作。
假设我们需要添加10000个元素进ArrayList
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class ArrayListTest2 { public static void main (String[] args) throws InterruptedException { List list = new ArrayList (); ExecutorService executorService = Executors.newFixedThreadPool(20 ); for (int sum = 0 ; sum < 10 ; sum ++){ <