008 Java集合浅析3
在前面的几节里,本教程从整体架构上去把握了JDK中的集合框架,并简单分析了其中Collection组的顶级接口,知道Collection接口的常见直接子接口有List、Set和Queue,并就这三个子接口的独有特性进行了简单地分析和比较。
本篇教程将会对实际编程中使用最频繁、最简单地ArrayList进行讲解,我先给出一个该类的类层次结构图。
从上面的结构图可以看出,集合框架的主题架构其实是以类为主体的,而不是接口。如果知道类、抽象类和接口之间的区别与联系,就能明白这里引入抽象类的原因。简单地说,如果不引入抽象类,每个具有Collection接口属性的类别都必须自己去实现该接口中的10多个方法,通过让类别直接继承自AbstractCollection这个抽象类,去除了重复代码。
首先,我们来看一下AbstractCollection这个抽象类的部分源码,如下:
public abstract class AbstractCollection<E> implements Collection<E> {
public boolean isEmpty() {
return size() == 0;
}
public boolean add(E e) {
throw new UnsupportedOperationException();
}
}
此抽象类就是Collection接口的实现,并实际实现了其中的某些方法,而且作为一个抽象类是不能直接用来实例化对象。
其次,再看看AbstractList这个抽象类的部分源码,如下:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
public boolean add(E e) {
add(size(), e);
return true;
}
}
此抽象类继承了AbstractCollection抽象类,并且实现了List接口,这就使得此类同时具备了普通Collection和特定的List类型集合的属性。注意:此类的subList方法返回的List<E>类型是两个内部类中的一个,要么是RandomAccessSubList<E>,要么是SubList<E>。
最后,我们再来仔细看看ArrayList的源码,部分源码如下:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final int DEFAULT_CAPACITY = 10;
private transient Object[] elementData;
}
这个ArrayList作为一个非抽象方法,已经实现了抽象父类和父接口中的所有方法。整体来说,并没有太多新的方法和特性加入,但其中值得关注的点不少,现在我们就来一一剖析。
第一个不寻常的地方在于ArrayList实现了Serializable接口,但是该类的重要成员elementData却使用transient修饰符。transient修饰符的作用正是让被其修饰的变量不参与正常的序列化中,那么ArrayList是如何实现Serializable的呢?秘密藏在该类的两个方法:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
}
我们知道对于实现了Serializable接口的类,我们可以直接使用ObjectOutputStream和ObjectInputStream进行对象的序列化和反序列化。当我们的类中包含有这两个方法时,ObjectOutputStream的writeObject方法就会调用类中的writeObject方法。注意:如果的看得够仔细,你会发现writeObject方法是私有的,同时该方法在Object中并不存在,那么ObjectOutputStream作为外部类是如何找到writeObject方法并调用成功的呢?如果你知道Java的反射机制的话,我相信你可能已经猜到了,此处正是使用的反射机制。这里之所以如何操作是为了效率,毕竟只需要序列化elementData中size大小的变量,而不是capacity大小的变量。
第二个不寻常的地方在于ArrayList类的clone()方法,从类的定义中可以看到该类实现了Cloneable接口,根据Cloneable接口的语义,ArrayList类实现了自己的clone()方法,我们看一下源码:
public Object clone() {
try {
@SuppressWarnings("unchecked")
ArrayList<E> v = (ArrayList<E>) 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();
}
}
源码面前无秘密,可以看到方法中对于保存ArrayList内部数据的数组变量elementData是做了复制的,但是并没有针对数组变量本身再进行递归复制,所以在注释中写明了此方法是一个浅复制。
第三个不寻常的地方在于ArrayList类中含有一个名为modCount的变量,名字其实是modifyCount的缩写,表示ArrayList对象被修改的次数。而且这个modCount记录的只是修改size变量的次数,对于ArrayList对象中变量的替换是不会改变modCount的值。那么ArrayList类为什么要辛苦地去维护这个modCount变量值呢?如果你写过遍历ArrayList集合的同时去删除集合中某些元素的代码,并且你不是使用的迭代器方式的话,那么你一定遇到过ConcurrentModificationException异常。背后的原理就在于remove方法会修改modCount的值,而整个foreach循环过程不允许modCount值发生改变。
第四点来看一下ArrayList的内存数组增长机制,ArrayList模拟可动态增长的数组,其采用的内存增长机制也是很多笔试和面试题喜欢设计的话题,我们直接来看一下源码:
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);
}
从源码可以出到,通常情况下就是一个50%的增长幅度,但也可以直接设定具体增长的目标值。同时,某些JVM实际可分配的最大数组大小并不能达到Integer.MAX_VALUE,而是Integer.MAX_VALUE - 8。
关于ArrayList<E>还有比较多的知识点,限于篇幅就不一一讲解了,像其中迭代器模式的实现,内部类的使用等就暂时先跳过啦,以后以机会再专门讲解。最后提一下ArrayList<E>实现的RandomAccess这个接口,它是一个标记接口,本身不包含任何成员,用来标记某些集合类具有随机访问性,从而在应用泛型算法可以考虑利用该特点来提高算法性能。
本系列文档会在本人的微信公众号发布,欢迎大家扫码关注。
008 Java集合浅析3的更多相关文章
- 009 Java集合浅析4
前面一篇教程中,我们分析了List派别中的最常见也最重要的一个类ArrayList<E>.从我们的分析来看,ArrayList作为动态数组的模拟,使用的是连续内存空间来存储数据,带来了可随 ...
- Java集合框架之TreeMap浅析
Java集合框架之TreeMap浅析 一.TreeMap综述: TreeMap在Map中的结构如下:
- Java集合框架之HashMap浅析
Java集合框架之HashMap浅析 一.HashMap综述: 1.1.HashMap概述 位于java.util包下的HashMap是Java集合框架的重要成员,它在jdk1.8中定义如下: pub ...
- Java集合框架之Map接口浅析
Java集合框架之Map接口浅析 一.Map接口综述: 1.1java.util.Map<k, v>简介 位于java.util包下的Map接口,是Java集合框架的重要成员,它是和Col ...
- Java集合框架之TreeSet浅析
Java集合框架之TreeSet浅析 一.TreeSet综述: 1.1TreeSet简介: TreeSet是Java集合框架的重要成员,先来看看TreeSet在jdk1.8中的定义吧: public ...
- Java集合框架之HashSet浅析
Java集合框架之HashSet浅析 一.HashSet综述: 1.1HashSet简介 位于java.util包下的HashSet是Java集合框架的重要成员,它在jdk1.8中定义如下: publ ...
- Java集合框架之Set接口浅析
Java集合框架之Set接口浅析 一.java.util.Set接口综述: 这里只对Set接口做一简单综述,其具体实现类的分析,朋友们可关注我后续的博文 1.1Set接口简介 java.util.se ...
- Java集合框架之Vector浅析
Java集合框架之Vector浅析 一.Vector概述: 位于java.util包下的Vector是Java集合框架的重要一员,虽然没有ArrayList那么的常用,但是我们还要对其做相关学习: 1 ...
- Java集合框架之LinkedList浅析
Java集合框架之LinkedList浅析 一.LinkedList综述: 1.1LinkedList简介 同ArrayList一样,位于java.util包下的LinkedList是Java集合框架 ...
随机推荐
- Bootstrap 输入组
Bootstrap 输入组: <!DOCTYPE html> <html lang="en"> <head> <meta charset= ...
- Linux笔记(三) - 文件搜素
(1)文件搜索:find-name 根据文件名, *匹配任意字符 ,?单个字符-iname 根据文件名, 不区分大小写-size 根据文件大小查找 (+ 大于 -小于)(-a并且 -o或者)-us ...
- 部署JForum 2.1.9遇到的问题及解决方法
1. 主要问题是出在连接数据库和创建表阶段,当我们配置好MySQL的各种参数后,创建表的时候会报错: 原因:主要是由于建表的SQL语句和MySQL的版本不一致导致的. 解决办法:简单来说,在MYSQL ...
- CAReplicatorLayer复制Layer和动画, 实现神奇的效果
今天我们看下CAReplicatorLayer, 官方的解释是一个高效处理复制图层的中间层.他能复制图层的所有属性,包括动画. 一样我们先看下头文件 @interface CAReplicatorLa ...
- iOS超全开源框架、项目和学习资料汇总:UI篇
上下拉刷新控件 1. MJRefresh --仅需一行代码就可以为UITableView或者CollectionView加上下拉刷新或者上拉刷新功能.可以自定义上下拉刷新的文字说明.(推荐) 2. S ...
- quartz配置时间
我们需要把log4j的配置文件放入src目录下,启动main类就可以了. Cron Expressions cron的表达式被用来配置CronTrigger实例. cron的表达式是字符串,实际上是由 ...
- mybatis入门-mapper代理原理
原始dao层开发 在我们用mybatis开发了第一个小程序后,相信大家对于dao层的开发其实已经有了一个大概的思路了.其他的配置不用变,将原来的test方法,该为dao的方法,将原来的返回值,直接在d ...
- Mysql条件的类型决定了是否能走索引
sku表总数据量将近7百万 SELECT count(1) from sku;
- Maven入门-4.Maven的依赖
1.Maven的依赖1.1 添加依赖1.2 依赖范围(sope)依赖范围与classpath的关系1.3 依赖的传递性1.2.1 依赖传递性的冲突问题1. 第一种情况2. 第二种情况1.2.2 通过e ...
- Professional C# 6 and .NET Core 1.0 - 40 ASP.NET Core
本文内容为转载,重新排版以供学习研究.如有侵权,请联系作者删除. 转载请注明本文出处:Professional C# 6 and .NET Core 1.0 - 40 ASP.NET Core --- ...