文章目录
- 编写高质量代码: 改善Java程序的151个建议
- 前言
- 第五章 数组和集合
- 建议60:性能考虑,数组是首选
- 建议61:若有必要,使用变长数组
- 建议62:警惕数组的浅拷贝
- 建议63:在明确的场景下,为集合指定初始容量
- 建议64:多种最值算法,适时选择
- 建议65:避开基本类型数组转换列表陷阱
- 建议66:asList方法产生的List对象不可更改
- 建议67:不同的列表选择不同的遍历方法
- 建议68:频繁插入和删除时使用LinkedList
- 建议69:列表相等只需关心元素数据
- 建议70:子列表只是原列表的一个视图
- 建议71:推荐使用subList处理局部列表
- 建议72:生成子列表后不要再操作原列表
- 建议73:使用Comparator进行排序
- 建议74:不推荐使用binarySearch对列表进行检索
- 建议75:集合中的元素必须做到compareTo和equals同步
- 建议76:集合运算时使用更优雅的方式
- 建议77:使用shuffle打乱列表
- 建议78:减少HashMap中元素的数量
- 建议79:集合中的哈希码不要重复
- 建议80:多线程使用Vector或HashTable
- 建议81:非稳定排序推荐使用List
- 建议82:由点及面,一页知秋--集合大家族
编写高质量代码: 改善Java程序的151个建议
前言
大家好, 我是 Rocky 编程日记, 喜欢后端架构及中间件源码, 最近在整理 编写高质量代码: 改善Java程序的151个建议 书籍, 目前已经归纳总结了第五篇。希望能够给你带来帮助, 该文章只是整理了浅浅的一些点, 后续也会对集合框架进行源码剖析, 感兴趣的小伙伴们可以关注收藏~, 创作不易, 谢谢支持。
第五章 数组和集合
建议60:性能考虑,数组是首选
-
性能要求较高的场景中使用数组替代集合)(基本类型在栈内存中操作,对象在堆内存中操作。数组中使用基本类型是效率最高的,使用集合类会伴随着自动装箱与自动拆箱动作,所以性能相对差一些。
public class Suggest60 { public static void main(String[] args) { // int[] arr = new int[]{1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200}; int[] arr = new int[]{ 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, 1, 34, 1000, 1230, 213, 12321, 167, 178, 90, 100, 199, 200, }; List<Integer> list = Arrays.stream(arr).boxed().collect(Collectors.toList()); System.out.println(sum(arr)); System.out.println(sum(list)); } public static int sum(int[] dates) { long startTime = System.nanoTime(); int sum = 0; for (int i = 0; i < dates.length; i++) { sum += dates[i]; } System.out.println("int[] cost time : " + (System.nanoTime() - startTime) + " ms"); return sum; } public static int sum(List<Integer> dates) { long startTime = System.nanoTime(); int sum = 0; for (int i = 0; i < dates.size(); i++) { // 拆箱操作,Integer对象通过拆箱转换成了一个int基本类型 sum += dates.get(i); } System.out.println("List<Integer> cost time: " + (System.nanoTime() - startTime) + " ms"); return sum; } }
cost time: 38500 779 cost time: 14500 779 int[] cost time : 36400 ms 393325 List<Integer> cost time: 112600 ms 393325
对于性能濒于临界的系统来说该方案是比较危险的,特别是大数量的时候,首先,在初始化List数组时要进行装箱动作,把一个int类型包装成一个Integer对象,虽然有整型池在,但不在整型池范围内的都会产生一个新的Integer对象,而且众所周知,基本类型是在栈内存中操作的,而对象则是在堆内存中操作的,栈内存的特点是速度快,容量小,堆内存的特点是速度慢,容量大(从性能上来讲,基本类型的处理占优势)。其次,在进行求和计算(或者其他遍历计算)时要做拆箱动作,因此无谓的性能消耗产生了。
建议61:若有必要,使用变长数组
-
使用Arrays.copyOf(datas,newLen)对原数组datas进行扩容处理
// 在实际开发中, 如果确实需要变成的数据集,数组也是在考虑范围之内的,不能因固定长度而将其否定 public static <T> T[] expandCapacity(T[] dates, int newLen) { newLen = newLen < 0 ? 0 : newLen; // 不能是负值 return Arrays.copyOf(dates, newLen); // 生成一个新数组, 并拷贝原值 }
建议62:警惕数组的浅拷贝
-
通过Arrays.copyOf(box1,box1.length)方法产生的数组是一个浅拷贝,这与序列化的浅拷贝完全相同:基本类型是直接拷贝值,其他都是拷贝引用地址。数组中的元素没有实现Serializable接口
public class Suggest62 { public static void main(String[] args) { int balloonNum = 7; Balloon[] box1 = new Balloon[balloonNum]; for (int i = 0; i < balloonNum; i++) { box1[i] = new Balloon(Color.values()[i], i); } Balloon[] box2 = Arrays.copyOf(box1, box1.length); box2[6].setColor(Color.Blue); for (Balloon balloon : box1) { System.out.println(balloon); } } } // 气球颜色 enum Color { Red, Orange, Yellow, Green, Indigo, Blue, Violet; } // 气球 class Balloon { // 编号 private int id; // 颜色 private Color color; public Balloon(Color _color, int _id) { color = _color; id = _id; } public String toString() { return "编号: " + id + "颜色: " + color; } public void setColor(Color _color) { color = _color; } }
建议63:在明确的场景下,为集合指定初始容量
-
ArrayList集合底层使用数组存储,如果没有初始为ArrayList指定数组大小,默认存储数组大小长度为10,添加的元素达到数组临界值后,使用Arrays.copyOf方法进行1.5倍扩容处理。
具体内容可参考 JDK 源码, 后续文章会讲该块内容。每次扩容拷贝会非常耗费资源, 而且效率非常低下。如果我们已经知道一个 ArrayList 的可能长度, 然后对 ArrayList 设置一个初始容量可以显著提高系统性能。
-
HashMap是按照倍数扩容的,
-
Stack继承自Vector,所采用扩容规则的也是翻倍
建议64:多种最值算法,适时选择
-
最值计算时使用集合最简单,使用数组性能最优,利用Set集合去重,使用TreeSet集合自动排序
public class Suggest64 { // 自行实现,快速查找最大值 public static int max(int[] data) { int max = data[0]; for (int i : data) { max = max > i ? max : i; } return max; } // 先排序, 后取值. 数组数据少于1w条,基本上没有差别。 public static int max1(int[] data) { Arrays.sort(data.clone()); return data[data.length - 1]; } }
建议65:避开基本类型数组转换列表陷阱
-
原始类型数组不能作为asList的输入参数,否则会引起程序逻辑混乱)(基本类型是不能泛化的,在java中数组是一个对象,它是可以泛化的。使用Arrays.asList(data)方法传入一个基本类型数组时,会将整个基本类型数组作为一个数组对象存入,所以存入的只会是一个对象。JVM不可能输出Array类型,因为Array是属于java.lang.reflect包的,它是通过反射访问数组元素的工具类。在Java中任何一个数组的类都是“[I”,因为Java并没有定义数组这个类,它是编译器编译的时候生成的,是一个特殊的类。
public class Suggest65 { public static void main(String[] args) { int[] data = {1, 2, 3, 4, 5}; List list = Arrays.asList(data); System.out.println("列表中的元素数量是: " + list.size()); System.out.println("元素类型: " + list.get(0).getClass()); System.out.println("前后是否相等: " + data.equals(list.get(0))); } }
列表中的元素数量是: 1 元素类型: class [I 前后是否相等: true
建议66:asList方法产生的List对象不可更改
-
使用add方法向asList方法生成的集合中添加元素时,会抛UnsupportedOperationException异常。
-
原因:asList生成的ArrayList集合并不是java.util.ArrayList集合,而是Arrays工具类的一个内置类,我们经常使用的List.add和List.remove方法它都没有实现,也就是说asList返回的是一个长度不可变的列表。此处的列表只是数组的一个外壳,不再保持列表动态变长的特性
public class Suggest66 { public static void main(String[] args) { Week[] workDays = {Week.Mon, Week.Tue, Week.Wed, Week.Thu, Week.Fri}; List<Week> list = Arrays.asList(workDays); list.add(Week.Sat); // Exception in thread "main" java.lang.UnsupportedOperationException } } enum Week { SuN, Mon, Tue, Wed, Thu, Fri, Sat }
建议67:不同的列表选择不同的遍历方法
-
ArrayList数组实现了RandomAccess接口(随机存取接口),ArrayList是一个可以随机存取的列表。
集合底层如果是基于数组实现的,实现了RandomAccess接口的集合,使用下标进行遍历访问性能会更高;底层使用双向链表实现的集合,使用foreach的迭代器遍历性能会更高
public class Suggest67 { public static void main(String[] args) { int stuName = 80 * 100000; List<Integer> scores = new ArrayList<>(stuName); for (int i = 0; i < stuName; i++) { scores.add(new Random().nextInt(150)); } long start = System.currentTimeMillis(); System.out.println("平均分是: " + average(scores)); System.out.println("执行时间是: " + (System.currentTimeMillis() - start) + " ms"); long start1 = System.currentTimeMillis(); System.out.println("平均分是: " + average1(scores)); System.out.println("执行时间是: " + (System.currentTimeMillis() - start1) + " ms"); List<Integer> scores1 = new LinkedList<Integer>(); for (int i = 0; i < stuName; i++) { scores1.add(new Random().nextInt(150)); } long start2 = System.currentTimeMillis(); System.out.println("平均分是: " + average2(scores1)); System.out.println("执行时间是: " + (System.currentTimeMillis() - start2) + " ms"); long start3 = System.currentTimeMillis(); System.out.println("平均分是: " + average3(scores1)); System.out.println("执行时间是: " + (System.currentTimeMillis() - start3) + " ms"); } private static int average(List<Integer> scores) { int sum = 0; for (Integer score : scores) { sum += score; } return sum / scores.size(); } private static int average1(List<Integer> scores) { int sum = 0; for (int i = 0; i < scores.size(); i++) { sum += scores.get(i); } return sum / scores.size(); } private static int average2(List<Integer> scores) { int sum = 0; for (Integer score : scores) { sum += score; } return sum / scores.size(); } private static int average3(List<Integer> scores) { int sum = 0; if (scores instanceof RandomAccess) { for (int i = 0; i < scores.size(); i++) { sum += scores.get(i); } } else { for (int score : scores) { sum += score; } } return sum / scores.size(); } }
平均分是: 74 执行时间是: 23 ms 平均分是: 74 执行时间是: 18 ms 平均分是: 74 执行时间是: 58 ms 平均分是: 74 执行时间是: 55 ms
建议68:频繁插入和删除时使用LinkedList
-
ArrayList集合,每次插入或者删除一个元素,其后的所有元素就会向后或者向前移动一位,性能很低。LinkedList集合插入时不需要移动其他元素,性能高;
修改元素,LinkedList集合比ArrayList集合要慢很多;
添加元素,LinkedList与ArrayList集合性能差不多,LinkedList添加一个ListNode,而ArrayList则在数组后面添加一个Entry
建议69:列表相等只需关心元素数据
-
判断集合是否相等时只须关注元素是否相等即可(ArrayList与Vector都是List,都实现了List接口,也都继承了AbstractList抽象类,其equals方法是在AbstractList中定义的。所以只要求两个集合类实现了List接口就成,不关心List的具体实现类,只要所有的元素相等,并且长度也相等就表明两个List是相等的,与具体的容量类型无关。
public class Suggest69 { public static void main(String[] args) { List<String> strs = new ArrayList<String>(); strs.add("A"); Vector<String> str2 = new Vector<>(); str2.add("A"); System.out.println(str2.equals(strs)); } }
public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof List)) return false; ListIterator<E> e1 = listIterator(); ListIterator<?> e2 = ((List<?>) o).listIterator(); while (e1.hasNext() && e2.hasNext()) { E o1 = e1.next(); Object o2 = e2.next(); if (!(o1==null ? o2==null : o1.equals(o2))) return false; } return !(e1.hasNext() || e2.hasNext()); }
建议70:子列表只是原列表的一个视图
-
使用==判断相等时,需要满足两个对象地址相等,而使用equals判断两个对象是否相等时,只需要关注表面值是否相等。subList方法是由AbstractList实现的,它会根据是不是可以随机存取来提供不同的SubList实现方式,RandomAccessSubList是SubList子类,SubList类中subList方法的实现原理:它返回的SubList类是AbstractList的子类,其所有的方法如get、set、add、remove等都是在原始列表上的操作,它自身并没有生成一个数组或是链表,也就是子列表只是原列表的一个视图,所有的修改动作都反映在了原列表上)。
public class Suggest70 { public static void main(String[] args) { List<String> c = new ArrayList<>(); c.add("A"); c.add("B"); List<String> c1 = new ArrayList<>(c); List<String> c2 = c.subList(0, c.size()); c1.add("C"); System.out.println("c==c1 ? " + c.equals(c1)); // c==c1 ? false System.out.println("c==c2 ? " + c.equals(c2)); // c==c2 ? true String str = "AB"; String str1 = new String(str); String str2 = str.substring(0) + "C"; System.out.println("str == str1 ? " + str.equals(str1)); // str == str1 ? true System.out.println("str == str2 ? " + str.equals(str2)); // str == str2 ? false } }
public List<E> subList(int fromIndex, int toIndex) { return (this instanceof RandomAccess ? new RandomAccessSubList<>(this, fromIndex, toIndex) : new SubList<>(this, fromIndex, toIndex)); }
建议71:推荐使用subList处理局部列表
-
需求:要删除一个ArrayList中的20-30范围内的元素;将原列表转换为一个可变列表,然后使用subList获取到原列表20到30范围内的一个视图(View),然后清空该视图内的元素,即可在原列表中删除20到30范围内的元素。
public class Suggest71 { public static void main(String[] args) { demo01(); demo02(); demo03(); } private static void demo01() { List<Integer> initData = Collections.nCopies(100, 0); List<Integer> list = new ArrayList<>(initData); for (int i = 0; i < list.size(); i++) { if (i >= 20 && i < 30) { list.remove(i); } } } private static void demo02() { List<Integer> initData = Collections.nCopies(100, 0); List<Integer> list = new ArrayList<>(initData); for (int i = 20; i < 30; i++) { if (i < list.size()) { list.remove(i); } } } private static void demo03() { List<Integer> initData = Collections.nCopies(100, 0); List<Integer> list = new ArrayList<>(initData); list.subList(20, 30).clear(); } }
建议72:生成子列表后不要再操作原列表
-
subList生成子列表后,使用Collections.unmodifiableList(list);保持原列表的只读状态)(利用subList生成子列表后,更改原列表,会造成子列表抛出java.util.ConcurrentModificationException异常。原因:subList取出的列表是原列表的一个视图,原数据集(代码中的list变量)修改了,但是subList取出的子列表不会重新生成一个新列表(这点与数据库视图是不相同的),后面再对子列表操作时,就会检测到修改计数器与预期的不相同,于是就抛出了并发修改异常。
public class Suggest72 { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("A"); list.add("B"); list.add("C"); List<String> subList = list.subList(0, 2); list.add("D"); System.out.println("原列表长度: " + list.size()); System.out.println("子列表长度: " + subList.size()); } }
记录: 对于子列表操作,因为视图是动态生成的,生成子列表后再操作原列表,必然会导致“视图”的不稳定,最有效的办法就是通过Collections. unmodifiableList方法设置列表为只读状态
建议73:使用Comparator进行排序
-
Comparable接口可以作为实现类的默认排序法,Comparator接口则是一个类的扩展排序工具)
两种数据排序实现方式:
- 实现Comparable接口,必须要实现compareTo方法,一般由类直接实现,表明自身是可比较的,有了比较才能进行排序;
- 实现Comparator接口,必须实现compare方法,Comparator接口是一个工具类接口:用作比较,它与原有类的逻辑没有关系,只是实现两个类的比较逻辑
建议74:不推荐使用binarySearch对列表进行检索
-
indexOf与binarySearch方法功能类似,只是使用了二分法搜索。使用二分查找的首要条件是必须要先排序,不然二分查找的值是不准确的。indexOf方法直接就是遍历搜寻。从性能方面考虑,binarySearch是最好的选择。
public class Suggest74 { public static void main(String[] args) { List<String> cities = new ArrayList<>(); cities.add("上海"); cities.add("广州"); cities.add("广州"); cities.add("北京"); cities.add("天津"); int index1 = cities.indexOf("广州"); int index2 = Collections.binarySearch(cities, "广州"); System.out.println("索引值(indexOf):" + index1); System.out.println("索引值(binarySearch):" + index2); } }
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) { // 随机存取列表或者元素数量少于5000的顺序存取列表 if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD) return Collections.indexedBinarySearch(list, key); else // 元素数量大于5000的顺序存取列表 return Collections.iteratorBinarySearch(list, key); }
建议75:集合中的元素必须做到compareTo和equals同步
- 实现了compareTo方法,就应该覆写equals方法,确保两者同步)(在集合中indexOf方法是通过equals方法的返回值判断的,而binarySearch查找的依据是compareTo方法的返回值;equals是判断元素是否相等,compareTo是判断元素在排序中的位置是否相同
建议76:集合运算时使用更优雅的方式
- 1、并集:list1.addAll(list2);
- 2、交集:list1.retainAll(list2);
- 3、差集:list1.removeAll(list2);
- 4、无重复的并集:list2.removeAll(list1);list1.addAll(list2);
建议77:使用shuffle打乱列表
-
使用Collections.shuffle(tagClouds)打乱列表
public class Suggest77 { public static void main(String[] args) { demo01(); demo02(); demo03(); } private static void demo01() { int tagCloudNum = 10; List<String> tagClouds = initList(); Random random = new Random(); for (int i = 0; i < tagClouds.size(); i++) { int randomPosition = random.nextInt(tagCloudNum); String temp = tagClouds.get(i); tagClouds.set(i, tagClouds.get(randomPosition)); tagClouds.set(randomPosition, temp); } System.out.println("====start====="); for (String tagCloud : tagClouds) { System.out.print(tagCloud + " "); } System.out.println("====end====="); } private static List<String> initList() { return Arrays.asList("1","2","3","4","5","6","7","8","9","10"); } private static void demo02() { int tagCloudNum = 10; List<String> tagClouds = initList(); Random random = new Random(); for (int i = 0; i < tagClouds.size(); i++) { int randomPosition = random.nextInt(tagCloudNum); Collections.swap(tagClouds, i, randomPosition); } System.out.println("====start====="); for (String tagCloud : tagClouds) { System.out.print(tagCloud + " "); } System.out.println("====end====="); } private static void demo03() { int tagCloudNum = 10; List<String> tagClouds = initList(); Random random = new Random(); Collections.shuffle(tagClouds); System.out.println("====start====="); for (String tagCloud : tagClouds) { System.out.print(tagCloud + " "); } System.out.println("====end====="); } }
建议78:减少HashMap中元素的数量
-
尽量让HashMap中的元素少量并简单
**现象:**使用HashMap存储数据时,还有空闲内存,却抛出了内存溢出异常;
**原因:**HashMap底层的数组变量名叫table,它是Entry类型的数组,保存的是一个一个的键值对。
与ArrayList集合相比,HashMap比ArrayList多了一次封装,把String类型的键值对转换成Entry对象后再放入数组,这就多了40万个对象,这是问题产生的第一个原因;HashMap在插入键值对时,会做长度校验,如果大于或等于阈值(threshold变量),则数组长度增大一倍。默认阈值是当前长度与加载因子的乘积,默认的加载因子(loadFactor变量)是0.75,也就是说只要HashMap的size大于数组长度的0.75倍时,就开始扩容。导致到最后,空闲的内存空间不足以增加一次扩容时就会抛出OutOfMemoryError异常。
// -Xms64m -Xmx64m public class Suggest78 { public static void main(String[] args) { Runtime runtime = Runtime.getRuntime(); // JVM 终止前记录内存信息 runtime.addShutdownHook(new Thread() { @Override public void run() { StringBuffer sb = new StringBuffer(); long headMaxSize = runtime.maxMemory() >> 20; sb.append("最大可用内存:" + headMaxSize + "M\n"); long total = runtime.totalMemory() >> 20; sb.append("对内存大小: " + total + "M\n"); long free = runtime.freeMemory() >> 20; sb.append("空闲内存:" + free + "M"); System.out.println(sb); } }); // demo01(); demo02(); } private static void demo01() { Map<String, String> map = new HashMap<>(); // 放入近 40 万 键值对 for (int i = 0; i < 393217; i++) { map.put("key" + i, "value" + i); } } private static void demo02() { List<String> list = new ArrayList<>(); // 放入近 40 万 键值对 for (int i = 0; i < 393217; i++) { list.add("key" + i); } } }
建议79:集合中的哈希码不要重复
- 列表查找不管是遍历查找、链表查找或者是二分查找都不够快。最快的是Hash开头的集合(如HashMap、HashSet等类)查找,原理:根据hashCode定位元素在数组中的位置。HashMap的table数组存储元素特点:
- table数组的长度永远是2的N次幂;
- table数组中的元素是Entry类型;
- table数组中的元素位置是不连续的;每个Entry都有一个next变量,它会指向下一个键值对,用来链表的方式来处理Hash冲突的问题。如果Hash码相同,则添加的元素都使用链表处理,在查找的时候这部分的性能与ArrayList性能差不多
建议80:多线程使用Vector或HashTable
- Vector与ArrayList原理类似,只是是线程安全的,HashTable是HashMap的多线程版本。线程安全:基本所有的集合类都有一个叫快速失败(Fail-Fast)的校验机制,当一个集合在被多个线程修改并访问时,就可能出现ConcurrentModificationException异常,这是为了确保集合方法一致而设置的保护措施;实现原理是modCount修改计数器:如果在读列表时,modCount发生变化(也就是有其他线程修改)则会抛出ConcurrentModificationException异常。**线程同步:**是为了保护集合中的数据不被脏读、脏写而设置的
建议81:非稳定排序推荐使用List
- 非稳定的意思是:经常需要改动;TreeSet集合中元素不可重复,且默认按照升序排序,是根据Comparable接口的compareTo方法的返回值确定排序位置的。SortedSet接口(TreeSet实现了该接口)只是定义了在该集合加入元素时将其进行排序,并不能保证元素修改后的排序结果。因此TreeSet适用于不变量的集合数据排序,但不适合可变量的排序。对于可变量的集合,需要自己手动进行再排序)(SortedSet中的元素被修改后可能会影响其排序位置
建议82:由点及面,一页知秋–集合大家族
-
List:实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack,其中ArrayList是一个动态数组,LinkedList是一个双向链表,Vector是一个线程安全的动态数组,Stack是一个对象栈,遵循先进后出的原则;
-
Set:Set是不包含重复元素的集合,其主要的实现类有:EnumSet、HashSet、TreeSet,其中EnumSet是枚举类型的专用Set,HashSet是以哈希码决定其元素位置的Set,原理与HashMap相似,提供快速插入与查找方法,TreeSet是一个自动排序的Set,它实现了SortedSet接口;
-
Map:可以分为排序Map和非排序Map;排序Map为TreeMap,根据Key值进行自动排序;非排序Map主要包括:HashMap、HashTable、Properties、EnumMap等,其中Properties是HashTable的子类,EnumMap则要求其Key必须是某一个枚举类型;
-
Queue:分为两类,一类是阻塞式队列,队列满了以后再插入元素会抛异常,主要包括:ArrayBlockingQueue、PriorityBlockingQueue、LinkedBlockingQueue,其中ArrayBlockingQueue是以数组方式实现的有界阻塞队列;PriorityBlockingQueue是依照优先级组件的队列;LinkedBlockingQueue是通过链表实现的阻塞队列;另一类是非阻塞队列,无边界的,只要内存允许,都可以追加元素,经常使用的是PriorityQueue类。还有一种是双端队列,支持在头、尾两端插入和移除元素,主要实现类是:ArrayDeque、LinkedBlockingDeque、LinkedList;
-
数组:数组能存储基本类型,而集合不行;所有的集合底层存储的都是数组;
-
工具类:数组的工具类是:java.util.Arrays和java.lang.reflect.array;集合的工具类是java.util.Collections;
-
扩展类:可以使用Apache的commons-collections扩展包,也可以使用Google的google-collections扩展包