一直以来学习没有写笔记的习惯,也没有写博客,现在发现回过头很多知识不好复习巩固,以后用CSDN平台进行记录每天的学习~
先敬上JAVA集合框架图
上图摘自https://www.cnblogs.com/liuzyw/p/5495459.html
看起来眼花缭乱,让初学者很不想看下去,接下来我用人类习惯的树形结构来做最简单的解析
1.JAVA集合框架最大两个主干接口:Collection和Map
2.Collection里的主力军List和Set
即:JAVA集合框架简单理解为有List Set Map三大类
1.List是一个有序集合,里面的元素可以重复,可以通过索引进行访问(可以迭代器访问+整数索引访问)
2.Set是一个无序集合,元素不可以重复,不能通过索引进行访问(只能用迭代器访问)
3.Map是用key-value键值对的方式进行存储的集合,key不能重复,value不限制,可以通过key进行访问(只能通过key索引访问)
(迭代器就是第一个图左上角的Iterator,用来遍历,甚至修改集合,通过集合的iterator()方法进行返回它本身的迭代器对象)
我们今天来研究的是List集合和它的两个主要实现类ArrayList和LinkedList
他们有专属的一个迭代器ListIterator作用一会儿再讲
ArrayList:内部封装了一个动态数组,为什么说动态呢?因为它会随着容量的增大当要溢出时创建新的大的数组进行扩容,ArrayList的默认初始容量是10,我们也可以在创建时指定容量,所以我们在知道集合大致大小的时候最好指定大小,避免进行扩容降低效率.
ArrayList的优点是随机访问效率高,增删效率低,所以当我们的使用场景为查询多修改少的时候应该使用ArrayList.
LinkedList是我们所学的数据结构的双向链表的实现,我们知道链表的特点是增删非常容易,只用改变前驱索引和后继索引即可,而不必像ArrayList一样增删要导致其他的元素统统挪位置,但是它的查询非常麻烦,只能从头跨过n-1个元素来查询第n个元素,并且没有任何缓存位置信息的操作.(注:JDK内对LinkedList的get操作进行了优化,如果索引大于size()/2,则从尾端进行查找元素,尽量如此,效率仍然比较低)
LinkedList的优点是增删效率很高,访问效率较低,所以我们在频繁增删,查询较少的场景使用LinkedList
除了ArrayList和LinkedList之外,List接口下的结合还有Vector和Stack,简要说一下吧
Vector的英文是矢量,挺不容易理解为什么要找它作为名字的,Vector是与ArrayList相似的,不同就是Vector是同步的,同步操作将会耗费时间,所以在不需要同步时应该使用ArrayList而不要使用Vector
Stack就是一个先进后出,或者说是后进先出的堆栈,有push和pop方法,还有peek方法得到栈顶的元素,Stack刚创建时空的.
为了研究ArrayList和LinkedList在访问时和增删时的差距,我进行了一些测试
做测试前需要有进行测量的工具,就是Ststem类的currentTimeMillis()方法和nanoTime()方法,使用currentTimeMillis()测量出的单位是毫秒,nanoTime()测量出的单位是纳秒,1秒=1000毫秒=1000000000(10^9)(10亿)纳秒,1毫秒=1000000(10^6)纳秒.
首先声明了一个ArrayList和一个LinkedList并各自插入10个数据操作
结果是:,进行10W次插入操作仅用了4毫秒,可见JAVA及现在的计算器设备的强悍(题外话)
为了有区分度(两个虽然都是4,但完全不意味两者时间相等,这与计算机系统的时间粒度有关,测量时间也太短),我们进行100W次插入并用纳秒进行测量
看来ArrayList虽然在插入过程中会进行扩容操作,但其实仍然比LinkedList插入效率高,这里的ArrayList在声明时没有指定容量,我在指定容量(100W)后的消耗时间大约减少了2/5;
下面来实测一下ArrayList和LinkedList在访问特定元素的速度差异:
主要部分为:
测试结果为:
看来两者在元素访问还真是天上地下的差距与我们的理论分析一致.
下面用同样的方法进行插入操作,与第一个测试不同的是add操作会默认在尾部进行插入,这使得ArrayList的缺点(插入时移动后面元素)被掩埋,我们现在都在第二个元素,即1索引位置进行插入操作进行测试:
主要操作为:
测试结果为:
两者又在实力上甩开了天上地下的差距,真是难为了ArrayList每次插入后面的兄弟都要挪一挪,可见深厚的功底才是软件工程师必备的技能,设计上的错误会导致软件质量的质的变化
最后再次总结一下ArrayList和LinkedList的使用场景:
多访问,少增删的场景用ArrayList
少访问,多增删的场景用LinkedList
那么还有一个问题不知道你有没有注意到呢?ArrayList是非同步的,有Vector来保证同步,那么LinkedList有没有相应的同步集合呢?
答案是没有像ArrayList和Vector这样的哥们集合存在,但是可以用Collections类的synchronizedList()方法来获取同步集合,说到这里,Collections类又是什么呢?
Collections是一个工具类(不要和Collection接口搞混淆了哦),它是Collection的助攻,用于对集合元素的排序,搜索和线程安全等各种操作,是一个静态工具类,不能创建对象.
说道这里,前面的Iterator和ListIterator不知道你还记得吗?
从第一个图中我们可以看到Collection这个大佬扩展了Iterator接口,那么话说Iterator一定很厉害吧?
其实不然,Iterator只声明了三个方法:
boolean hasNext();
Object next();
void remove();
hasNext判断是否还有下一个元素,next为返回下一个元素,remove是删除?个元素
这里要注意一下,remove是动态的,remove()方法删除的是刚才访问过的那个元素,即如果刚才用的next()就会把刚才访问到的那个元素删除掉,如果刚才用的previous(咦,哪里来的previous?一会再讲),则把刚才访问过的(右边的)删除掉,为了更生动一点,我来写一下:
假设现在Iterator的位置用|表示
现在是A|BC
如果用next(),则位置为:AB|C
那么现在remove(),则集合变为A|C
如果刚才用previous()则位置为:|ABC
remove()的话集合变为|BC
这下清晰明了了~
刚才上文说到的previous在哪里呢?在Iterator的子类ListIterator,前文已经说过是为List集合专用的,它里面有previous(),
add()(不同于集合的add,这是在迭代器的位置前插入),set(设置刚才访问过的那个元素的值)等等List独有用到的方法
说到set和add,这里又要提到一个异常ConcurrentModificationException
我们来看一段代码:
List<String> list=...(省略);
ListIterator<String> iter1=list.listIterator();
ListIterator<String> iter2=list.listIterator();
iter1.next();
iter1.remove();
iter2.next();//throws ConcurrentModificationException
这里是由于iter2检测出这个链表从另一个迭代器修改了,或者被该集合自身的方法修改了,所以报出的异常,那么为什么要报异常呢?
这里还有一个要注意的点,remove()方法不能使用两次,这里再写一下:
AB|C使用了remove后被访问的B删除了,这时是没有被访问的元素的(只能这么说了),所以再删除就会出错,所以JAVA为了安全起见多个迭代器一旦一个进行修改其他使用的时候就会报错,为了解决这个问题在《JAVA核心技术卷1》第10版第358页有:
可以根据需要给容器附加许多的迭代器,但是这些迭代器只能读取列表,另外,再单独附加一个既能读又能写的迭代器.
但是不理解如何操作,希望有读懂的读者给个提示~
由于时间原因今天就先写到这里了~明天继续研究Set和Map集合,突然发现自己也挺唠叨的,扯这么多~睡觉~
对了本文的参考主要是《JAVA核心技术卷1》集合部分和一个大佬的博客https://www.cnblogs.com/xiaoxi/p/6089984.html
大佬的博客写的很详细很全面,可以移步学习~
本文纯手打,转载注明出处~