简单说明:
1、上图中虚线且无依赖字样、说明是直接实现的接口
2、虚线但是有依赖字样、说明此类依赖与接口、但不是直接实现接口
3、实线是继承关系、类继承类、接口继承接口
1、ArrayList是内部是以动态数组的形式来存储数据的、知道数组的可能会疑惑:数组不是定长的吗?这里的动态数组不是意味着去改变原有内部生成的数组的长度、而是保留原有数组的引用、将其指向新生成的数组对象、这样会造成数组的长度可变的假象。
2、ArrayList具有数组所具有的特性、通过索引支持随机访问、所以通过随机访问ArrayList中的元素效率非常高、但是执行插入、删除时效率比较地下、具体原因后面有分析。
3、ArrayList实现了AbstractList抽象类、List接口、所以其更具有了AbstractList和List的功能、前面我们知道AbstractList内部已经实现了获取Iterator和ListIterator的方法、所以ArrayList只需关心对数组操作的方法的实现、
4、ArrayList实现了RandomAccess接口、此接口只有声明、没有方法体、表示ArrayList支持随机访问。
5、ArrayList实现了Cloneable接口、此接口只有声明、没有方法体、表示ArrayList支持克隆。
6、ArrayList实现了Serializable接口、此接口只有声明、没有方法体、表示ArrayList支持序列化、即可以将ArrayList以流的形式通过ObjectInputStream/ObjectOutputStream来写/读。
// Collection中定义的API boolean add(E object) boolean addAll(Collection<? extends E> collection) void clear() boolean contains(Object object) boolean containsAll(Collection<?> collection) boolean equals(Object object) int hashCode() boolean isEmpty() Iterator<E> iterator() boolean remove(Object object) boolean removeAll(Collection<?> collection) boolean retainAll(Collection<?> collection) int size() <T> T[] toArray(T[] array) Object[] toArray() // AbstractList中定义的API void add(int location, E object) boolean addAll(int location, Collection<? extends E> collection) E get(int location) int indexOf(Object object) int lastIndexOf(Object object) ListIterator<E> listIterator(int location) ListIterator<E> listIterator() E remove(int location) E set(int location, E object) List<E> subList(int start, int end) // ArrayList新增的API Object clone() void ensureCapacity(int minimumCapacity) void trimToSize() void removeRange(int fromIndex, int toIndex)
总结:相对与AbstractCollection而言、多实现了List中新增的通过索引操作元素的方法。
总结:从ArrayList源码可以看出、ArrayList内部是通过动态数组来存储数据、从中我们也可以很容易的找到ArrayList的几个特性:
1、 有序:如果不指定元素存放位置、则元素将依次从Object数组的第一个位置开始放、如果指定插入位置、则会将元素插入指定位置、后面的所有元素都后移
2、 可重复:从源码中没有看到对存放的元素的校验
3、 随机访问效率高:可以直接通过索引定位到我们要找的元素
4、 自动扩容:ensureCapacity(int minCapacity)方法中会确保数组的最小size、当不够时会将原来的容量扩增到:(oldCapacity * 3) / 2 + 1。
5、 变动数组元素个数(即添加、删除数组元素)效率低、在增删的操作中我们常见的一个函数: System.arraycopy()、他是将删除、或者添加之后、原有的元素进行移位、这是需要较大代价的。
6、 ArrayList不是线程安全的、即当使用多线程操作ArrayList时会有可能出错、后面总结会有。
因为使用集合、我们最关心的就是使用不同集合的不同方法的效率问题、而在这些中、最能体现效率问题的关键点是对集合的遍历、所以对于示例、分为两部分:第一部分是关于集合的不同的遍历方法的耗时示例、第二部分是集合的API的使用示例。
01)使用Iterator遍历ArrayList
for(Iterator<Integer> iter = list.iterator(); iter.hasNext(); ) { iter.next(); }
02)使用ListIterator遍历ArrayList
for(Iterator<Integer> iter = list.listIterator(); iter.hasNext(); ) { iter.next(); }
03)使用随机访问(即for(int i=0;i<xxx; i++)这种形式称为随机访问)遍历ArrayList
for (int i = 0; i < list.size(); i++){ list.get(i); }
04)使用增强for循环遍历ArrayList
for(@SuppressWarnings("unused") int i : list);
05)示例
package com.chy.collection.example; import java.util.ArrayList; import java.util.Iterator; import java.util.ListIterator; public class EragodicArrayList { /** * 测试不同遍历方式的效率 */ public static void testObtainAllElements(){ //初始化一个较大的ArrayList ArrayList<Integer> list = new ArrayList<Integer>(); for(int i=0; i<2000000; i++){ list.add(i); } //零:使用Iterator long start = startTime(); for(Iterator<Integer> iter = list.iterator(); iter.hasNext(); ) { iter.next(); } endTime(start); //result: 63ms //一:使用Iterator long start0 = startTime(); for(Iterator<Integer> iter = list.listIterator(); iter.hasNext(); ) { iter.next(); } endTime(start0); //result: 78ms //二:使用随机访问、通过索引 long start1 = startTime(); for (int i = 0; i < list.size(); i++){ list.get(i); } endTime(start1); //result: 16ms //三:使用增强for循环 long start2 = startTime(); for(@SuppressWarnings("unused") int i : list); endTime(start2); //result:62ms //四:使用ListIterator long start3 = startTime(); ListIterator<Integer> li = list.listIterator(0); while(li.hasNext()){ li.next(); } endTime(start3); //result: 63ms } private static void endTime(long start) { long end = startTime(); System.out.println(end - start + " ms"); } private static long startTime() { long start = System.currentTimeMillis(); return start; } public static void main(String[] args) { testObtainAllElements(); } }
结果及说明:
63 ms 78 ms 15 ms 63 ms 62 ms
从上面可以看出:使用随机访问效率最高、其他的差不多。
package com.chy.collection.example; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.ListIterator; import com.chy.collection.bean.Student; public class ArrayListTest { /** * 测试ArrayList的添加元素方法、以及与size有关的方法 */ public static void testArrayListSize(){ //use default object array's size 10 ArrayList<String> list = new ArrayList<String>(); list.add("a"); list.add(1, "b"); // use specified size 4 ArrayList<String> list2 = new ArrayList<String>(4); list2.add("c"); list2.add("d"); list2.add("e"); list2.add("f"); //use specified size 5 ArrayList<String> list3 = new ArrayList<String>(5); list3.add("g"); list3.add("h"); list3.add("i"); list3.add("j"); list3.add("k"); list.addAll(list2);//从list末尾开始追加 System.out.println(list.size());// result: 6 list.addAll(6, list3);//从list索引6开始添加 System.out.println(list.size());// result: 11 //see AbstractCollection.toString(); System.out.println(list); //result: [a, b, c, d, e, f, g, h, i, j, k] // 对于ArrayList的大小、我们可以使用三个方法来操作 list.add(null); list.add(null); System.out.println(list.size()); list.trimToSize();//将list的大小设置成与其包含的元素相同、null也算是list中的元素、并且可以重复出现 System.out.println(list.size()); list.ensureCapacity(1);//确保list的大小不小于传入的参数值。 System.out.println(list.size()); System.out.println(list.size()); } /** * 测试ArrayList的包含、删除方法 */ public static void testArrayListContainsRemove(){ //初始化包含学号从1到10的十个学生的ArrayList ArrayList<Student> list1 = new ArrayList<Student>(); Student s1 = new Student(1,"chy1"); Student s2 = new Student(2,"chy2"); Student s3 = new Student(3,"chy3"); Student s4 = new Student(4,"chy4"); list1.add(s1); list1.add(s2); list1.add(s3); list1.add(s4); for (int i = 5; i < 11; i++) { list1.add(new Student(i, "chy" + i)); } System.out.println(list1); //初始化包含学号从1到4的四个学生的ArrayList ArrayList<Student> list2 = new ArrayList<Student>(); list2.add(s1); list2.add(s2); list2.add(s3); list2.add(s4); //查看list1中是否包含学号为1的学生( 这里要注意、ArrayList中存放的都是对象的引用、而非堆内存中的对象) System.out.println(list1.contains(s1)); //查看list1中是否包含list2 System.out.println(list1.containsAll(list2)); //从新构造一个指向学号为1的student、查看list2是否包含、不包含就添加进去、在判断list1是否包含list2 Student newS1 = new Student(1, "newchy1"); System.out.println("list2 contains newS1 ? " + list2.contains(newS1)); if(!list2.contains(newS1)){ list2.add(newS1); } System.out.println("list2 members :" + list2.toString()); System.out.println("list1 contains list2 ? " + list1.containsAll(list2)); //删除list1中索引为0的学生 System.out.println(list1.remove(0)); //如果学号为1的学生存在则删除、不存在删除学号为2的学生 if(!list1.remove(s1)){ System.out.println(list1.remove(s2)); } //删除list2中的学生 list1.removeAll(list2); System.out.println(list1); //清空list1 list1.clear(); //求list1与list2中元素的交集 list1.retainAll(list2); System.out.println(list1); } /** * 测试ArrayList的获取元素方法、 */ public static void testObtainArrayListElements(){ //将字符串数组转化成ArrayList String[] strArray = {"a", "b" ,"c", "d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"}; /* 使用时会抛异常、是由于Arrays.asList(strArray)返回的是一个Object[]、不能强转成ArrayList<String>类型 ArrayList<String> list2 = (ArrayList<String>)Arrays.asList(strArray); System.out.println(list2); */ //一般情况下使用下面这种转换方式、他会自动的将数组转换之后的类型设置为runtime时的类型 ArrayList<String> list1 = new ArrayList<String>(Arrays.asList(strArray)); System.out.println(list1); //获取某个索引处的元素 System.out.println("str " + list1.get(0) + " size: " + list1.size()); //将最后一个元素设置成"a"、打印被替换的元素 System.out.println("old element :" + list1.set(list1.size()-1, list1.get(0)) + " list elements: " + list1); System.out.println(); //返回第一个、最后一个“a”、“w”、“z”的索引、不存在则返回-1、内部是根据ListIterator来返回索引的 System.out.println("first index of a : " + list1.indexOf("a") + " last index of a :" + list1.lastIndexOf("a")); System.out.println("first index of w : " + list1.indexOf("w") + " last index of w :" + list1.lastIndexOf("w")); System.out.println("first index of z : " + list1.indexOf("z") + " last index of z :" + list1.lastIndexOf("z")); } /** * 对ListIterator方法的测试 */ public static void testListIterator(){ String[] strArray = {"a", "b" ,"c", "d","e"}; ArrayList<String> list = new ArrayList<String>(Arrays.asList(strArray)); //倒序遍历list ListIterator<String> li = list.listIterator(list.size()); while(li.hasPrevious()){ System.out.println(li.previous()); } System.out.println("================================"); //以获取idnex方式 、正序遍历list ListIterator<String> li1 = list.listIterator(0); while(li1.hasNext()){ //System.out.println(li1.nextIndex()); 会造成死循环、具体可以看源码 //System.out.println(li1.previousIndex()); 同样会造成死循环、具体可以看源码 String s = li1.next(); if("d".equals(s)){ li1.set("a"); } if("e".equals(s)){ li1.add("f"); } if("b".equals(s)){ li1.remove(); } } System.out.println(list); //对于在遍历过程中想获取index、要注意死循环、和字节想要获取的方式、具体可以自己动手试试 ListIterator<String> li2 = list.listIterator(); while(li2.hasNext()){ li2.next(); System.out.println(li2.nextIndex() + "========" + li2.previousIndex()); } } /** * 测试ArrayList转换成Array时注意事项、附Array转换成List */ public static void testArrayList2Array(){ //关于Array转换成ArrayList上面已经有过介绍、现在再补充一点特殊情况 int[] intArray = new int[10]; for (int i = 0; i < intArray.length; i++) { intArray[i] = i; } //将上面的数组转化成ArrayList //ArrayList<int> list = Arrays.asList(intArray); 这种写法编译就会报错、因为集合的定义中、只能存放对象(其实是对象的引用)、所以我们要使用包装类型Integer //要先将上面的数组转换成Integer类型数组、只能手动转、不能强制或者自动转换、若有的话望贴出来啊 Integer[] integerArray = new Integer[intArray.length]; for (int i = 0; i < intArray.length; i++) { integerArray[i] = intArray[i]; } //ArrayList<Integer> list = (ArrayList<Integer>)Arrays.asList(integerArray); //System.out.println(list.get(0)); 会报错、原因上面有 //通常使用下面的转换方式 ArrayList<Integer> normalList = new ArrayList<Integer>(Arrays.asList(integerArray)); System.out.println(normalList.get(0)); //第一种 /* * 会报强制转换错误、 //ArrayList转换成Array Integer[] itg = (Integer[])normalList.toArray(); System.out.println(itg[0]); */ //第二种 Integer[] ia = new Integer[normalList.size()]; normalList.toArray(ia); System.out.println(ia[0]); //第三种、应该使用这种形式的定义、传入的参数的本质是供toArray内部调用其类型、对其size简单处理一下、如果size大于list的size、则后面的补null、如果小于、则使用新的数组替换传入的、并作为结果返回 Integer[] ia2 = normalList.toArray(new Integer[11]); System.out.println(ia2[10]); } /** * 测试fail-fast机制 */ public static void testFailFast(){ String[] s = {"a", "b", "c", "d", "e"}; ArrayList<String> strList = new ArrayList<String>(Arrays.asList(s)); Iterator<String> it = strList.iterator(); while(it.hasNext()){ String str = it.next(); System.out.println(str); //这里本来是多线程动了ArrayList中的元素造成的、现在仅仅是模拟一种情况、就是在迭代的过程中、另一个线程向ArrayList中添加一个元素造成的fail-fast //异常信息:java.util.ConcurrentModificationException if("d".equals(str)){ strList.add(str); } } } public static void main(String[] args) { // testArrayListSize(); // testArrayListContainsRemove(); // testObtainArrayListElements(); // testArrayList2Array(); // testFailFast(); testListIterator(); } }
对于ArrayList、在记住其特性、有序可重复、便与查找、不便于增删的同时最好是能知道为什么他会有这些特性、其实源码就是最好的说明书、平常所接触的东西都是别人在源码的基础上分析得出的结论、只有自己的才是最适合自己的、别人总结的再好、看过、受教了、但是还是希望自己能动手总结一份、再差也是自己总结的、慢慢改进、只有自己的东西才是最适合自己的!