ArrayList源码学习-Cloneable,RandomAccess,Serializable接口讲解

ArrayList接口实现如下图所示

1、Cloneable接口

一个类要调用clone()方法,就要实现Cloneable接口并且重写Object的clone()方法,否则会报CloneNotSupportedException 异常,并且要在clone()方法中调用了super.clone(),这意味着无论clone类的继承结构是什么样的,都调用了java.lang.Object类的clone()方法。

java.lang.Object类的clone()方法源码如下:

  protected native Object clone() throws CloneNotSupportedException;

可以看到ote修饰符为prcted,这就意味着重写clone()方法要被其他类调用就必须重写为public,以下是ArrayList重写的clone()源码:

 public Object clone() {
        try {
            //调用super.clone()克隆出一个新的对象
            ArrayList<?> v = (ArrayList<?>) super.clone();
            // 通过复制的方式手动给该对象复制
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

2、RandomAccess接口

先看一下RandomAccess接口的源码:

public interface RandomAccess {
}

可以看到这是一个空接口,实现这个接口是作为一个标志存在的,那这个标志的作用是什么呢?我们一起去Collections里面的binarySearch方法看看,代码如下:

public static <T>  int binarySearch(List<? extends Comparable<? super T>> list, T key) {
        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
            return Collections.indexedBinarySearch(list, key);
        else
            return Collections.iteratorBinarySearch(list, key);
    }

instanceof:判断某个对象是否是某种类型,这里用来判断传入的list是否实现了RandomAccess 接口。

如果实现了RandomAccess 接口则调用了Collections.indexedBinarySearch方法,没实现则调用Collections.iteratorBinarySearch方法

indexedBinarySearch方法源码

private static <T> int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
        int low = 0;
        int high = list.size()-1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = list.get(mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found
    }

iteratorBinarySearch

private static <T> int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
    {
        int low = 0;
        int high = list.size()-1;
        ListIterator<? extends Comparable<? super T>> i = list.listIterator();

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = get(i, mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found
    }

通过以上源码对比不难发现,实现了RandomAccess 接口则使用for循环方法遍历,没实现则使用Iterator方法遍历,那这样做的意义何在呢?我们知道ArrayList实现了RandomAccess 接口,但是LinkedList并没有实现这个接口,LinkedList接口实现如下:

接下来我们用一个小例子来说明,分别对ArrayList和LinkedList用for和Iterator循环10W次,代码如下:

  public static long ArrayListFor() {
		List list=new ArrayList<>();
    	for (int i = 0; i < 100000; i++) {
    		list.add(i);
		}
    	long start = System.currentTimeMillis();
    	for (int i = 0; i < list.size(); i++) {
    		Object obj = list.get(i);
		}
    	long end = System.currentTimeMillis();
		return end-start;
	}
    

    public static long ArrayListIterator() {
		List list=new ArrayList<>();
    	for (int i = 0; i < 100000; i++) {
    		list.add(i);
		}
    	long start = System.currentTimeMillis();
    	Iterator iterator = list.iterator();
    	while (iterator.hasNext()) {
    		Object obj=iterator.next();
		}
    	long end = System.currentTimeMillis();
		return end-start;
	}
    
    public static long LinkedListFor() {
		List list=new LinkedList<>();
    	for (int i = 0; i < 100000; i++) {
    		list.add(i);
		}
    	long start = System.currentTimeMillis();
    	for (int i = 0; i < list.size(); i++) {
    		Object obj = list.get(i);
		}
    	long end = System.currentTimeMillis();
		return end-start;
	}
    

    public static long LinkedListIterator() {
		List list=new LinkedList<>();
    	for (int i = 0; i < 100000; i++) {
    		list.add(i);
		}
    	long start = System.currentTimeMillis();
    	Iterator iterator = list.iterator();
    	while (iterator.hasNext()) {
    		Object obj=iterator.next();
		}
    	long end = System.currentTimeMillis();
		return end-start;
	}

直接上测试代码和测试结果:

	public static void main(String[] args) {
		long arrayListFor = ArrayListFor();
		long arrayListIterator = ArrayListIterator();
		long linkedListFor = LinkedListFor();
		long linkedListIterator = LinkedListIterator();
		System.out.println("arrayListFor:"+arrayListFor);
		System.out.println("arrayListIterator:"+arrayListIterator);
		System.out.println("linkedListFor:"+linkedListFor);
		System.out.println("linkedListIterator:"+linkedListIterator);
	}

//打印输出如下:
arrayListFor:3
arrayListIterator:5
linkedListFor:5353
linkedListIterator:7

可以看到ArrayList的for循环速度较快,和LinkedList用Iterator速度更快,至于为什么会这样就要从数据结构的角度来谈了。

(1)、ArrayList是基于索引(index)的数组,下标是明确的,索引在搜索和读取数据的时间复杂度是O(1),但是增加和删除的时候就比较慢,因为要对所有的数据进行重排.

(2)LinkedList是基于双向链表的,因此LinkedList中插入或删除的时间复杂度仅为O(1),但是获取数据的时间复杂度却是O(n)。

      现在假设LinkedList要循环10个元素,取第一个元素为Node(0), 取第二个元素要先取到Node(0),再根据Node(0).next找到Node(1),同样取第三个元素要先取到Node(0),再根据Node(0).next找到Node(1),根据Node(1).next找到Node(2),依次类推,很容易就知道链表在搜索的时候有多慢了。

3、Serializable是序列化接口,ArrayList重写了readObject()和writeObject()方法

readObject()即反序列化,源码如下:

	  private void readObject(java.io.ObjectInputStream s)
		        throws java.io.IOException, ClassNotFoundException {
                       //定义一个新的空数组
		        elementData = EMPTY_ELEMENTDATA;
		        // 调用默认的序列化进程,但不会做serialVersionUID的检查
		        s.defaultReadObject();
		        // 读取容量
		        s.readInt(); 
		        if (size > 0) {
		            // 调用扩容方法根据元素大小分配容量
		            ensureCapacityInternal(size);
		            Object[] a = elementData;
		            // 按照顺序将元素写进数组
		            for (int i=0; i<size; i++) {
		                a[i] = s.readObject();
		            }
		        }
		    }

writaObject()即序列化,源码如下:

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // 这里其实是获取容量
        int expectedModCount = modCount;
        //调用默认的序列化处理程序
        s.defaultWriteObject();
        
        // 类似于克隆一样使ObjectOutputStream 的长度兼容modCount
        s.writeInt(size);

        //将所有的数据按照顺序写进序列化对象
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }
        //这里实际上在判断在序列化过程中原数组对象的容量有没有发生改变,即有没有删除或者新增操      //作,若有则抛出ConcurrentModificationException异常
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

通过源码可以发现readObject()和writeObject()方法都用private修饰,都是私有的,为什么要定义为私有以及是怎么调用的呢?

通过翻看ObjectOutputStream源码,找到readObject()方法

public final Object readObject()
        throws IOException, ClassNotFoundException
    {
        ...
        try {
            Object obj = readObject0(false);
            ....
            return obj;
        } finally {
            ...
        }
    }

然后进入readObject0方法,里面有一系列的判断,现在只截取我们需要的那部分贴出来:

 private Object readObject0(boolean unshared) throws IOException {
        boolean oldMode = bin.getBlockDataMode();
        ...
        try {
            switch (tc) {
               ...
                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));
                ...
            }
        } finally {
            ...
        }
    }

