ArrayList 源码分析 (顺便复习序列化,单例)

一、Something about ArrayList

- 每次添加超过限制,列表就会增加50%容量,每次扩容挺浪费时间的,如果一开始就知道大概的列表长度,可以直接构造

- 采用System.arrayCopy()复制到新数组

- 列表可以按数组下标访问元素-get(i)/set(i)

- remove时,需要复制移动元素,性能很差

- 线程不安全,在多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类~


ArrayList实现了,

1. java.io.Serializable接口,支持序列化

复习1:

每个枚举类型都会继承类java.lang.Enum,而该类实现了Serializable接口,所以所有类型对象都是默认可以被序列化的。当对象C被持久化到一个A地文件中时,必须确保B地的classpath中包含C.class才可以从A地还原对象。

因为序列化是持久化对象的状态,所以会自动忽略静态数据。如果有不想序列化的数据域时,可以采用关键字transient来修饰,也可以使用transient数据再次被序列化-重写writeObject和readObject:

transient private Integer age = null;

private void writeObject(ObjectOutputStream out) throws IOException {
     out.defaultWriteObject();
     out.writeInt(age);     
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
     in.defaultReadObject();
     age = in.readInt();
}
readObject和writeObject都是private方法,而且不存在Object,Serializable,对象流定义中,所以它们是如何被对象流调用的呢?反射~

假设两个对象同时引用同一个对象,我们不能只存储内存引用地址,因为当对象重新被加载中,可能与原来的内存地址完全不同,所以被引用的对象也将被序列化。序列化时,每个对象都有一个序列号,第一次遇到的时候,将该对象保存到流中,再次遇到,只会写入类似“与序列号X对象相同”,读取的时候也一样,第一次遇到去构建,再次遇到的之后直接获取相关的对象引用!

package kevin.seria;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Simulator {
    public static void main(String[] args) {
        new Simulator().go();
    }

    private void go(){
        try {
            ObjectOutputStream out  = new ObjectOutputStream(new FileOutputStream("seria"));
            Student student1 = new Student(new NewBook(2011,"moree"),"kevin");
            out.writeObject(student1); //
            student1.setName("Jordan");
            out.writeObject(student1);
            student1.setName("Paul");
            out.writeObject(student1);
            System.out.println("object has been written..");
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try{
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("seria"));
            Student s1 = (Student)in.readObject();
            Student s2 = (Student)in.readObject();
            Student s3 = (Student)in.readObject();
            System.out.println("Objects read here: ");
            System.out.println("Student1's name: "+s1.getName());
            System.out.println("Student2's name: "+s2.getName());
            System.out.println("Student3's name: "+s3.getName());
        }catch(FileNotFoundException e){
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
一个基类实现了序列化接口,那么它的子类都可以序列化。父类没有实现序列化接口,而子类实现了该接口的时候,反序列化的时候会调用父类的无参构造函数,如果父类没有无参构造函数会抛出异常~

使用Externalizable接口之后,之前基于Serializable接口的序列化机制就将失效~序列化的细节需要程序员去完成,如果writeExternal()和readExternal()未做处理,序列化行为将不会保存或者读取任何一起字段。读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将保存对象的字段的值填充到新的对象中~所以实现Externalizable接口的类必须要提供一个无参构造器,并且它的访问权限必须为public~

private String name = "Andy";
transient private Integer age = 12;

public void writeExternal(ObjectOutput out) throws IOExeception {
     out.writeObject(name);
     out.writeInt(age);
}

public void writeExternal(ObjectInput in) throws IOException, ClassNotFoundException {
     name = (String) in.readObject();
     age = in.readInt();
}

在单例模式中,我们期望某个类的实例始终唯一,但是采用序列化会返回一个新的对象,该怎么办呢?此时需要定义readResolve方法来代替,返回唯一的单例对象,对象序列化之后就可以直接调用它~但是无论是Serializable或者Externalizable接口,都可以定义readResolve~

protected Object readResolve() throws ObjectStreamException

指纹是一个根据类名,接口名,成员方法以及属性等来生成一个64位的哈希字段。可以利用JDK的bin目录下的serialver.exe工具产生这个serialVersionUID的值,命令为“serialver 类名”。类如果发生变化,它的SHA指纹也会跟着变化。除此之外,同一个类在不同的JVM上生成的SHA可能会不一样,这样在反序列化的过程中就会出现问题,因为如果SHA不一样的话,会抛出异常。为了实现一个类对其早期版本的兼容,可以将指纹定义到一个静态常量serialVersionUID里面。

当指纹一致,假如类的方法发生了改变,则读入新对象数据的时候不会发生任何问题。假如流中的对象具有在当前版本中所没有的数据域,那么会忽略这些额外的数据,相反的如果当前版本中有流中的对象没有的数据域,则会将这些新添加的域设置为它们的默认值。但是如果两部分数据之间名字匹配而类型不匹配的话就不会兼容~

复习2:

单例模式7种写法:

1)懒汉

- 线程不安全

public class Singleton {
   private static Singleton s = null;
   private Singleton() {}
   public static Singleton getInstance() {
       if(s == null) 
           s = new Singleton();
       return s;
   }
}
- 线程安全
public static sysnchronized Singleton getInstance()
效率不高,每次访问都需要等待另一个线程访问完毕才可以进入~

2)饿汉

public class Singleton {
   private static Singleton s = new Singleton();
   private Singleton() {}
   public static Singleton getInstance() {
       return s;
   }
}
类加载的时候就创建单例,并存放在内存中,如果单例占用的内存空间比较大,那饿汉方式就不大合适了~
3)静态内部类
public class Singleton {
   private static class SingletonHolder {
       private static final Singleton s = new Singleton();
   }
  
   private Singleton() {}
  
   public static Singleton getInstance() {
       return SingletonHolder. s;
   }
}
当调用getInstance方法的时候才去加载SinglerHolder~实现lazy loading~

4)双重校验锁

是懒汉方式的升级版~避免synchronized带来的效率问题~

public class Singleton {
   private static Singleton s = null;
   private Singleton() {}
   public static Singleton getInstance() {
       if(s == null)
           synchronized(Singleton. class) {
               if( s == null)
                   s = new Singleton();
           }
       return s;
   }
}
如果instance不为null,那么不需要执行加锁和初始化操作,减少了synchronized带来的开销,但是,s = new Singleton()会带来风险,这句话可以分解为:

1. 在内存中为对象分配空间

2. 初始化对象

3. 将变量指向刚分配的内存地址

根据JAVA语言规范,所有线程在执行程序时要遵守intra-thread semantics: 可以运行那些在单线程内不会改变单线程程序执行结果的重排序。所以:

ArrayList 源码分析 (顺便复习序列化,单例)_第1张图片

在多线程环境下,这种重排序就会导致问题发生:实例可能还没有初始化,此时另一个线程调用getInstance,就会取到状态不正确的对象,程序就会出错~而volatile的一个语义是禁止指令重排序优化~

private volatile static Singleton s = null;
5)枚举(Effective java推荐)
public enum Singleton {
    INSTANCE;
    public void relevantMethod() {}
}
- 不需要像其他方法一样需要额外的工作来实现序列化

- 其他方法可以使用反射强行调用私有构造器

public enum Season {
    SPRING, SUMMER, AUTUMN, WINTER;
}
public final class T extends Enum { //不能继承
     public static final T SPRING;
     public static final T SUMMER;
     public static final T AUTUMN;
     public static final T WINTER;
     private static final T ENUM$VALUES[];

     private T(String name, int i) {
          super(name, i);     
     }

     static{
          SPRING = new T("SPRING, 0");
          SUMMER = new T("SUMMER, 1");
          AUTUMN = new T("AUTUMN, 2");
          WINTER= new T("WINTER, 3");
          ENUM$VALUES = (new T[] {SPRING, SUMMER, AUTUMN, WINTER});
     }

     public static T[] values() {
          T at[];
          int i;
          T at1[];
          System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
          return at1;
     }
     public static T valueOf(String s) {
          ......
     }
}
枚举类型在序列化的时候,仅仅将枚举对象的name属性输出到结果中,然后反序列化的时候则通过java.lang.Enum的valueOf方法根据名字查找枚举对象。编译器不允许任何对序列化的定制,因此禁止了writeObject, readObject等方法~
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
     throw new InvalidObjectException( "can't deserialize enum");
}
public static > T valueOf(Class enumType , String name) {
        T result = enumType.enumConstantDirectory().get( name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException( "Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType .getCanonicalName() + "." + name );
}
enumType会通过反射T类的values方法,获取所有的实例!然后构建一个名字和实例对应的Map-enumConstantDirectory

应用场景:

> 线程池

> 网站计数器

> 数据库连接池

> 任务管理器

公共访问节点,生成唯一标识等,比如线程池,网站计数器,数据库连接池:

public class Test1{
    private static Test1 t1= null; 
    private int i = 0;
    private Test1() {}
    public static Test1 getInstance() {
        if(t1 == null)
            synchronized(Test1.class) {
                if(t1 == null)
                    t1 = new Test1();
            }
        return t1;
    }
    
    public void add(String s) throws InterruptedException {
        System.out.println(s);
        Thread.sleep(500);
        System.out.println(i++);
    }
    public static void main(String[] args) {
        Thread t1 = new Thread(new T1());
        t1.start();
        Thread t2 = new Thread(new T2());
        t2.start();
    }
}


class T1 implements Runnable {


    @Override
    public void run() {
        Test1 t1 = Test1.getInstance();
        try {
            t1.add("T1");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
}


class T2 implements Runnable {


    @Override
    public void run() {
        Test1 t1 = Test1.getInstance();
        try {
            t1.add("T1");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

http://blog.csdn.net/likika2012/article/details/11483167

2. RandomAccess接口,支持快速随机访问

3. Clonable接口,支持被克隆

public Object clone() {
        try {
            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);
        }
}
所有的类都默认继承Object,所以都可以调用super.clone来进行浅拷贝(只拷贝对象引用而不是具体的值),也可以重写clone来实现深拷贝~
- x.clone() != x 
- x.clone().getClass() = x.getClass() 两个对象是同一个类型
- x.clone().equals(x) 内容一样
Arrays.copyOf依赖于System.arraycopy,都是浅拷贝

                              修改

懒惰拷贝:浅拷贝 ---> 深拷贝

可以通过序列化来实现深拷贝~


三者都是标识接口,没有任何方法和属性,当一个类实现了一个标识接口之后就像是给自己打了个标签:

private void writeObject0(Object obj, boolean unshared) throws IOException { 
      ...
    if (obj instanceof String) { 
        writeString((String) obj, unshared); 
    } else if (cl.isArray()) { 
        writeArray(obj, desc, unshared); 
    } else if (obj instanceof Enum) { 
        writeEnum((Enum) obj, desc, unshared); 
    } else if (obj instanceof Serializable) { 
        writeOrdinaryObject(obj, desc, unshared); 
    } else { 
        if (extendedDebugInfo) { 
            throw new NotSerializableException(cl.getName() + "\n" 
                    + debugInfoStack.toString()); 
        } else { 
            throw new NotSerializableException(cl.getName()); 
        } 
    } 
    ... 
} 
二、ArrayList源码分析

1. 属性

transient Object[] elementData;
private int size;
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

为什么elementData是transient?

因为elementData是一个缓存数组,所以有些空间并没有元素,都序列化的话,太浪费空间了~

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i
2. 构造方法

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() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if ( elementData.getClass() != Object[]. class)
                elementData = Arrays. copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this. elementData = EMPTY_ELEMENTDATA;
        }
}

6260652: http://www.tuicool.com/articles/uIBB3q,http://blog.csdn.net/u014082714/article/details/51811998

Arrays有一个内部类ArrayList(不支持add,remove等改变list方法),asList会返回该ArrayList,而不是java.util.ArrayList~

3. 元素存储

public E set( int index, E element) {
        rangeCheck( index);

        E oldValue = elementData( index);
        elementData[index] = element;
        return oldValue;
}
private void rangeCheck( int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index ));
}
    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 ));
    }
