首页 > 编程知识 正文

arraylist线程安全(注解transient)

时间:2023-05-06 02:32:17 阅读:101359 作者:2497

在阅读数组列表源码时,发现保存元素的数组elementData使用短暂的修饰,该关键字声明数组默认不会被序列化。

/**

*存储数组列表元素的数组缓冲区。

*数组列表的容量是这个数组缓冲区的长度。任何的

*元素数据==DEFAULTCAPACITY _ EMPTY _元素数据的空数组列表

*将在添加第一个元素时扩展为默认容量.

*/

//Android-note:也可从java.util.Collections访问

瞬态对象[]元素数据;//非私有以简化嵌套类访问那么在序列化后,数组列表里面的元素数组保存的数据不就完全丢失了吗?深入研究代码后发现事实上,并不会,数组列表提供了两个用于序列化和反序列化的方法,readObject和writeObject:

/**

*将数组列表/tt实例的状态保存到流中(即

*是,序列化它)。

*

* @序列数据支持数组列表/tt的数组长度

*实例被发出(int),然后是它的所有元素

*(每个都是一个目标/tt)的正确顺序。

*/

private void WriteObject(Java。io。对象输出流

引发java.io.IOException{

//写出元素计数和任何隐藏的内容

int expectedModCount=modCount

s。DefaultWriteObject();

//将大小写为与克隆行为兼容的容量()

s.writeInt(大小);

//按照正确的顺序写出所有的元素。

for(int I=0;isizeI){ 0

s。write object(element data[I]);

}

if (modCount!=expectedModCount){ 0

抛出新的ConcurrentModificationException();

}

}

/**

*从一个流(即,

*反序列化它)。

*/

私有void ReadObject(Java。io。ObjectInputStream

引发java.io.IOException,ClassNotFoundException {

元素数据=空元素数据

//读取大小和任何隐藏的内容

s。DefaultReadObject();

//读入容量

s。ReadInt();//忽略

如果(大小0){ 0

//像克隆()一样,根据大小而不是容量来分配阵列

ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }

ArrayList在序列化的时候会调用writeObject,直接将size和element写入ObjectOutputStream; 反序列化时调用readObject,从ObjectInputStream获取size和element,再恢复到elementData。

###为什么不直接用elementData来序列化,而采用上面的方式来实现序列化呢?

原因在于elementData是一个缓存数组,默认size为10,对ArrayList进行add操作当空间不足时, 会对ArrayList进行扩容。通常扩容的倍数为1.5倍。

/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ 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); }

所以elementData数组会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上面的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。

###序列化的时候是怎么调用writeObject和readObject的 奇怪了?尽管writeObject和readObject被外部类调用但事实上这是两个private的方法。并且它们既不存在于java.lang.Object,也没有在Serializable中声明。那么ObjectOutputStream如何使用它们的呢?

序列化时需要使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。而 writeObject() 方法在传入的对象存在 writeObject() 的时候会去反射调用该对象的 writeObject() 来实现序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理类似。

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); oos.writeObject(list);

我们以ObjectInputStream为例,大体梳理一下调用流程,感兴趣的同学可以跟着读一下源码

首先,反序列化时会调用readObject() -> Object obj = readObject0(false) -> readObject0 -> return checkResolve(readOrdinaryObject(unshared)) -> readOrdinaryObject -> readSerialData(obj, desc);

然后readSerialData会调用slotDesc.invokeReadObject(obj, this) 这里调用ObjectStreamClass的invokeReadObject(Object obj, ObjectInputStream in) 里面的readObjectMethod.invoke(obj, new Object[]{ in });

这显然是一个通过反射进行的方法调用,那么readObjectMethod是什么方法? readObjectMethod = getPrivateMethod(cl, "readObject",new Class<?>[] { ObjectInputStream.class },Void.TYPE); writeObjectMethod = getPrivateMethod(cl, "writeObject",new Class<?>[] { ObjectOutputStream.class },Void.TYPE); 可以看到writeObjectMethod也在这里 getPrivateMethod方法如下:

/** * Returns non-static private method with given signature defined by given * class, or null if none found. Access checks are disabled on the * returned method (if any). */ private static Method getPrivateMethod(Class<?> cl, String name, Class<?>[] argTypes, Class<?> returnType) { try { Method meth = cl.getDeclaredMethod(name, argTypes); meth.setAccessible(true); int mods = meth.getModifiers(); return ((meth.getReturnType() == returnType) && ((mods & Modifier.STATIC) == 0) && ((mods & Modifier.PRIVATE) != 0)) ? meth : null; } catch (NoSuchMethodException ex) { return null; } }

到这里我们就大概上明白了,ObjectInputStream会通过反射的形式,调用private的readObject方法。 实现反序列化。

其实在java集合框架中,还有很多中集合都采用了这种方式,修饰数据集合数组,比如 CopyOnWriteArrayList private transient volatile Object[] elements; HashMap transient Node<K,V>[] table; HashSet private transient HashMap<E,Object> map;

究其原因,都是为了保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。

作者:gxdgz呆喵链接:https://juejin.cn/post/6951237430292774948来源:掘金

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。