可以看到case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared) 这段代码,里面调用了readOrdinaryObject()方法,接下来我们进去看看,同样只截取我们需要的那部分贴出来:

 private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        ...
        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }

        ....
        return obj;
    }


注:Externalizable也是一个序列化接口,至于与Serializable的区别,总结如下:

1、Serializable序列化时不会调用默认的构造器,而Externalizable序列化时会调用默认构造器、

2、Serializable:一个对象想要被序列化就要实现Serializable接口,它所有属性(包括private及引用的对象)都可以被序列化和反序列化来保存、传递。 Externalizable是Serializable接口的子类,如果不希望序列化那么多可以使用这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性。

这里判断如果实现Externalizable接口(介绍参考代码块注解部分)则调用readExternalData(),没有实现则调用readSerialData(),ArrayList并没有实现Externalizable接口,我们直接进去readSerialData()方法里面看看:

  private void readSerialData(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
        for (int i = 0; i < slots.length; i++) {
            ObjectStreamClass slotDesc = slots[i].desc;
            ....
                        slotDesc.invokeReadObject(obj, this);
                    } 
                  ....
               ...
            }
        }
    }

可以看到ObjectStreamClass 对象调用了invokeReadObject()方法,继续进invokeReadObject()方法看看,

void invokeReadObject(Object obj, ObjectInputStream in)
        throws ClassNotFoundException, IOException,
               UnsupportedOperationException
    {
        if (readObjectMethod != null) {
            try {
                readObjectMethod.invoke(obj, new Object[]{ in });
            } catch (InvocationTargetException ex) {
               ......
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }

可以看到在判断readObjectMethod 属性不为空时通过反射调用的,具体的调用在ObjectStreamClass里面,部分源码如下:

                        writeObjectMethod = getPrivateMethod(cl, "writeObject",
                            new Class<?>[] { ObjectOutputStream.class },
                            Void.TYPE);
                        readObjectMethod = getPrivateMethod(cl, "readObject",
                            new Class<?>[] { ObjectInputStream.class },
                            Void.TYPE);
                        readObjectNoDataMethod = getPrivateMethod(
                            cl, "readObjectNoData", null, Void.TYPE);
                        hasWriteObjectData = (writeObjectMethod != null);

到这里我们就顺着这条线搞明白了为什么writeObject()和readObject()重写要用private修饰,主要是由getPrivateMethod决定的,

writeObjec()t的源码跟踪跟上面类似。

到这里我们就讲完了ArrayList实现的三个接口,相信对序列化和克隆已经有个初步的印象了,但是要深入的学习还需移步专业的书本去系统的学习,对于RandomAccess接口这里讲的还算全面,同时也列举了实例来说明,下一篇我们就着重讲解ArrayList实现的一些方法。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值