文章目录
探索 ArrayList 原理
jdk1.8 API
黑马教学视频: java进阶教程丨全面深入解析ArrayList原理(源码分析+面试讲解)
2. ArrayList 继承体系
- 源码结构图如下
- ArrayList主要实现了List接口,同时标记为可以序列化Serializable、可复制CloneAble、支持随机访问RandomAccess。
3. ArrayList 继承体系源码分析
- ArrayList
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
3.1 Serializable 接口
- 序列化
- 1、将一个Java对象变成字节序列,方便持久化存储到磁盘,避免程序运行结束后对象就从内存里消失,
- 2、变换成字节序列也更便于网络运输和传播。
- 3、序列化机制从某种意义上来说也弥补了平台化的一些差异,毕竟转换后的字节流可以在其他平台上进行反序列化来恢复对象,因此可以实现跨平台存储。
- Serializable 接口,就是序列化接口,实现此接口的类将不会使任何状态序列化或反序列化;
- 序列化: 将
对象
的数据写入到文件(写对象
) - 反序列化: 将文件中
对象
的数据读取出来 - 序列化
- 示例如下:
写对象示例
注意必须传一个对象,序列化也指的就是序列化对象 - ApeEntity 实例
@NoArgsConstructor @AllArgsConstructor @Data public class ApeEntity { private String id; private String sbmc; private String sblx; private String ipdz; }
- 测试写入文件
public static void main(String[] args) throws IOException { ApeEntity ape1 = new ApeEntity("1001","天地","天地","192.168.1.2"); ApeEntity ape2 = new ApeEntity("1002","海康","海康","192.168.1.2"); ApeEntity ape3 = new ApeEntity("1003","大华","大华","192.168.1.2"); ArrayList<ApeEntity> list = new ArrayList<>(); list.add(ape1); list.add(ape2); list.add(ape3); writeObjStream("D:/temp/exchange_snmp1.log", list); } public static void writeObjStream(String filepath, ArrayList<ApeEntity> list) throws IOException { FileOutputStream outputStream = new FileOutputStream(filepath); ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(list); objectOutputStream.close(); }
- 实体对象
ApeEntity
未实例化 ,执行上面代码报错;Exception in thread "main" java.io.NotSerializableException: arrayList.ApeEntity at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at java.util.ArrayList.writeObject(ArrayList.java:766) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1140) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at arrayList.source.ArrayListSerializableDemo.writeObjStream(ArrayListSerializableDemo.java:77) at arrayList.source.ArrayListSerializableDemo.main(ArrayListSerializableDemo.java:28)
- ApeEntity 实例序列化
@NoArgsConstructor @AllArgsConstructor @Data public class ApeEntity implements Serializable { private String id; private String sbmc; private String sblx; private String ipdz; }
- 序列化后效果,在目的位置生成了文件
- 序列化后效果,在目的位置生成了文件
- 示例如下:
- 反序列化
- 示例代码如下:
public static void main(String[] args) throws IOException, ClassNotFoundException { readObjStream("D:/temp/exchange_snmp1.log"); } public static void readObjStream(String filepath) throws IOException, ClassNotFoundException { FileInputStream fileInputStream = new FileInputStream(filepath); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); List<ApeEntity> list = (List<ApeEntity>) objectInputStream.readObject(); objectInputStream.close(); System.out.println(list.toString()); }
- 反序列化执行效果,
Connected to the target VM, address: '127.0.0.1:50838', transport: 'socket' [ApeEntity(id=1001, sbmc=天地, sblx=天地, ipdz=192.168.1.2), ApeEntity(id=1002, sbmc=海康, sblx=海康, ipdz=192.168.1.2), ApeEntity(id=1003, sbmc=大华, sblx=大华, ipdz=192.168.1.2)] Disconnected from the target VM, address: '127.0.0.1:50838', transport: 'socket'
- 反序列化执行效果,
- 序列化: 将
3.2 CloneAble 接口
-
一个类实现CloneAble 接口来指示Object.clone() 方法,该方法对于该类的实例进行字段的复制是合法的。
-
简而言之:克隆就是依据已经有的数据,创造一份新的完全一样的数据拷贝;
-
克隆的前提条件:
- 1、被克隆对象所在的类必须实现 CloneAble 接口;
- 2、必须重写clone方法;
-
源码分析:
public Object clone() { try { // 调用父类(Object)的clone()方法,克隆一个object对象,然后强转为ArrayList ArrayList<?> v = (ArrayList<?>) super.clone(); // 调用Arrays类的copyOf,将ArrayList的elementData数组赋值给副本的elementData数组 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); } }
-
clone 的基本使用
public static void main(String[] args) { Student student1 = new Student("xiaoming1","man",0); Student student2 = new Student("xiaoming2","man",0); Student student3 = new Student("xiaoming3","man",0); ArrayList<Student> stuList = new ArrayList<>(); stuList.add(student1); stuList.add(student2); stuList.add(student3); ArrayList<Student> stuList2 = (ArrayList)stuList.clone(); for (Student stu:stuList2 ) { System.out.println(stu); } System.out.println(stuList == stuList2); }
示例结果
Student(name=xiaoming1, sex=man, age=0) Student(name=xiaoming2, sex=man, age=0) Student(name=xiaoming3, sex=man, age=0) false
-
案例分析:
- 已知学生对象
{姓名:小明,性别 男,年龄10}
,现需要将学生小明复制到另一个对象中去,且两个之间互相不影响- 方法一:
public static void test1(){ Student stu1 = new Student("小明","男",10,null); Student stu2 = new Student(); stu2.setName(stu1.getName()); stu2.setSex(stu1.getSex()); stu2.setAge(stu1.getAge()); }
- 方法二 克隆:注意student 需要实现 Cloneable,并重写clone方法;
- studet.java
@Data @AllArgsConstructor @NoArgsConstructor public class Student Cloneable{ private String name; private String sex; private Integer age; @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
- 克隆
示例结果如下Student stu1 = new Student("小明","男",10); Object clone = stu1.clone(); System.out.println(stu1 == clone); System.out.println(stu1); System.out.println(clone); stu1.setAge(100); System.out.println(stu1); System.out.println(clone);
false Student(name=小明, sex=男, age=10) Student(name=小明, sex=男, age=10) Student(name=小明, sex=男, age=100) Student(name=小明, sex=男, age=10)
以上实例简单使用浅克隆,完全可以满足需求,如果说student 中还内嵌对象,如学生中还技能对象,那么浅克隆就满足不了了;
- 如下学生类示例
技能,特长类public class Student implements Serializable ,Cloneable{ private String name; private String sex; private Integer age; private Skill skill; //技能,特长 @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
public class Skill { private String name; }
- 示例代码如下
public static void test4() throws CloneNotSupportedException { Student stu1 = new Student(); stu1.setName("小明"); stu1.setSex("男"); stu1.setAge(10); Skill skill = new Skill("跆拳道"); stu1.setSkill(skill); Object clone = stu1.clone(); System.out.println(stu1 == clone); System.out.println(stu1); System.out.println(clone); stu1.setAge(100); skill.setName("武术"); stu1.setSkill(skill); System.out.println(stu1); System.out.println(clone); }
- 示例代码效果如下
false Student(name=小明, sex=男, age=10, skill=Skill(name=跆拳道)) Student(name=小明, sex=男, age=10, skill=Skill(name=跆拳道)) Student(name=小明, sex=男, age=100, skill=Skill(name=武术)) Student(name=小明, sex=男, age=10, skill=Skill(name=武术))
当修改学生的值时,影响到了克隆出来学生的值 ,此时浅克隆已经满足不了需求,这时我们需要用到深克隆
- 深克隆
- 技能、特长类实现 Cloneable
public class Skill implements Cloneable{ private String name; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
- 学生类重写 Clone 方法
public class Student implements Serializable ,Cloneable{ private String name; private String sex; private Integer age; private Skill skill; @Override public Object clone() throws CloneNotSupportedException { //深克隆不能简单的调用父类的方法 //引用super.clone 先克隆出一个 object,然后向下转型 Student stu = (Student)super.clone(); Skill skill = (Skill)this.skill.clone(); stu.setSkill(skill); return stu; } }
- 技能、特长类实现 Cloneable
- 直接还是用 test4() 示例测试,效果如下
false Student(name=小明, sex=男, age=10, skill=Skill(name=跆拳道)) Student(name=小明, sex=男, age=10, skill=Skill(name=跆拳道)) Student(name=小明, sex=男, age=10, skill=Skill(name=武术)) Student(name=小明, sex=男, age=10, skill=Skill(name=跆拳道))
相互之间已经做到了互不影响
- studet.java
- 方法一:
- 已知学生对象
3.3 RandomAccess 接口
- 标记接口由List实现使用,以表明它们支持快速(通过为恒定时间)随机访问
- 此接口的主要目的是允许通用算法更改其行为,以便在应用于随机访问列表和顺序访问列表时提供良好的性能
- 需要值得让人注意的是,List 实现了此接口,如果对于类的典型实例,此种方式比迭代器实例(iterator)更快
- 典型实例
for(int i = 0 , n = list.size(); i< n ;i++) list.get(i);
- 迭代器实例
for(Iterator i=list.iterator(); i.hasNext();) i.next();
- 典型实例
- 案例演示1
ArrayList
随机访问列表和顺序访问列表哪种方式更快
示例结果如下public static void test(){ ArrayList<Integer> stuList = new ArrayList<>(); for(int i = 0;i < 100000 ; i++){ stuList.add(i); } //测试随机访问时间 long startMillis = System.currentTimeMillis(); for (int i = 0 ; i< stuList.size();i++) { stuList.get(i); } long endMillis = System.currentTimeMillis(); System.out.println("ArrayList-随机访问列表耗时:"+(endMillis - startMillis)); //测试顺序访问时间 Iterator<Integer> iterator = stuList.iterator(); startMillis = System.currentTimeMillis(); while (iterator.hasNext()){ iterator.next(); } endMillis = System.currentTimeMillis(); System.out.println("ArrayList-顺序访问列表耗时:"+(endMillis - startMillis)); }
ArrayList-随机访问列表耗时:1 ArrayList-顺序访问列表耗时:3
因为ArrayList 是实现了RandomAccess 接口的,所以它会比Iterator 快;
- 案例演示2
LinkList
随机访问列表和顺序访问列表哪种方式更快
示例结果如下public static void test2(){ LinkedList<Integer> stuList = new LinkedList<>(); for(int i = 0;i < 100000 ; i++){ stuList.add(i); } long startMillis = System.currentTimeMillis(); for (int i = 0 ; i< stuList.size();i++) { stuList.get(i); } long endMillis = System.currentTimeMillis(); System.out.println("LinkedList-随机访问列表耗时:"+(endMillis - startMillis)); Iterator<Integer> iterator = stuList.iterator(); startMillis = System.currentTimeMillis(); while (iterator.hasNext()){ iterator.next(); } endMillis = System.currentTimeMillis(); System.out.println("LinkedList-顺序访问列表耗时:"+(endMillis - startMillis)); }
LinkedList-随机访问列表耗时:4581 LinkedList-顺序访问列表耗时:1
因为LinkedList 是没有RandomAccess 接口的,所以它会比随机访问更快;还有需要值 得注意的时候,在添加数据的时候我们很明显能看出来linkedList 添加数据是比较慢的:
linkedList 添加数据是比较慢原因:当数据量大时,ArrayList每次扩容都能得到很大的新空间,解决了前期频繁扩容的劣势,而LinkedList虽然有尾指针,但是每次add都要将对象new成一个Node(而ArrayList直接数组对应位置元素赋值)
3.3.1 ArrayList 实际开发应用场景应用
- 基于实现 RandomAccess接口之后,随机访问列表比顺序访问列表效率更高,所以建议当查询出结果之后进行判断是否实现了接口RandomAccess,然后判断自己使用哪种方式的遍历;
- 直接上判断代码如下:
//模拟从数据库查询出数据结果 List<User> list = dao.query(example); //判断 if(list.instanceof RandomAccess){ //随机访问 for(int i = 0; i < list.size(); i++){ …… } }else{ //顺序访问 for(User user:List){ …… } }
3.4 ArrayList 抽象类
-
ArrayList extends
AbstractList
AbstractList extends
AbstractCollection
java中所有类都继承 Object,如下图 ArrayList的继承结构。
-
AbstractList是一个抽象类,实现了List接口,List定义了一些List通用方法,而AbstractList抽象类中可以有抽象方法,还可以有具体的实现方法, AbstractList实现接口中一些通用的方法,实现了基础的add/get/indexOf/iterator/subList/RandomAccessSubList方法,ArrayList再继承AbstractList,拿到通用基础的方法,然后自己在实现一些自己特有的方法,这样的好处是:让代码更简洁,继承结构最底层的类中通用的方法,减少重复代码。