您的位置:首页 > 编程语言 > Java开发

java数组和集合

2013-08-30 16:42 169 查看
1.性能考虑,优先选择数组 

      数组在项目开发当中使用的频率是越来越少,特别是在业务为主的开发当中,首先数组没有List,Set等集合提供的诸多方法,查找增加算法都要自己编写,极其繁琐麻烦,但由于List,Set等集合使用泛型支持后,存放的都为包装类,而数组是可以使用基本数据类型,而使用基本数据类型的执行运算速度要比包装类型快得多,而且集合类的底层也是通过数组进行实现. 

2.若有必要,使用变长数组 

      在学习集合类当中,很多人喜欢将数组的定长拿来和集合类型的自变长来做比较,但其实这种比较并不合适,通过观察集合类例如ArrayList的实现其实可以看出,所谓的集合变长,其实只是用婉转的方式对原数组进行了扩容 

Java代码  


public static <T> T[] expandCapacity(T[] data, int newLength) {  

        // 判断是否为负值  

        newLength = newLength < 0 ? 0 : newLength;  

  

        // 生成新数组,拷贝原值并制定长度  

        return Arrays.copyOf(data, newLength);  

    }  

      当性能要求高的时候,可以考虑使用对数组进行封装使用,数组长度不变不是我们不使用它们的借口 

3.警惕数组的浅拷贝 

      数组的浅拷贝在Java编程中亦是基础中的基础,浅拷贝是在为数组拷贝时,基本类型拷贝的是值,而引用类型拷贝的是引用地址,在上面的例子当中,拷贝数组使用的Arrays.copyOf为浅拷贝,在使用时需要注意 

4.在明确的场景下,为集合指定初始容量 

      在我们平常的使用当中,因为集合类型是自动变长的,所以基本创建对象时不会为集合类附上初始值,就拿我们最常用的ArrayList来说明,我们首先要知道,当集合容量到达临界点时,会将底层的数组进行copyOf的操作,生成新的数组,而新的数组容量为旧数组的1.5倍,而默认数组长度为10,当我们明确知道要放置入容器中的数据数量较多时,应该指明初始值,避免多次使用copyOf造成的性能开销 

ArrayList是经常会被用到的,一般情况下,使用的时候会像这样进行声明:

List arrayList = new ArrayList();

如果像上面这样使用默认的构造方法,初始容量被设置为10。当ArrayList中的元素超过10个以后,会重新分配内存空间,使数组的大小增长到16。

可以通过调试看到动态增长的数量变化:10->16->25->38->58->88->...

 

也可以使用下面的方式进行声明:

List arrayList = new ArrayList(4);

将ArrayList的默认容量设置为4。当ArrayList中的元素超过4个以后,会重新分配内存空间,使数组的大小增长到7。

可以通过调试看到动态增长的数量变化:4->7->11->17->26->...
5.选择合适的最值算法 

      对数据进行最大值或最小值的查找,这是数据结构最基本的知识,在Java当中我们亦有很多种的方式进行实现,以下列举2种算法 

Java代码  


public static int getMaxByArray(int[] data) {  

        // 最简单自行实现的查找方式  

        int max = data[0];  

        for (int i = 1, size = data.length; i < size; i++) {  

            max = max > data[i] ? max : data[i];  

        }  

        return max;  

    }  

Java代码  


public static int getMaxByArray(int[] data) {  

        // 先排序后获取最后位  

        Arrays.sort(data);  

        return data[data.length - 1];  

    }  

6.基本类型数组转换陷阱! 

      请观察以下代码 

Java代码  


public static void main(String[] args) {  

        int[] nums = new int[] { 1, 2, 3, 4, 5 };  

        List list = Arrays.asList(nums);  

        System.out.println(list.size());  

        // 此时输出的size为1  

    }  

      我们期望的结果是将数组中的元素通过Arrays.asList转换到集合类当中,但事与愿违,我们只将数组本身增加了进入,并没有将数组内的值分拆分开来,此时若然对集合List增加了泛型就会在编译期间给出错误的提示,或将数组本身改变成Integer就可以解决问题      
