读书笔记--编写高质量代码:改善java程序的151个建议(五)数组和集合

读书笔记--编写高质量代码:改善java程序的151个建议(五)数组和集合

考虑性能的话,首选数组

数组在操作基本类型的时候的性能比用list要高很多倍。如果有性能瓶颈,也可以自己实现变长数组来提高性能,比集合类要快很多。

还有要警惕数组的浅拷贝,数组在操作对象的时候,数组里保存的是对象的内存地址,所以拷贝的时候也是拷贝的内存地址,要深拷贝需要自己实现。

条件允许的情况下,可以为集合类指定初始容量来提高性能

集合类指定初始容量后,就减少了集合类自增带来的性能消耗。 arraylist的默认容量是10 ,当加入第11个元素的时候就就会自动增长1.5倍 10*1.5 vector和hashMap都是默认增长2倍。默认2倍增长的集合类要小心使用,特别到后期,如果存了10w数据了,这时候在查询10w零1个数据之后集合类会重新申请2倍的空间 ,然后把数据复制过来,大家想想如果是100w数据那?

可变参数的陷阱

基本类型不能被泛型,基本类型的数组在当作可变参数泛型参数的时候,只会是数组本身作为一个参数。

例如List.asList(T...a)方法,如果传递基本类型的数组,那么生成的list只有一个元素,如果是对象类型的数组,list的元素个数就和数组一直。

asList返回的的列表是不可改变的。

不同的list要用不同的遍历方法来提高性能

大家都知道java5新增了foreach循环,但是大家可能不知道foreach和普通的for(i=1;i<100;i++)的性能上的区别。

foreach是统一才用iterator接口实现循环遍历,底层模型也就类似一个linked链表。 如果一个arrayList用foreach循环那么性能会很低,如果直接用索引的方法性能会很高。 如果一个LinkedList用foreach循环那么性能会很高,如果用索引的方法性能就会很低。

其实判断一个list要用哪种方式循环主要看list有没有实现randomAccess接口,这个接口和Cloneable,Serializable等接口一样,是一种标志性接口,RandomAccess接口标志该类实现了随机访问,该类中的元素之间没有相互依赖和索引关西,可以随机访问和存储。大家在工作中使用的时候可以if(list instanceof RandomAccess)判断一下,是的话就用索引的方法 ,不是就直接foreach。

linkedList和ArrayList

这两个集合类的特点相信大家都耳熟能详了,linkedList是双向链表,修改删除性能高,但是查询特别慢,arraylist是数组,每次修改删除都要把之后的元素的索引重置,所以性能不高,但是查询的性能很高,直接索引就可以。

列表的相等只关心元素数据

列表在比较的时候,比较的是实际的元素数据,和元素个数,和列表的本身类型无关。

subList方法

用subList产生的自列表只是原列表的是一个视图,操作子列表会影响原列表。一旦生成子列表就不能在对原列表有修改或删除操作,不然会引起子列表和原列表保存的修改版本不一致引发并发修改异常。

subList的一个妙用是当作removeRange来使用,比如要删除一个列表中20~30的列表元素,可以这么用:

list.subList(20,30).clear();

通过Comparable接口和Comparator接口让列表支持排序。

只要让javabean实现Comparable接口,实现compareTo方法,javabean就支持排序了。

Collections.sort(list);

实现Comparable接口只能让类支持一种排序方法,如何支持多种排序那?那就要用到Comparator接口了。

实现一个Comparator接口得到一个指定泛型的排序类SortA,就可以这么用了:

Collections.sort(list,new SortA());

这就可以了。

实现了compareTo方法,就要实现equals方法,确保两者同步。

在列表操作中,一旦实现了compareTo方法来支持排序,那么就要也实现equals方法。因为列表中的有些操作是依赖于compareTo比如binarySearch(),有些是依赖于equals,比如indexOf(),如果两者逻辑不统一,会造成混乱。

集合运算常用方法

  • addAll:将一个list全部添加到另一个list中
  • retainAll:计算两个集合的共有元素
  • removeAll:就listA于另外的listB的差集
  • 可以利用removeAll和addAll方法求无重复的并集
  • Collections.shuffle()方法可以随机打乱一个集合的顺序

不要让hashCode重复

map集合类的实现方式是通过计算entry(map内部来保存key-value的对象)的hashcode来得出map内部保存的数据的下标来保存数据,如果hashcode相同,数据就会保存到同一个数组下标下,已链表的形式保存,这么一来会极大的影响性能。

区分线程安全和同步修改异常

线程安全是指多个线程同时访问一个方法,如果该方法有synchronized关键字,那么多个线程会依次的访问该方法,而不是并发方法。集合类的的线程安全版本(比如:vector是arrayList的线程安全版本,hashTable是hashMap的线程安全版本)的方法都有synchronized关键字,也就是都是线程安全方法,但是如果是多个线程同时访问一个集合类的不同方法,那么这些操作不是线程安全的,但是会引发同步修改错误,因为集合类的多个方法修改的是相同的数据。

treeSet和list排序比较

treeset只是在每次新增元素的时候自动排序,在之后修改的操作并不触发排序,有时候可能不满足业务需求。而list不支持排序功能 但可以让list的泛型实现compareable接口,然后用collections.sort()方法来排序。

集合大家族总结

1.List

实现list接口的集合主要有ArrayList,LinkedList,Vector,Stack,其中ArrayList是一个动态数组,LinkedList是一个双向链表,Vector是一个线程安全的动态数组,Stack是一个对象栈,遵循先进后出的原则

2.Set

Set是不包含重复元素的集合,其猪哟呵的实现类有:EnumSet,HashSet,TreeSet,其中EnumSet是枚举类型的专用Set,所有元素的枚举类型;HashSet是以哈希码觉醒其元素位置的set,其原理和HashMap相似,它提供快速的插入和查找方法;TreeSet是一个自动排序的Set,它实现了SortedSet接口。

3.Map

Map是一个大家族,它可以分为排序Map和非排序map,排序map主要是TreeMap类,他根据key值进行自动排序;非排序map主要包括:HashMap,HashTable,Properties,EnumMap等,其中properties是HashTable的子类,他的主要用途是从property文件中读取数据,并提供方便的读写操作;EnumMap则是要求其key必须是一个枚举类型。 这里有一篇文章介绍hashmap实现原理,很不错,大家可以看看

map中还有一个wekHashMap类需要说明,它是一个才用弱键方法实现的Map类,它的特点就是:WeakhashMap对象的存在并不会阻止垃圾回收器对键值对的回收,也就是说用WeakHashMap装载数据不用担心内存溢出的问题,GC会自动删除不用的键值对,这是好事,但也存在一个严重的问题:GC是静悄悄回收的,我们程序无法知晓该动作,存在着重大隐患。

4.Queue

队列,它分为两类,一类是阻塞式队列,队列满了以后在插入元素则会抛出异常,主要包括:ArrayBlockingQueue,PriorityBlockingQueue,LinkedBlockingQueue,其中ArrayBlockingQueue是一个以数组方法实现的有界阻塞队列,PriorityBlockingQueue是依照优先级组建的队列,LinkedBlockingQueue是通过链表实现的阻塞队列;另一类是非阻塞队列,无边界的,只要内存允许,都可以持续追加元素,我们最经常使用的就是PriorityQueue类。

还有一种队列,是双端队列,支持在头和尾两端插入和移除元素,它的主要实现类是:ArrayDeque,LinkedBlockingDeque,LinkedList。

5.数组

数组和集合的最大区别就是数组能够容纳基本类型,而集合就不行,更重要的一点就是所有的集合类底层存储的都是数组。

6.工具类

数组的工具类是java.util.Arrays和java.lang.reflect.Array,集合的工具类是java.util.Collections,有了这两个工具类,操作数组和集合会易如反掌,得心应手。

7.扩展类

集合类当然可以自行扩展了,想写出一个自己的list?没问题,但最好的办法还是“拿来主义”,可以使用Apache的commons-collections工具包,也可以使用Google的google-collections扩展包,这些足以应对我们开发的需要。

猜你喜欢

转载自blog.csdn.net/qq413041153/article/details/36381525