编写高质量代码: 改善Java程序的151个建议 | 第五章 数组和集合


在这里插入图片描述

编写高质量代码: 改善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接口则是一个类的扩展排序工具)

    两种数据排序实现方式:

    1. 实现Comparable接口,必须要实现compareTo方法,一般由类直接实现,表明自身是可比较的,有了比较才能进行排序;
    2. 实现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数组存储元素特点:
    1. table数组的长度永远是2的N次幂;
    2. table数组中的元素是Entry类型;
    3. 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扩展包

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值