7.asList方法产生的List对象不可更改 
      通过上面的例子,我们可以看到使用Arrays.asList方法可以将一个数组转换成一个List,那通过asList方法返回的List有什么特别呢?注意,这个返回的List是不支持更改的,原因是因为asList方法返回的,并不是java.util.ArrayList,而是Arrays工具类中的一个静态私有内部类,虽然都有实现和ArrayList一样的父类AbstractList,但在复写add等方法时,却是抛出了UnsupportedOperationException, 
这个静态私有内部类只实现了size,toArray,get,contains这几个方法 
8.对不同的数据结构使用不同的遍历方式 

      请观看以下代码 

Java代码  


public static void main(String[] args) {  

        // 以下为ArrayList集合的遍历方式  

        int num = 80 * 10000;  

        List<Integer> arrayList = new ArrayList<Integer>(num);  

        for (int i = 0, size = arrayList.size(); i < size; i++) {  
            arrayList.get(i);  

        }  

  

        // 以下为LinkedList集合的遍历方式  

        List<Integer> linkedList = new LinkedList<Integer>();  

        for (Integer integer : linkedList) {  

  

        }  

    }  

      为什么对LinkedList和ArrayList要选择不同的遍历方式? 

      1.因为ArrayList实现了RamdomAccess接口(随机存取接口),RamdomAccess 

        接口和Serializable,Cloneable接口一样是Java中的标示接口,代表这个 

        这个类可以随机存取,对ArrayList来说就标志着,数据之间没有关联, 

        即相邻的两个位置没有互相依赖的关系,可以随机访问, 

      2.Java中的foreach语法是iterator(迭代器)的变形用法,我们知道迭代器 

        是23种设计模式的一种,但迭代器是需要知道两个元素时间的关系的,不然 

        怎么提供hasNext的支持呢?就是因为上一个元素要判断下一个元素是否 

        存在,强行建立了这种关系,违背了ArrayList随机存取的特别 

      3.在LinkedList中,因为是通过双向链表的形式来存储,所以对迭代器的 

        支持非常好,因为LinkedList相邻的两个元素本来就存在关系 

      所以在对LinkedList和ArrayList要采取不同的遍历方式,读者若然有兴趣 

      可以尝试一下对LinkedList采用下标的形式访问,会发现两者的效率有较大 

      的差距 

8.适时选择ArrayList或LinkedList 
      ArrayList和LinkedList的主要区别: 

      1.ArrayList底层的数据结构为数组,而LinkedList底层结构为双向链表 

      2.在插入数据时,由于ArrayList每次插入后都需要将数组元素向后顺延 
        位置,而LinkedList只需要更改头节点和尾节点即可完成插入操作,所以 
        在插入操作较为频繁时,优先使用LinkedList 

      3.在删除数据时,由于ArrayList要保持数组的有序性,当删除后元素要亦 
        需要向后或向前移位,而LinkedList照旧还是更改头尾节点. 

      4.在更新时,由于LinkedList会使用折半遍历的方式进行查找定位元素再 
        进行更新,对比起ArrayList的直接定位下标元素替换,ArrayList对更新 
        的效率更佳 

      5.LinkedList可以模拟队列,通过LinkedList的addFirst,addLast等操作 
8.适时选择ArrayList或LinkedList 

      ArrayList和LinkedList的主要区别: 

      1.ArrayList底层的数据结构为数组,而LinkedList底层结构为双向链表 

      2.在插入数据时,由于ArrayList每次插入后都需要将数组元素向后顺延 

        位置,而LinkedList只需要更改头节点和尾节点即可完成插入操作,所以 

        在插入操作较为频繁时,优先使用LinkedList 

      3.在删除数据时,由于ArrayList要保持数组的有序性,当删除后元素要亦 

        需要向后或向前移位,而LinkedList照旧还是更改头尾节点. 

      4.在更新时,由于LinkedList会使用折半遍历的方式进行查找定位元素再 

        进行更新,对比起ArrayList的直接定位下标元素替换,ArrayList对更新 

        的效率更佳 

      5.LinkedList可以模拟队列,通过LinkedList的addFirst,addLast等操作 

9.列表相等只需关心元素数据 

      Java为了我们可以安心的面向List,Set,Map等接口进行编程,因此对集合类中的equlas进行了复写,让我们在比较两个集合是否相等时,只需要比较元素数据是否相等即可,避免了因为替换集合实现类造成的错误 

Java代码  


public static void main(String[] args) {  

        List<Integer> arrayList = new ArrayList<Integer>();  

        arrayList.add(1);  

        arrayList.add(2);  

  

        List<Integer> linkedList = new LinkedList<Integer>();  

        linkedList.add(1);  

        linkedList.add(2);  

  

        System.out.println(arrayList.equals(linkedList));  

        // 不用关心具体实现,输出为true  

    }  

10.子列表只是原列表的视图 

      List接口中,提供了subList方法,作用就是返回一个列表中的子列表,这个方法与String类型的substring有点类似,请观察以下代码 

Java代码  


public static void main(String[] args) {  

        List<Integer> list = new ArrayList<Integer>();  

        list.add(1);  

        list.add(2);  

        List<Integer> subList = list.subList(0, 2);  

        System.out.println(subList); // 输出为1,2  

        subList.add(3); // 子列表增加元素  

        System.out.println(list); // 输出原列表,输出为1,2,3  

    }  

      我们可以把List接口中的subList想成和数据库中的视图一样,在数据库中虽然不建议我们对视图进行增删改,但确实是可以操作的,而且操作完也可以反映到表当中,subList也同理,它只是原列表的视图,所有的操作都将直接影响原列表,但subList和数据库视图有一点不同,下面将会进行说明 

11.推荐使用subList处理局部列表 

      现在有一个需求,在一个长度为50的列表中删除位置是10~20的元素 

Java代码  


public static void main(String[] args) {  

        List<Integer> list = new ArrayList<Integer>();  

        // 增加50行  

        for (int i = 0, size = 50; i < size; i++) {  

            list.add(i);  

        }  

          

        // 方法1  

        for (int i = 10; i < 20; i++) {  

            list.remove(10);  

        }  

          

        // 方法2  

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

        System.out.println(list);  

    }  

      以上方法2,为什么要不停删除位置为10的元素呢?这是很容易忽略的问题,因为List当删除元素时,为了保持有序性,会移动元素替换被删除的位置,所以当位置为10的元素删除后,11的位置会替换上来..在上面已经提到,修改子列表会直接影响原列表,使用方法2的一行就能完成,是不是很方便? 

12.生成子列表后不要再操作原列表 

      subList生成子列表是原列表的视图,而对子列表的改动会直接影响原列表,那若然是对原列表的更改呢? 

Java代码  


public static void main(String[] args) {  

        List<Integer> list = new ArrayList<Integer>();  

        list.add(1);  

        list.add(2);  

        List<Integer> subList = list.subList(0, 2);  

        // 原列表增加数据  

        list.add(3);  

        // 对比元素数量  

        System.out.println(list.size() == subList.size());  

  

        // 运行结果,抛出java.util.ConcurrentModificationException异常  

    }  

      看到这个异常,很多人会觉得很郁闷,并发修改异常?可我程序并没有运行在多线程的环境当中,而原因是因为,当创建subList时,list会将1个变量modCount传递到subList的构造参数当中,而当list进行修改时,就会对modCount进行累加,而subList在进行操作时,会检查modCount是否和list的一致,若然不一直就抛出异常,这就是原因,这也是subList和数据库视图不同的地方,表修改后直接反应到视图,而list修改后,不会反应到subList,所以在使用subList时,应该使用如下方式对list进行加锁 

Java代码  


public static void main(String[] args) {  

        List<Integer> list = new ArrayList<Integer>();  

        list.add(1);  

        list.add(2);  

        List<Integer> subList = list.subList(0, 2);  

        // 设置列表为只读状态  

        list = Collections.unmodifiableList(list);  

        // 再对列表进行操作  

        list.add(4);  

          

        //运行结果,抛出java.lang.UnsupportedOperationException异常  

    }  

      使用以上方式就可以明确表示,此列表是只读,当对列表进行操作时,就会抛出异常,增强了语义,而一个List可以有多个subList,但只要存在一个subList,就不能对原List进行操作.请谨记 

