标签:保存 ror bsp length font ring 地方 机制 data
文章系作者原创,如有转载请注明出处,如有雷同,那就雷同吧~(who care!)
一、写在前面
这是源码分析计划的第一篇,博主准备把一些常用的集合源码过一遍,比如:ArrayList、HashMap及其对应的线程安全实现,此文章作为自己相关学习的一个小结,记录学习成果的同时,也希望对有缘的朋友提供些许帮助。
当然,能力所限,难免有纰漏,希望发现的朋友能够予以指出,不胜感激,以免误导了大家!
二、稳扎稳打过源码
首先,是源码内部的成员变量定义以及构造方法:
1 /** 2 * Default initial capacity. 3 (默认初始化长度)ps:实际是“延时初始化”(lazy init),后文详解 4 */ 5 private static final int DEFAULT_CAPACITY = 10; 6 7 /** 8 * Shared empty array instance used for empty instances. 9 (共享空数组,为了追求效率) 10 */ 11 private static final Object[] EMPTY_ELEMENTDATA = {}; 12 13 /** 14 * Shared empty array instance used for default sized empty instances. We 15 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when 16 * first element is added. 17 (区别于EMPTY_ELEMENTDATA,使用默认构造方法时,默认使用此空数组,再配合DEFAULT_CAPACITY共同实现lazy init) 18 */ 19 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 20 21 /** 22 * The array buffer into which the elements of the ArrayList are stored. 23 * The capacity of the ArrayList is the length of this array buffer. Any 24 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 25 * will be expanded to DEFAULT_CAPACITY when the first element is added. 26 (集合数据真正存放的地方,所以对于ArrayList我们可以理解为提供了一组高效操作方法的数组。注意注释后半段:当集合的首个元素被添加时,把空集合DEFAULTCAPACITY_EMPTY_ELEMENTDATA扩展为DEFAULT_CAPACITY大小的集合,这就是lazy init,使用时才分配内存空间,目的是防止空间的浪费。)ps:transient 表示此变量不参与序列化 27 */ 28 transient Object[] elementData; // non-private to simplify nested class access 29 30 /** 31 * The size of the ArrayList (the number of elements it contains). 32 * 33 * @serial 34 (数组大小) 35 */ 36 private int size;
1 /** 2 * Constructs an empty list with the specified initial capacity. 3 * 4 * @param initialCapacity the initial capacity of the list 5 * @throws IllegalArgumentException if the specified initial capacity 6 * is negative 7 (没什么可说,初始化参数>0创建该长度数组,=0使用共享空数组,<0报错) 8 */ 9 public ArrayList(int initialCapacity) { 10 if (initialCapacity > 0) { 11 this.elementData = new Object[initialCapacity]; 12 } else if (initialCapacity == 0) { 13 this.elementData = EMPTY_ELEMENTDATA; 14 } else { 15 throw new IllegalArgumentException("Illegal Capacity: "+ 16 initialCapacity); 17 } 18 } 19 20 /** 21 * Constructs an empty list with an initial capacity of ten. 22 (使用默认空数组,lazy init,添加元素时数组扩展至默认容量DEFAULT_CAPACITY) 23 */ 24 public ArrayList() { 25 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 26 } 27 28 /** 29 * Constructs a list containing the elements of the specified 30 * collection, in the order they are returned by the collection‘s 31 * iterator. 32 * 33 * @param c the collection whose elements are to be placed into this list 34 * @throws NullPointerException if the specified collection is null 35 */ 36 public ArrayList(Collection<? extends E> c) { 37 elementData = c.toArray(); 38 if ((size = elementData.length) != 0) { 39 // c.toArray might (incorrectly) not return Object[] (see 6260652) 40 (准确的说这是个bug,从下面链接可以看到会在jdk9修复,bug产生原因后面详解。附上链接http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652) 41 if (elementData.getClass() != Object[].class) 42 elementData = Arrays.copyOf(elementData, size, Object[].class); 43 } else { 44 // replace with empty array. 45 this.elementData = EMPTY_ELEMENTDATA; 46 } 47 }
进阶分析,集合常用操作,先从简单入手:
1 /** 2 思路很简单: 3 1.判断index是否越界,越界则异常 4 2.直接取出数组elementData相应位置index的元素并强转为E 5 **/ 6 public E get(int index) { 7 rangeCheck(index); 8 9 return elementData(index); 10 } 11 private void rangeCheck(int index) { 12 if (index >= size) 13 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 14 } 15 E elementData(int index) { 16 return (E) elementData[index]; 17 }
1 /** 2 同样很简单: 3 1.判断index是否越界,越界则异常 4 2.取到index位置的值作为原值 5 3.设置该位置值为修改后的值 6 4.return原值 7 **/ 8 public E set(int index, E element) { 9 rangeCheck(index); 10 11 E oldValue = elementData(index); 12 elementData[index] = element; 13 return oldValue; 14 }
如前所述,单个元素按位次获取操作很简单,只要你了解到arrayList内部是数组存放数据,那上述操作完全可以想得到。
下面继续,集合的添加和删除操作,没看过源码的朋友可以思考下,添加和删除操作同上述两个操作主要的区别在哪里?为什么会复杂些?
因为添加元素的时候可能位置不够嘛,那怎么办?位置不够就加位置,也就是所谓的“扩容”!我们自己思考下实现思路,然后同作者的思路对比下:
1.上面我们提到,默认构造方法使用的lazy_init模式,添加元素时才init。那第一步判断是否需要init,需要则init。
2.判断是否需要扩容(通俗点说就是当前数组有没有空位置?)需要就扩容(创建个容量更大的新数组),但是扩容也不是无限的,超过上限就报错(Integer.Max_Value),并把原数据copy到新数组,否则什么都不做
3.在指定位置添加元素,并size++
1 /** 2 看下作者是如何思考的,和自己的思考做下对比: 3 1.判断是否需要lazy init,如果需要则init,然后执行步骤2 4 2.判断是否需要扩容,如果需要则执行步骤3,否则执行步骤5 5 3.扩容,新数组长度=当前数组长度*1.5,并判断扩容后长度是否满足目标容量,不满足,则新数组长度=目标容量。接着判断新数组长度是否超过阈值MAX_ARRAY_SIZE,超过则执行步骤4 6 4.目标数组长度=如果目标数组长度>MAX_ARRAY_SIZE?Integer.MAX_VALUE:MAX_ARRAY_SIZE 7 5.扩容结束后,执行数据copy,从原数组copy数据到新数组 8 6.在指定的位置添加元素,并使长度增加 9 **/ 10 public boolean add(E e) { 11 ensureCapacityInternal(size + 1); // Increments modCount!! 12 elementData[size++] = e; 13 return true; 14 } 15 private void ensureCapacityInternal(int minCapacity) { 16 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 17 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 18 } 19 20 ensureExplicitCapacity(minCapacity); 21 } 22 private void ensureExplicitCapacity(int minCapacity) { 23 modCount++; 24 25 // overflow-conscious code 26 if (minCapacity - elementData.length > 0) 27 grow(minCapacity); 28 } 29 private void grow(int minCapacity) { 30 // overflow-conscious code 31 int oldCapacity = elementData.length; 32 int newCapacity = oldCapacity + (oldCapacity >> 1); 33 if (newCapacity - minCapacity < 0) 34 newCapacity = minCapacity; 35 if (newCapacity - MAX_ARRAY_SIZE > 0) 36 newCapacity = hugeCapacity(minCapacity); 37 // minCapacity is usually close to size, so this is a win: 38 elementData = Arrays.copyOf(elementData, newCapacity); 39 } 40 private static int hugeCapacity(int minCapacity) { 41 if (minCapacity < 0) // overflow 42 throw new OutOfMemoryError(); 43 return (minCapacity > MAX_ARRAY_SIZE) ? 44 Integer.MAX_VALUE : 45 MAX_ARRAY_SIZE; 46 }
对比后发现,总体思路是没问题的,但是作者为什么要加入下面这样的容量上限处理逻辑?朋友们可以思考下,ArrayList的容量上限到底是Integer.MAX_VALUE还是MAX_ARRAY_SIZE?
答案是:Integer.MAX_VALUE,依然还是它,并不是源码中作者定义的MAX_ARRAY_SIZE,那就引申出一个问题了-MAX_ARRAY_SIZE存在的意义。
我的理解是,这个上限的存在,减小了内存溢出的概率。首先注释中也提到了“Some VMs reserve some header words in an array”,意思就是有些虚拟机把头信息保留在数组中,毫无疑问保存信息是要占空间的,也就是我实际数组空间可能是不足Integer.MAX_VALUE的,作者应该是这样的思路(我猜的~)“我可以基本确定实际空间不会小于MAX_ARRAY_SIZE(可能是分析了主流虚拟机的实现而得出的结论),因此设置个阈值,来确保不会内存溢出,但如果这个容量还是不够,那把剩下的风险很高的8也给你好了,至于是不是会溢出,天知道,反正我已经尽力了!”
if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
1 /** 2 比较简单,一笔带过,按位置删除 3 思路: 4 1.越界检测 5 2.取出当前位置的元素 6 3.当前位置之后的所有元素整体前移一位置 7 4.最后位置置空,size--,返回删除的元素值。 8 **/ 9 public E remove(int index) { 10 rangeCheck(index); 11 12 modCount++; 13 E oldValue = elementData(index); 14 15 int numMoved = size - index - 1; 16 if (numMoved > 0) 17 System.arraycopy(elementData, index+1, elementData, index, 18 numMoved); 19 elementData[--size] = null; // clear to let GC do its work 20 21 return oldValue; 22 } 23 /** 24 思路很简单,按元素删除(注意只删除从头开始第一个匹配值),遍历->匹配->删除 25 值得注意的是,单独对null做特殊处理,按地址比较 26 **/ 27 public boolean remove(Object o) { 28 if (o == null) { 29 for (int index = 0; index < size; index++) 30 if (elementData[index] == null) { 31 fastRemove(index); 32 return true; 33 } 34 } else { 35 for (int index = 0; index < size; index++) 36 if (o.equals(elementData[index])) { 37 fastRemove(index); 38 return true; 39 } 40 } 41 return false; 42 } 43 44 单个删除集合元素
1 public boolean removeAll(Collection<?> c) { 2 Objects.requireNonNull(c); 3 return batchRemove(c, false); 4 } 5 6 private boolean batchRemove(Collection<?> c, boolean complement) { 7 final Object[] elementData = this.elementData; 8 int r = 0, w = 0; 9 boolean modified = false; 10 try { 11 //遍历当前集合所有元素 12 for (; r < size; r++) 13 if (c.contains(elementData[r]) == complement) 14 //如果指定集合不包含该元素(即不应删除的,需要保留的),把当前元素移动到头部w位置(原头部元素因不符合条件,直接删除掉,这里覆盖也即删除),并把w标记移到下一位 15 elementData[w++] = elementData[r]; 16 } finally { 17 // Preserve behavioral compatibility with AbstractCollection, 18 // even if c.contains() throws. 19 //这儿两种情况: 20 //无异常r==size 不会进入这个if 21 //有异常,则把因异常而未来得及比较的所有元素整体copy到w位置,并把w标记移位size - r(可以理解为还未比较的数量) 22 if (r != size) { 23 System.arraycopy(elementData, r, 24 elementData, w, 25 size - r); 26 w += size - r; 27 } 28 //这儿很好理解,w位置(该位置之前都是比较或者异常而需要保留的)之后的所有都是应该删除的。 29 if (w != size) { 30 // clear to let GC do its work 31 for (int i = w; i < size; i++) 32 elementData[i] = null; 33 modCount += size - w; 34 size = w; 35 modified = true; 36 } 37 } 38 return modified; 39 }
1 /** 2 看起来比较简单,但仔细分析就会发现还是有些深度的,有兴趣可以看下defaultReadObject的实现,这里就暂时忽略,以后有机会详细分析下java的序列化与反序列化。 3 实现思路(以序列化为例,反序列化同理) 4 1.调用默认序列化 5 2.写入size 6 3.遍历数组,写入所有集合元素 7 **/ 8 private void writeObject(java.io.ObjectOutputStream s) 9 throws java.io.IOException{ 10 // Write out element count, and any hidden stuff 11 int expectedModCount = modCount; 12 s.defaultWriteObject(); 13 14 // Write out size as capacity for behavioural compatibility with clone() 15 s.writeInt(size); 16 17 // Write out all elements in the proper order. 18 for (int i=0; i<size; i++) { 19 s.writeObject(elementData[i]); 20 } 21 22 if (modCount != expectedModCount) { 23 throw new ConcurrentModificationException(); 24 } 25 } 26 27 /** 28 * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, 29 * deserialize it). 30 */ 31 private void readObject(java.io.ObjectInputStream s) 32 throws java.io.IOException, ClassNotFoundException { 33 elementData = EMPTY_ELEMENTDATA; 34 35 // Read in size, and any hidden stuff 36 s.defaultReadObject(); 37 38 // Read in capacity 39 s.readInt(); // ignored 40 41 if (size > 0) { 42 // be like clone(), allocate array based upon size not capacity 43 ensureCapacityInternal(size); 44 45 Object[] a = elementData; 46 // Read in all elements in the proper order. 47 for (int i=0; i<size; i++) { 48 a[i] = s.readObject(); 49 } 50 } 51 }
/** 迭代器 1.了解快速失败机制即可 2.关注下forEachRemaining的实现,可扩展研究下lambda表达式 **/ public Iterator<E> iterator() { return new Itr(); } /** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
三、 ArrayList使用建议
1.当你需要确定长度的ArrayList时,推荐使用该长度初始化,理由是防止频繁扩容,扩容相对而言是对性能影响相当大的操作(请注意是相对而言,相对于常规的增删改查等操作)。
2.谨慎使用clone、toArray,对于源对象和方法返回对象,数据修改操作会相互影响。他们最终都执行了System.arrayCopy(),都是“浅拷贝”:只copy了引用,所有对于其中一个引用的修改操作会传递到其他引用上。java的copy应该都是“浅拷贝”,测试如下:
/** 测试代码,随手为之,仅供参考。 **/ class TestObj { TestObj(Integer i, String s) { this.i = i; this.s = s; } Integer i; String s; public String toString() { return "i=" + i + "," + "s=" + s; } } ArrayList<TestObj> arrayList = new ArrayList<>(); int size = 10; for (Integer i = 0; i < size; i++) { arrayList.add(new TestObj(i, "test" + i)); } ArrayList<TestObj> cloneArrayList = (ArrayList<TestObj>) arrayList.clone(); cloneArrayList.add(new TestObj(101, "test" + 101)); System.out.println("arrayList size:" + arrayList.size()); System.out.println("cloneArrayList size:" + cloneArrayList.size()); System.out.println("arrayList index 0:" + arrayList.get(0).toString()); System.out.println("cloneArrayList index 0:" + cloneArrayList.get(0).toString()); //修改cloneArrayList index 0 TestObj testInteger = cloneArrayList.get(0); testInteger.i = 1000111; System.out.println("修改cloneArrayList index=0对象后,ps:我没修改arrayList哦"); System.out.println("arrayList index 0:" + arrayList.get(0).toString()); System.out.println("cloneArrayList index 0:" + cloneArrayList.get(0).toString()); 测试结果: arrayList size:10 cloneArrayList size:11 arrayList index 0:i=0,s=test0 cloneArrayList index 0:i=0,s=test0 修改cloneArrayList index=0对象后,ps:我没修改arrayList哦 arrayList index 0:i=1000111,s=test0 cloneArrayList index 0:i=1000111,s=test0
3.谨慎使用subList,深入分析后会发现,本质原因同上面类似,都是相同的引用,所以数据修改操作会相互影响,如下例子:
1 public static void testSubList() { 2 3 Integer index = 10; 4 5 List<Integer> myList = new ArrayList<>(index); 6 7 for (int i = 0; i < index; i++) { 8 myList.add(i); 9 } 10 11 List<Integer> mySubList = myList.subList(3, 5); 12 13 14 System.out.println("打印myList:"); 15 myList.forEach(System.out::println); 16 17 System.out.println("对mySubList增加个元素,注意我没对myList做任何操作哦"); 18 mySubList.add(100); 19 20 21 System.out.println("打印mySubList:"); 22 mySubList.forEach(System.out::println); 23 24 System.out.println("再次打印myList:"); 25 myList.forEach(System.out::println); 26 27 28 } 29 运行结果: 30 打印myList: 31 0 32 1 33 2 34 3 35 4 36 5 37 6 38 7 39 8 40 9 41 对mySubList增加个元素,注意我没对myList做任何操作哦 42 打印mySubList: 43 3 44 4 45 100 46 再次打印myList: 47 0 48 1 49 2 50 3 51 4 52 100 53 5 54 6 55 7 56 8 57 9
4.细心的朋友可能发现了,arrayList没有提供对数组的构造方法,但是我们知道array->arrayList是比较常见的需求,那如何做呢?办法不止一种,选择你喜欢的即可,如下:
1 int[] ints = {1, 2, 3, 4}; 2 List<Integer> myIntListByStream = Arrays.stream(ints).boxed().collect(Collectors.toList()); 3 4 5 Integer[] integers = {1, 2, 3, 4}; 6 List<Integer> myListByAsList = Arrays.asList(integers); //ps:此方式得到的数组不可修改,这里的修改指的是所有更改List数据的行为 7 List<Integer> myListByNewArrayListAsList = new ArrayList<>(Arrays.asList(integers)); 8 List<Integer> myListByStream = Arrays.stream(integers).collect(Collectors.toList());
四、源码相关扩展(我能想到的可以深入思考的一些地方)
1.快速失败(fail fast)机制:
我们知道,ArrayList不是线程安全的集合类。意味着在并发操作时,很容易发生错误。按照常规思路,发现结果出错,抛出异常即可。但在实际应用中,我们并不满足于此,有没有一种检测机制,在并发操作前进行校验,提前告诉我们可能发生的错误呢?如你所料,就是我们提到的快速失败机制。它是如何实现的呢?其实很简单,我们定义一个计数器modCount,这个计数器记录所有集合结构变动的次数,比如增加两个元素就modCount+=2,再比如删除3个元素就modCount+=3,这个计数器只能增加不能减少,然后我们在执行一些需要快速失败的操作时(比如:迭代器、序列化等等),执行之前记录下当前modCount为expectedModCount,在适当的时候判断expectedModCount、modCount是否相等就可以判断这期间是否有过集合结构的变动。
2.lambda表达式:读源码我们发现,ArrayList中加入了许多支持lambda的方法,作为JDK8的亮点之一,应该能够熟练使用,然后再详细分析lambda的实现机制我觉得也很有意思。
3.jdk中常用JNI方法的实现:上文我们在对clone方法做分析的时候,最终只分析到Object的native方法,我觉得有机会去看下常用的一些native方法的实现也是很有意思的,待研究。
五、总结一下
老实说,分析完ArrayList发现,所花的时间大出我预计,我一度认为我已经理解的很透彻了,但是在写这篇文章的途中我又把差不多一半的源码回顾了一遍(这真是个悲伤的故事),不过不管怎么说,这是个好的开始,后面一篇解析hashMap。
JAVA常用集合源码解析系列-ArrayList源码解析(基于JDK8)
标签:保存 ror bsp length font ring 地方 机制 data
原文地址:http://www.cnblogs.com/moranqingqiu/p/6725240.html