Java集合系列(一)List集合

目录 List的几种实现的区别与联系 List转换及删除元素 数组转List 集合 如何在遍历时删除ArrayList中元素 SynchronizedList与Vector的区别 什么是快速失败, 安全失败机制 快速失败(fail-fast) 安全失败(fail-safe) 对象排序 实体类自身具备排序能力 使用比较器排序 参考资料 List的几种实现的区别与联系 List主要有ArrayList、LinkedList与Vector几种实现。 ArrayList底层数据结构是数组, 增删慢、查询快; 线程不安全, 效率高; 不可以设置扩展容量, 默认增长1.5倍; 无参构造器初始化时, 初始容量为0。 LInkedList底层数据结构是链表, 增删快、查询慢; 线程不安全, 效率高。 Vector底层数据结构是数组, 增删慢、查询快; 线程安全, 效率低; 可以设置扩展容量, 默认增长2倍; 无参构造器初始化时, 初始容量为10。 List转换及删除元素 数组转List 集合 对于一个数组, 可以通过 Arrays.asList(T... a) 方法转换成List集合, 需要注意的是, 此方法得到的ArrayList对象是基于Arrays内部类 java.util.Arrays$ArrayList 来创建的, 而非 java.util.ArrayList 。这就涉及到了一个问题, 通过 asList(T... a) 转换得到的List集合是不允许进行增删操作的, 我们先看如下代码: 复制代码 @Test public void convertList() { List list = Arrays.asList("张小凡", "陆雪琪", "碧瑶"); //list.add("qingshanli"); list.remove(0); } 复制代码 运行时会报异常 java.lang.UnsupportedOperationException 我们先来看看 java.util.Arrays$ArrayList 的方法层次结构: 可以看出, java.util.Arrays$ArrayList 并没有覆写父类AbstractList的 add() 和 remove() 方法, 根据Java的三大特性之多态性可知, 上面代码中的增删操作实际调用的是父类AbstractList的方法, 我们再来看看AbstractList的部分源代码: 复制代码 public E set(int index, E element) { throw new UnsupportedOperationException(); } public void add(int index, E element) { throw new UnsupportedOperationException(); } public E remove(int index) { throw new UnsupportedOperationException(); } 复制代码 到此, 可以得知其实通过 Arrays.asList(T... a) 转换得到的List集合是一个固定长度的集合, 所以不能进行增删操作。 如何在遍历时删除ArrayList中元素 方式一: 普通循环 复制代码 public void test(List list) { for (int i = 0; i < list.size(); i++) { if (list.get(i) % 2 == 0) { list.remove(list.get(i)); i--; // 索引改变! } } } 复制代码 这种方式在删除操作时, 会改变集合的索引和size大小, 遍历时可能会产生角标越界异常, 因此不是特别推荐。 方式二: 高级for循环 复制代码 public static void main(String[] args) { List list = new ArrayList(); for (int i = 0; i < 5; i++){ list.add(i); } for (Integer num : list) { System.out.print("value="+num); if (num % 2 == 0) { list.remove(num); System.out.println(" delete"); }else{ System.out.println(" not delete"); } } } 复制代码 运行结果如下, 第一个元素删除正常, 后面继续遍历删除则抛异常 java.util.ConcurrentModificationException 如下, 反编译上述代码, 可以看出高级for循环底层其实就是使用iterator迭代器来进行遍历 复制代码 public static void main(String[] args) { ArrayList list = new ArrayList(); for (int i = 0; i < 5; ++i) { list.add(Integer.valueOf((int)i)); } Iterator i = list.iterator(); while (i.hasNext()) { Integer num = (Integer)i.next(); System.out.print((String)new StringBuilder().append((String)"value=").append((Object)num).toString()); if (num.intValue() % 2 == 0) { list.remove((Object)num); System.out.println((String)" delete"); continue; } System.out.println((String)" not delete"); } } 复制代码 再来看看iterator迭代器实现类的部分源代码: View Code 可以看出, 迭代器内部的每次遍历操作 next() 、 remove() 方法都会记录List集合内部的modCount当做预期值expectedModCount, 然后在每次循环中判断预期值expectedModCount与List的成员变量modCount是否相等。但是因为上面 list.remove() 调用的是List集合的 remove() 方法, 继续跟踪源代码发现每次调用该方法就会 modCount++; , 但是迭代器内记录的预期值expectedModCount并没有跟着改变, 所以当第二次删除操作时就会发生异常。 方式三: iterator迭代器遍历 复制代码 public void test(List list) { Iterator it = list.iterator(); while (it.hasNext()) { int num = it.next(); System.out.print("value="+num); if (num % 2 == 0) { it.remove(); System.out.println(" delete"); }else{ System.out.println(" not delete"); } } } 复制代码 原理与方式二基本类似, 但是这里使用的是迭代器iterator的 remove() 方法, 我们再回顾之前迭代器实现类的源代码, 发现 remove() 方法中有 expectedModCount = modCount; 这个操作, 即调用迭代器的 remove() 方法时会同步List集合的modCount到迭代器的预期值expectedModCount当中, 所以迭代器方式删除才不会产生。 SynchronizedList与Vector的区别 什么是快速失败, 安全失败机制 快速失败(fail-fast) fail-fast机制是java集合中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。 使用迭代器遍历一个集合对象时,如果遍历过程中对集合进行了增删改, 则会抛出 ConcurrentModificationException 。 复制代码 for (Integer id : list) { if (id == 2) { list.remove(id); } } 复制代码 在前面我们已经介绍过, 迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个modCount变量来作为预期值expectedmodCount。集合在被遍历期间如果内容发生变化,就会改变集合内部的modCount值。每当迭代器使用而而迭代遍历调用 next() 方法时每次都会检测 if(modCount==expectedmodCount) ,符合条件就返回遍历;否则将抛出异常终止遍历。 如果集合发生变化时修改modCount值, 并且又同步到expectedmodCount预期值, 比如前文中提到的iterator迭代器的 remove() 方法, 异常则不会抛出。因此, 不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。另外, 在java.util包下的集合类都是快速失败的, 是不能在多线程下发生并发修改的(即迭代过程中被修改)。 安全失败(fail-safe) 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是在开始遍历时先复制原有集合内容,在拷贝的集合上进行遍历, 即在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。java.util.concurrent包下的容器都是安全失败的, 可以在多线程下并发修改。 对象排序 实体类自身具备排序能力 Comparable接口用于使某个类具备可排序能力。实体类实现该接口后覆写其 compareTo() 方法,即可使实体类自身具备可排序的能力 。代码清单如下: 复制代码 public class Student implements Comparable { private String name; private int age; @Override public int compareTo(Student o) { int flag = this.name.compareTo(o.name); if(flag == 0) { flag = this.age - o.age; } return flag; } } 复制代码 实体类具备了排序能力后, 调用List集合的 sort(Comparator c) 或者Collections工具类的 sort(List list) 方法即可实现排序。 复制代码 List list = new ArrayList(); list.sort(null); //Collections.sort(list); 复制代码 使用比较器排序 Comparator是一个比较器接口,可以用来给不具备排序能力的对象进行排序。实现该比较器需覆写其 compare() 方法即可进行排序, 代码清单如下: 复制代码 public class Student { private String name; private int age; } public class StudentComparator implements Comparator { @Override public int compare(Student o1, Student o2) { int flag = o1.getName().compareTo(o2.getName()); if(flag == 0) { flag = o1.getAge() - o2.getAge(); } return flag; } } public class Test { public void sortTest() { List list = new ArrayList();   list.sort(new StudentComparator()); //Collections.sort(list, new StudentComparator()); } } 复制代码 参考资料 java中的增强for循环的实现原理与坑 Java中的fail-fast机制 每个人都是自己的上帝。如果你自己都放弃自己了,还有谁会救你?------《肖申克的救赎》目录 List的几种实现的区别与联系 List转换及删除元素 数组转List 集合 如何在遍历时删除ArrayList中元素 SynchronizedList与Vector的区别 什么是快速失败, 安全失败机制 快速失败(fail-fast) 安全失败(fail-safe) 对象排序 实体类自身具备排序能力 使用比较器排序 参考资料 List的几种实现的区别与联系 List主要有ArrayList、LinkedList与Vector几种实现。 ArrayList底层数据结构是数组, 增删慢、查询快; 线程不安全, 效率高; 不可以设置扩展容量, 默认增长1.5倍; 无参构造器初始化时, 初始容量为0。 LInkedList底层数据结构是链表, 增删快、查询慢; 线程不安全, 效率高。 Vector底层数据结构是数组, 增删慢、查询快; 线程安全, 效率低; 可以设置扩展容量, 默认增长2倍; 无参构造器初始化时, 初始容量为10。 List转换及删除元素 数组转List 集合 对于一个数组, 可以通过 Arrays.asList(T... a) 方法转换成List集合, 需要注意的是, 此方法得到的ArrayList对象是基于Arrays内部类 java.util.Arrays$ArrayList 来创建的, 而非 java.util.ArrayList 。这就涉及到了一个问题, 通过 asList(T... a) 转换得到的List集合是不允许进行增删操作的, 我们先看如下代码: 复制代码 @Test public void convertList() { List list = Arrays.asList("张小凡", "陆雪琪", "碧瑶"); //list.add("qingshanli"); list.remove(0); } 复制代码 运行时会报异常 java.lang.UnsupportedOperationException 我们先来看看 java.util.Arrays$ArrayList 的方法层次结构: 可以看出, java.util.Arrays$ArrayList 并没有覆写父类AbstractList的 add() 和 remove() 方法, 根据Java的三大特性之多态性可知, 上面代码中的增删操作实际调用的是父类AbstractList的方法, 我们再来看看AbstractList的部分源代码: 复制代码 public E set(int index, E element) { throw new UnsupportedOperationException(); } public void add(int index, E element) { throw new UnsupportedOperationException(); } public E remove(int index) { throw new UnsupportedOperationException(); } 复制代码 到此, 可以得知其实通过 Arrays.asList(T... a) 转换得到的List集合是一个固定长度的集合, 所以不能进行增删操作。 如何在遍历时删除ArrayList中元素 方式一: 普通循环 复制代码 public void test(List list) { for (int i = 0; i < list.size(); i++) { if (list.get(i) % 2 == 0) { list.remove(list.get(i)); i--; // 索引改变! } } } 复制代码 这种方式在删除操作时, 会改变集合的索引和size大小, 遍历时可能会产生角标越界异常, 因此不是特别推荐。 方式二: 高级for循环 复制代码 public static void main(String[] args) { List list = new ArrayList(); for (int i = 0; i < 5; i++){ list.add(i); } for (Integer num : list) { System.out.print("value="+num); if (num % 2 == 0) { list.remove(num); System.out.println(" delete"); }else{ System.out.println(" not delete"); } } } 复制代码 运行结果如下, 第一个元素删除正常, 后面继续遍历删除则抛异常 java.util.ConcurrentModificationException 如下, 反编译上述代码, 可以看出高级for循环底层其实就是使用iterator迭代器来进行遍历 复制代码 public static void main(String[] args) { ArrayList list = new ArrayList(); for (int i = 0; i < 5; ++i) { list.add(Integer.valueOf((int)i)); } Iterator i = list.iterator(); while (i.hasNext()) { Integer num = (Integer)i.next(); System.out.print((String)new StringBuilder().append((String)"value=").append((Object)num).toString()); if (num.intValue() % 2 == 0) { list.remove((Object)num); System.out.println((String)" delete"); continue; } System.out.println((String)" not delete"); } } 复制代码 再来看看iterator迭代器实现类的部分源代码: View Code 可以看出, 迭代器内部的每次遍历操作 next() 、 remove() 方法都会记录List集合内部的modCount当做预期值expectedModCount, 然后在每次循环中判断预期值expectedModCount与List的成员变量modCount是否相等。但是因为上面 list.remove() 调用的是List集合的 remove() 方法, 继续跟踪源代码发现每次调用该方法就会 modCount++; , 但是迭代器内记录的预期值expectedModCount并没有跟着改变, 所以当第二次删除操作时就会发生异常。 方式三: iterator迭代器遍历 复制代码 public void test(List list) { Iterator it = list.iterator(); while (it.hasNext()) { int num = it.next(); System.out.print("value="+num); if (num % 2 == 0) { it.remove(); System.out.println(" delete"); }else{ System.out.println(" not delete"); } } } 复制代码 原理与方式二基本类似, 但是这里使用的是迭代器iterator的 remove() 方法, 我们再回顾之前迭代器实现类的源代码, 发现 remove() 方法中有 expectedModCount = modCount; 这个操作, 即调用迭代器的 remove() 方法时会同步List集合的modCount到迭代器的预期值expectedModCount当中, 所以迭代器方式删除才不会产生。 SynchronizedList与Vector的区别 什么是快速失败, 安全失败机制 快速失败(fail-fast) fail-fast机制是java集合中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。 使用迭代器遍历一个集合对象时,如果遍历过程中对集合进行了增删改, 则会抛出 ConcurrentModificationException 。 复制代码 for (Integer id : list) { if (id == 2) { list.remove(id); } } 复制代码 在前面我们已经介绍过, 迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个modCount变量来作为预期值expectedmodCount。集合在被遍历期间如果内容发生变化,就会改变集合内部的modCount值。每当迭代器使用而而迭代遍历调用 next() 方法时每次都会检测 if(modCount==expectedmodCount) ,符合条件就返回遍历;否则将抛出异常终止遍历。 如果集合发生变化时修改modCount值, 并且又同步到expectedmodCount预期值, 比如前文中提到的iterator迭代器的 remove() 方法, 异常则不会抛出。因此, 不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。另外, 在java.util包下的集合类都是快速失败的, 是不能在多线程下发生并发修改的(即迭代过程中被修改)。 安全失败(fail-safe) 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是在开始遍历时先复制原有集合内容,在拷贝的集合上进行遍历, 即在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。java.util.concurrent包下的容器都是安全失败的, 可以在多线程下并发修改。 对象排序 实体类自身具备排序能力 Comparable接口用于使某个类具备可排序能力。实体类实现该接口后覆写其 compareTo() 方法,即可使实体类自身具备可排序的能力 。代码清单如下: 复制代码 public class Student implements Comparable { private String name; private int age; @Override public int compareTo(Student o) { int flag = this.name.compareTo(o.name); if(flag == 0) { flag = this.age - o.age; } return flag; } } 复制代码 实体类具备了排序能力后, 调用List集合的 sort(Comparator c) 或者Collections工具类的 sort(List list) 方法即可实现排序。 复制代码 List list = new ArrayList(); list.sort(null); //Collections.sort(list); 复制代码 使用比较器排序 Comparator是一个比较器接口,可以用来给不具备排序能力的对象进行排序。实现该比较器需覆写其 compare() 方法即可进行排序, 代码清单如下: 复制代码 public class Student { private String name; private int age; } public class StudentComparator implements Comparator { @Override public int compare(Student o1, Student o2) { int flag = o1.getName().compareTo(o2.getName()); if(flag == 0) { flag = o1.getAge() - o2.getAge(); } return flag; } } public class Test { public void sortTest() { List list = new ArrayList();   list.sort(new StudentComparator()); //Collections.sort(list, new StudentComparator()); } } 复制代码 参考资料 java中的增强for循环的实现原理与坑 Java中的fail-fast机制 每个人都是自己的上帝。如果你自己都放弃自己了,还有谁会救你?------《肖申克的救赎》https://www.cnblogs.com/qingshanli/p/9572038.html
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信