13.使用Comparator进行排序 

      我们都知道,在集合类中要排序时,可以使用JDK提供的帮助工具,例如 

Collections中的sort方法对List进行排序,亦可以使用Tree的数据结构来进行排序,例如Set接口中的TreeSet,TreeMap,使用Tree结构来进行排序时,是需要对象拥有比较性的,我们可以在实体类中实现Comparable<T>接口,但排序的方式是多变的,有时经过一段时间后,需求变了,需要按另外一个元素进行排序,但修改了已经稳定的实体类是不应该的,我们可以使用Comparator来进行排序,请看以下代码 

Java代码  


public static void main(String[] args) {  

  

        // 使用匿名内部类方式实现  

        List<Integer> list = new ArrayList<Integer>();  

        Collections.sort(list, new Comparator<Integer>() {  

  

            @Override  

            public int compare(Integer o1, Integer o2) {  

                // TODO Auto-generated method stub  

                return 0;  

            }  

        });  

  

        // TreeMap实现  

        Map<String, Integer> treeMap = new TreeMap<String, Integer>(  

                new Comparator<String>() {  

                    @Override  

                    public int compare(String o1, String o2) {  

                        return 0;  

                    }  

                });  

    }  

      无论是Collections的sort方法和TreeMap,TreeSet都会有重载的方式,接受一个Comparator(比较器),此时有可以在这里进行元素比较的定义 

14.使用binarySearch需要注意 

      binarySearch(二分查找)是性能较高的一种查找方式,通常我们使用的indexOf为顺序查找,当查找到匹配的值时马上返回,但若然此集合的元素数量多的话,使用顺序查找就性能就会降低,特别是当要查找的元素位于集合的末尾时,效率更低,而二分查找是将数据折半然后进行查找,没有找到指定元素时再折半,但需要注意,二分查找必须保持集合的有序性,所以在开发当中,若要使用二分查找,需先将集合进行排序.这点务必注意 

15.集合中元素equlas和compareTo必须同步 

      在刚才提及的binarySearch和indexOf两种查找当中,有一点是非常不同的,indexOf的查找的时候是使用equlas来进行匹配,equlas返回为true时认为找到元素,而binarySearch是使用compareTo进行匹配,当compareTo返回0时认为找到元素,而当一个用户实体当中,equlas是使用用户名来重写equlas判断,而compareTo使用密码来比较,那将会导致不必要的错误 

16.集合运算时,使用更优雅的方式 

      在集合的操作当中,经常会出现两个集合的 并集(or),交集(and),差集(not)等的操作,当然我们可以遍历两个集合,对比元素求出我们需要的并集交集差集等运算的结果,但这种方式处理,真的是最方便?最简单?最优雅?请看以下代码 

Java代码  


public static void main(String[] args) {  

        List<Integer> list1 = new ArrayList<Integer>();  

        List<Integer> list2 = new ArrayList<Integer>();  

  

        // 并集操作  

        list1.addAll(list2);  

  

        // 交集操作,retainAll会删除list1中没有出现在list2中的元素  

        list1.retainAll(list2);  

  

        // 差集操作  

        list1.removeAll(list2);  

  

        // 无重复并集  

        list2.removeAll(list1);  

        list1.addAll(list2);  

    }  

17.使用shuffle打乱列表 

      现在我们有一个需求,做一个扑克游戏,54张扑克存储到一个集合当中,每次发牌之前,都需要打乱一下扑克的顺序,这样的需求,我们可以有多种实现方式,使用Random来随机调整集合位置,或者集合的位置顺序交换,可有更优雅的方式吗? 

Java代码  


public static void main(String[] args) {  

        List<Integer> list = new ArrayList<Integer>();  

        // 你好,我是one-line君  

        Collections.shuffle(list);  

    }  

18.多线程时使用Vetor或HashTable 
      通过查看JDK API可以发现,Vetor和HashTable是JDK1.0时已存在的类,他们是线程安全的,可在多线程的环境下使用 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: