您的位置:首页 > 其它

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

2016-10-15 19:05 351 查看
一、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: 可以运行那些在单线程内不会改变单线程程序执行结果的重排序。所以:



在多线程环境下,这种重排序就会导致问题发生:实例可能还没有初始化,此时另一个线程调用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 extends Enum <T>> T valueOf(Class<T> 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<size; i++) {
s.writeObject(elementData[i]);
}

if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
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<? extends E> 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(Collection<?extendsE>c) {
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<? extends E> 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/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: