集合一些易忽视的地方
1.请讲下Java里面的容器
集合Collection有两种:
List:元素是有序的,元素可以重复,因为该集合体有索引
ArrayList:
底层数据结构是数组,查询快,增删慢。
线程不安全,效率高。
当元素放满了后,以原长度的50%+1的长度加长集合容器的长度。
Vector:
底层数据结构是数组,查询快,增删慢。
线程安全,效率低。
当元素放满了后,以原长度100%的长度加长集合容器的长度。
LinkedList:
底层数据结构是链表,查询慢,增删快。
线程不安全,效率高。
Vector(线程安全的)相对ArrayList查询慢
Vector相对LinkedList增删慢(数组结构)
Set:元素是无序的,元素不可以重复。
a)、HashSet:不能保证元素的排列顺序,线程不同步。
b)、TreeSet:可以set集合中的元素进行排序,线程不同步。
集合Map:
Map:存储键值对
a)、HashMap:底层是哈希表数据结构,可以存入null作为键或值,线程不同步
b)、HashTable:底层是哈希表数据结构,不可以存入null作为键或值,线程同步。
c)、TreeMap:底层是二叉树结构,线程不同步。
2、fail-fast与fail-safe有什么区别?
Iterator的fail-fast属性与当前的集合共同起作用,因此它不会受到集合中任何改动的影响。Java.util包中的所有集合类都被设计为fail-fast的,而java.util.concurrent中的集合类都为fail-safe的。Fail-fast迭代器抛出ConcurrentModificationException,而fail-safe迭代器从不抛出ConcurrentModificationException。
2.遍历一个List<String> strList = new ArrayList<>();有哪些不同的方式?哪种方式更安全?
//使用for-each循环 for(String obj : strList){ System.out.println(obj); } //using iterator Iterator<String> it = strList.iterator(); while(it.hasNext()){ String obj = it.next(); System.out.println(obj); }
使用迭代器更加线程安全,因为Iterator的fail-fast属性与当前的集合共同起作用,因此它不会受到集合中任何改动的影响。它可以确保,在当前遍历的集合元素被更改的时候,它会抛出ConcurrentModificationException。在遍历一个集合的时候,我们可以使用并发集合类来避免ConcurrentModificationException,比如使用CopyOnWriteArrayList,而不是ArrayList。
3.下面的这段代码有错吗?说说看?
List<String> list = new ArrayList<String>(2); list.add("guan"); list.add("bao"); String[] array = new String[list.size()]; array = list.toArray();
解:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。
使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。
说明:使用 toArray 带参方法,入参分配的数组空间不够大时,toArray 方法内部将重新分配内存空间,并返回新数组地址;如果数组元素大于实际所需,下标为[ list.size() ]的数组元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。
4、看下面这段代码:
String[] str = new String[] { "you", "wo" }; List list = Arrays.asList(str);
1)如果在上述代码后添加如下代码会如何?
list.add("yangguanbao");
2)如果添加如下代码:输出结果是?
str[0] = "mmzs";
System.out.println(list.get(0));
解:使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。
str[0] 改变,list.get(0)会随之修改。
5、Iterator是什么?
Iterator接口提供遍历任何Collection的接口。我们可以从一个Collection中使用迭代器方法来获取迭代器实例。迭代器取代了Java集合框架中的Enumeration。迭代器允许调用者在迭代过程中移除元素。
6、Iterater和ListIterator之间有什么区别?
(1)我们可以使用Iterator来遍历Set和List集合,而ListIterator只能遍历List。
(2)Iterator只可以向前遍历,而LIstIterator可以双向遍历。
(3)ListIterator从Iterator接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。
7、下面是一个集合的遍历的片段:
1 List<String> list = new ArrayList<String>(); 2 3 list.add("1"); 4 5 list.add("2"); 6 7 for (String item : list) { 8 9 if ("1".equals(item)) { 10 11 list.remove(item); 12 13 } 14 15 }
1)请问上述操作如何?
2)若把list.remove(item)换成list.add(“3”);操作如何?
3)若在第6行添加list.add("3");那么代码会出错吗?
4)若把if语句中的“1”换成“2”,结果你感到意外吗?
运行结果:1)没错,后面3个都会报ConcurrentModificationException的异常;
不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
二者本质是一样的,都是通过Iterator迭代器来实现的遍历,foreach是增强版的for循环,可以看作是方式二的简化形式
正例:Java学习交流QQ群:603654340 我们一起学Java!
1 Iterator<String> iterator = list.iterator(); 2 3 while (iterator.hasNext()) { //方式二 4 5 String item = iterator.next(); 6 7 if (删除元素的条件) { 8 9 iterator.remove(); 10 11 } 12 13 }
首先,这涉及多线程操作,Iterator是不支持多线程操作的,List类会在内部维护一个modCount的变量,用来记录修改次数
举例:ArrayList源码
protected transient int modCount = 0;
每生成一个Iterator,Iterator就会记录该modCount,每次调用next()方法就会将该记录与外部类List的modCount进行对比,发现不相等就会抛出多线程编辑异常。
为什么这么做呢?我的理解是你创建了一个迭代器,该迭代器和要遍历的集合的内容是紧耦合的,意思就是这个迭代器对应的集合内容就是当前的内容,我肯定不会希望在我冒泡排序的时候,还有线程在向我的集合里插入数据对吧?所以Java用了这种简单的处理机制来禁止遍历时修改集合。
至于为什么删除“1”就可以呢,原因在于foreach和迭代器的hasNext()方法,foreach这个语法,实际上就是
while(itr.hasNext()){ itr.next() }
所以每次循环都会先执行hasNext(),那么看看ArrayList的hasNext()是怎么写的:
public boolean hasNext() { return cursor != size; }
cursor是用于标记迭代器位置的变量,该变量由0开始,每次调用next执行+1操作,于是:
你的代码在执行删除“1”后,size=1,cursor=1,此时hasNext()返回false,结束循环,因此你的迭代器并没有调用next查找第二个元素,也就无从检测modCount了,因此也不会出现多线程修改异常;但当你删除“2”时,迭代器调用了两次next,此时size=1,cursor=2,hasNext()返回true,于是迭代器傻乎乎的就又去调用了一次next(),因此也引发了modCount不相等,抛出多线程修改的异常。
当你的集合有三个元素的时候,你就会神奇的发现,删除“1”是会抛出异常的,但删除“2”就没有问题了,究其原因,和上面的程序执行顺序是一致的。