public boolean add(E e) {
        ensureCapacityInternal( size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
}

private void ensureCapacityInternal (int minCapacity ) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math. max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity (int minCapacity ) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow( minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer. MAX_VALUE - 8;
private void grow( int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData , newCapacity );
}

private static int hugeCapacity (int minCapacity ) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer. MAX_VALUE :
            MAX_ARRAY_SIZE;
}
容量扩增:

- 如果是空实例{},就采用默认的容量10

- 判断minCapacity是否overflow(大于Integer.MAX_VALUE的话会溢出为负)或是否大于数组长度(minCapacity = size + n, minCapacity - elementData.length?>0)

1)newCapacity = 1.5*oldCapacity

2)如果newCapacity溢出或者小于minCapacity(要保证最小空间大小)的情况下,newCapacity重新取值minCapacity

3)如果newCapacity大于MAX_ARRAY_SIZE,就判断minCapacity是否大于MAX_ARRAY_SIZE,大于就取Integer.MAX_VALUE,否则就取MAX_ARRAY_SIZE~(继续用newCapacity很容易就溢出,但是实际上很多空间都还没装满元素,用minCapacity即满足空间要求,又不会太浪费)

public boolean addAll(Collectionc) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal (size + numNew );  // Increments modCount
        System.arraycopy(a , 0, elementData , size , numNew );
        size += numNew;
        return numNew != 0;
}

public boolean addAll(int index , Collection c) {
        rangeCheckForAdd( index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal( size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System. arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a , 0, elementData , index , numNew );
        size += numNew;
        return numNew != 0;
}

4. 删除元素

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; // clear to let GC do its work

        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; // clear to let GC do its work
}
ArrayList元素可以是null~
protected void removeRange( int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData , toIndex , elementData , fromIndex,
                         numMoved);

        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[ i] = null;
        }
        size = newSize;
}

removeRange为protected方法,所以继承该类才能被使用,一般使用list.subList(start, end).clear();来代替removeRange,而clear实质也是调用SubList里面的removeRange方法。

快速失败机制:依赖于modcount,在面对并发的修改时,迭代器很快就会完全失败,而不是冒着将来某个不确定时间发生任意不确定行为的风险

Reference:
http://blog.csdn.net/li295214001/article/details/48135939
http://blog.jobbole.com/94074/
http://blog.csdn.net/goodlixueyong/article/details/51935526
http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html
http://blog.csdn.net/likika2012/article/details/11483167
http://developer.51cto.com/art/201202/317181.htm
http://stackoverflow.com/questions/2289183/why-is-javas-abstractlists-removerange-method-protected
http://blog.csdn.net/moreevan/article/details/6698529
http://www.cnblogs.com/ITtangtang/p/3948555.html
http://yikun.github.io/2015/04/04/Java-ArrayList%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E5%8F%8A%E5%AE%9E%E7%8E%B0/

你可能感兴趣的:(数据结构与算法)