`
chenssy
  • 浏览: 62087 次
  • 性别: Icon_minigender_1
  • 来自: 岳阳
社区版块
存档分类
最新评论

java提高篇(十九)-----数组之二

 
阅读更多

前面一节主要介绍了数组的基本概念,对什么是数组稍微深入了一点点,在这篇博文中主要介绍数组的其他方面。

三、性能?请优先考虑数组

在java中有很多方式来存储一系列数据,而且在操作上面比数组方便的多?但为什么我们还需要使用数组,而不是替代它呢?数组与其他种类的容器之间的区别有三个方面:效率、类型和保存基本类型的能力。在java中,数组是一种效率最高的存储和随机访问对象引用序列的方式。

在项目设计中数组使用的越来越少了,而且它确实是没有List、Set这些集合使用方便,但是在某些方面数组还是存在一些优势的,例如:速度,而且集合类的底层也都是通过数组来实现的。

--------这是ArrayList的add()------
    public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
    }

下面利用数组和list来做一些操作比较。

一、求和

Long time1 = System.currentTimeMillis();
        for(int i = 0 ; i < 100000000 ;i++){
            sum += arrays[i%10];
        }
        Long time2 = System.currentTimeMillis();
        System.out.println("数组求和所花费时间:" + (time2 - time1) + "毫秒");
        Long time3 = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            sum  += list.get(i%10);
        }
        Long time4 = System.currentTimeMillis();
        System.out.println("List求和所花费时间:" + (time4 - time3) + "毫秒");
--------------Output:
数组求和所花费时间:696毫秒
List求和所花费时间:3498毫秒

从上面的时间消耗上面来说数组对于基本类型的求和计算的速度是集合的5倍左右。其实在list集合中,求和当中有一个致命的动作:list.get(i)。这个动作是进行拆箱动作,Integer对象通过intValue方法自动转换成一个int基本类型,在这里就产生了不必要的性能消耗。

所以在性能要求较高的场景中请优先考虑数组。

四、变长数组?

数组是定长的,一旦初始化声明后是不可改变长度的。这对我们在实际开发中是非常不方便的,聪明的我们肯定是可以找到方法来实现的。就如java不能实现多重继承一样,我们一样可以利用内部类和接口来实现(请参考:java提高篇(九)-----实现多重继承)。

那么如何来实现变长数组呢?我们可以利用List集合add方法里面的扩容思路来模拟实现。下面是ArrayList的扩容方法:

public void ensureCapacity(int minCapacity) {
        modCount++;  
        int oldCapacity = elementData.length;
        /**
         * 若当前需要的长度超过数组长度时进行扩容处理
         */
        if (minCapacity > oldCapacity) {
            Object oldData[] = elementData;    
            int newCapacity = (oldCapacity * 3) / 2 + 1;    //扩容
            if (newCapacity < minCapacity)
                newCapacity = minCapacity;
            //拷贝数组,生成新的数组
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    }

这段代码对我们有用的地方就在于if语句后面。它的思路是将原始数组拷贝到新数组中,新数组是原始数组长度的1.5倍。所以模拟的数组扩容代码如下:

public class ArrayUtils {
    /**
     * @desc 对数组进行扩容
     * @author chenssy
     * @data 2013-12-8
     * @param <T>
     * @param datas 原始数组
     * @param newLen 扩容大小
     * @return T[]
     */
    public static <T> T[] expandCapacity(T[] datas,int newLen){
        newLen = newLen < 0 ? datas.length :datas.length + newLen;   
        //生成一个新的数组
        return Arrays.copyOf(datas, newLen);
    }
    
    /**
     * @desc 对数组进行扩容处理,1.5倍
     * @author chenssy
     * @data 2013-12-8
     * @param <T>
     * @param datas  原始数组
     * @return T[]
     */
    public static <T> T[] expandCapacity(T[] datas){
        int newLen = (datas.length * 3) / 2;      //扩容原始数组的1.5倍
        //生成一个新的数组
        return Arrays.copyOf(datas, newLen);
    }
    
    /**
     * @desc 对数组进行扩容处理,
     * @author chenssy
     * @data 2013-12-8
     * @param <T>
     * @param datas 原始数组
     * @param mulitiple 扩容的倍数
     * @return T[]
     */
    public static <T> T[] expandCapacityMul(T[] datas,int mulitiple){
        mulitiple = mulitiple < 0 ? 1 : mulitiple;
        int newLen = datas.length * mulitiple;
        return Arrays.copyOf(datas,newLen );
    }
}

通过这种迂回的方式我们可以实现数组的扩容。因此在项目中如果确实需要变长的数据集,数组也是在考虑范围之内的,我们不能因为他是固定长度而排斥他!

五、数组复制问题

以前在做集合拷贝的时候由于集合没有拷贝的方法,所以一个一个的复制是非常麻烦的,所以我就干脆使用List.toArray()方法转换成数组然后再通过Arrays.copyOf拷贝,在转换成集合,个人觉得非常方便,殊不知我已经陷入了其中的陷进!我们知道若数组元素为对象,则数组里面数据是对象引用

public class Test {
    public static void main(String[] args) {
        Person person_01 = new Person("chenssy_01");
        
        Person[] persons1 = new Person[]{person_01};
        Person[] persons2 = Arrays.copyOf(persons1,persons1.length);
        
        System.out.println("数组persons1:");
        display(persons1);
        System.out.println("---------------------");
        System.out.println("数组persons2:");
        display(persons2);
        //改变其值
        persons2[0].setName("chessy_02");
        System.out.println("------------改变其值后------------");
        System.out.println("数组persons1:");
        display(persons1);
        System.out.println("---------------------");
        System.out.println("数组persons2:");
        display(persons2);
    }
    public static void display(Person[] persons){
        for(Person person : persons){
            System.out.println(person.toString());
        }
    }
}
-------------Output:
数组persons1:
姓名是:chenssy_01
---------------------
数组persons2:
姓名是:chenssy_01
------------改变其值后------------
数组persons1:
姓名是:chessy_02
---------------------
数组persons2:
姓名是:chessy_02

从结果中发现,persons1中的值也发生了改变,这是典型的浅拷贝问题。所以通过Arrays.copyOf()方法产生的数组是一个浅拷贝。同时数组的clone()方法也是,集合的clone()方法也是,所以我们在使用拷贝方法的同时一定要注意浅拷贝这问题。

有关于深浅拷贝的博文,参考:

渐析java的浅拷贝和深拷贝:http://www.cnblogs.com/chenssy/p/3308489.html

使用序列化实现对象的拷贝:http://blog.csdn.net/chenssy/article/details/12952063

六、数组转换为List注意地方

我们经常需要使用到Arrays这个工具的asList()方法将其转换成列表。方便是方便,但是有时候会出现莫名其妙的问题。如下:

public static void main(String[] args) {
        int[] datas = new int[]{1,2,3,4,5};
        List list = Arrays.asList(datas);
        System.out.println(list.size());
    }
------------Output:
1

结果是1,是的你没有看错, 结果就是1。但是为什么会是1而不是5呢?先看asList()的源码

public static <T> List<T> asList(T... a) {
        return new ArrayList<T>(a);
    }

注意这个参数:T…a,这个参数是一个泛型的变长参数,我们知道基本数据类型是不可能泛型化的,也是就说8个基本数据类型是不可作为泛型参数的,但是为什么编译器没有报错呢?这是因为在java中,数组会当做一个对象来处理,它是可以泛型的,所以我们的程序是把一个int型的数组作为了T的类型,所以在转换之后List中就只会存在一个类型为int数组的元素了。所以我们这样的程序System.out.println(datas.equals(list.get(0)));输出结果肯定是true。当然如果将int改为Integer,则长度就会变成5了。

我们在看下面程序:

enum Week{Sum,Mon,Tue,Web,Thu,Fri,Sat}
    public static void main(String[] args) {
        Week[] weeks = {Week.Sum,Week.Mon,Week.Tue,Week.Web,Week.Thu,Week.Fri};
        List<Week> list = Arrays.asList(weeks);
        list.add(Week.Sat);
    }

这个程序非常简单,就是讲一个数组转换成list,然后改变集合中值,但是运行呢?

Exception in thread "main" java.lang.UnsupportedOperationException
    at java.util.AbstractList.add(AbstractList.java:131)
    at java.util.AbstractList.add(AbstractList.java:91)
    at com.array.Test.main(Test.java:18)

编译没错,但是运行竟然出现了异常错误!UnsupportedOperationException ,当不支持请求的操作时,就会抛出该异常。从某种程度上来说就是不支持add方法,我们知道这是不可能的!什么原因引起这个异常呢?先看asList()的源代码:

public static <T> List<T> asList(T... a) {
        return new ArrayList<T>(a);
    }

这里是直接返回一个ArrayList对象返回,但是注意这个ArrayList并不是java.util.ArrayList,而是Arrays工具类的一个内之类:

private static class ArrayList<E> extends AbstractList<E>
    implements RandomAccess, java.io.Serializable{
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;
        ArrayList(E[] array) {
            if (array==null)
                throw new NullPointerException();
        a = array;
    }
       /** 省略方法 **/
    }

但是这个内部类并没有提供add()方法,那么查看父类:

public boolean add(E e) {
    add(size(), e);
    return true;
    }
    public void add(int index, E element) {
    throw new UnsupportedOperationException();
    }

这里父类仅仅只是提供了方法,方法的具体实现却没有,所以具体的实现需要子类自己来提供,但是非常遗憾

这个内部类ArrayList并没有提高add的实现方法。在ArrayList中,它主要提供了如下几个方法:

1、size:元素数量

2、toArray:转换为数组,实现了数组的浅拷贝。

3、get:获得指定元素。

4、contains:是否包含某元素。

所以综上所述,asList返回的是一个长度不可变的列表。数组是多长,转换成的列表是多长,我们是无法通过add、remove来增加或者减少其长度的。

参考文献:《编写高质量代码--改善Java程序的151个建议》


分享到:
评论

相关推荐

    Java数据结构篇-链表与数组实现栈.pptx.pptx

    数据结构的定义 数据结构是计算机存储、组织数据的方式,用于高效地访问和修改数据。...Java提供了丰富的数据结构库,包括数组、链表、栈、队列等,这些数据结构为程序员提供了处理各种问题的工具和方法。

    Java提高篇之hashCode

    在讲解数组时(java提高篇(十八)——数组),我们提到数组是java中效率高的数据结构,但是“高”是有前提的。第一我们需要知道所查询数据的所在位置。第二:如果我们进行迭代查找时,数据量一定要小,对于大数据量...

    Java语言程序设计教程(Java 7)——入门与提高篇02

    第2章是对基本编码能力的培养,首先介绍如何使用Java语言表示信息以及如何使用Java语言处理信息,然后介绍了如何使用选择结构和循环结构,最后介绍了如何编写方法和使用数组。

    Java语言程序设计教程(Java 7)——入门与提高篇03

    第3章是对面向对象基本概念的介绍,首先介绍了如何编写类和如何创建对象,然后介绍了基本类型及其封装类型的用法,在此基础上介绍了对象数组、String相关类和一些常用的工具类。

    Java核心开发技术从入门到精通.谭贞军.扫描版.part2

    第16课到22课技术提高篇,分别讲解Java网络通信开发、HTML和XML、JSP、JavaBean和Servlet、数据库、JDBC技术和SQL语句;第23课和24课是综合应用篇,分别讲解了使用Java开发画图板和数据库项目——公司管理系统的方法...

    从零开始学Java资源包

    全书内容分为六篇共23章,讲解了Java的各个方面,主要内容包括Java语言的环境配置、基本语法、流程控制语句、字符串处理、数组、面向对象、图形界面设计、输入/输出、异常处理、网络编程、数据库及Java Web基础JSP和...

    《Java程序设计自学手册》源代码

    第2篇为提高篇 第9章 第14章 主要介绍异常处理 I O处理技术 线程技术 JDBC技术 Java Swing等一些高级开发技术 在本篇的最后还通过一个进销存管理系统对前面章节的内容进行了实践;第3篇为实例篇 第15章 第20章 主要...

    Java_Web编程新手自学手册.pdf

    第7~12章是提高篇,讲解了搭建Java Web开发环境,HTML,和XML,JSP指令和内置对象,标签和动作指令,Servlet详解,JavaBean组件技术等内容;第13~15章是数据库篇,讲解了数据库技术,使用JDBC连接数据库,数据库...

    Java程序设计自学手册光盘

    第2篇为提高篇(第9章~第14章),主要介绍异常处理、I/O处理技术、线程技术、JDBC技术、Java Swing等一些高级开发技术,在本篇的最后还通过一个进销存管理系统对前面章节的内容进行了实践;第3篇为实例篇(第15章~第...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    第1篇 Java语言基本语法 第1章 让自己的第一个Java程序跑起来 2 教学视频:19分钟 1.1 想要用Java改变这个世界吗? 2 1.1.1 Java有什么优势? 2 1.1.2 Java在哪儿? 3 1.2 准备好开始Java之旅 3 1.2.1 下载...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    第1篇 Java语言基本语法 第1章 让自己的第一个Java程序跑起来 2 教学视频:19分钟 1.1 想要用Java改变这个世界吗? 2 1.1.1 Java有什么优势? 2 1.1.2 Java在哪儿? 3 1.2 准备好开始Java之旅 3 1.2.1 下载...

    Java核心开发技术从入门到精通.谭贞军.扫描版.part1

    第16课到22课技术提高篇,分别讲解Java网络通信开发、HTML和XML、JSP、JavaBean和Servlet、数据库、JDBC技术和SQL语句;第23课和24课是综合应用篇,分别讲解了使用Java开发画图板和数据库项目——公司管理系统的方法...

    Java提高篇之关键字static

     Java把内存分为栈内存和堆内存,其中栈内存用来存放一些基本类型的变量、数组和对象的引用,堆内存主要存放一些对象。在JVM加载一个类的时候,若该类存在static修饰的成员变量和成员方法,则会为这些成员变量和...

    由浅入深学Java—基础、进阶与必做260题 高清pdf版

    《由浅入深学Java:基础、进阶与必做260题》共分为4篇共28章,第1篇从最基本的JDK安装讲起,包括基本语法与数据类型、数组、字符串、程序控制语句、面向对象编程、继承、多态、接口等内容;第2篇讲解了图形界面开发...

    由浅入深学Java—基础、进阶与必做260题.pdf

    内容简介 本书讲解了Java方方面面的知识,全书共分为4篇共28章,第1篇从*基本的JDK安装讲起,包 括基本语法与数据类型、数组、字符串、程序控制语句、面向对象编程、继承、多态、接口等内容;第2 篇讲解了图形界面...

    java自学之道

    第二篇 提高篇 第3章 Java常用算法 3.1递归算法 3.2冒泡排序算法 3.3 快速排序算法 3.4选择排序算法 3.5直接插入算法 3.6希尔排序算法 3.7 二分查找算法 3.8 二叉树 3.9 图的实现 3.10 生产者消费者的实现 3.11 ...

    学通Java的24堂课

    第2部分 提高篇 第8堂课 类的继承与多态特性 219 第9堂课 其他类特性与异常处理 257 第10堂课 swing编程基础 283 第11堂课 多线程编程 319 第12堂课 事件处理的应用 345 第13堂课 常用工具类 373 第14堂课 ...

    Java开发实战1200例(第1卷).(清华出版.李钟尉.陈丹丹).part3

    实例043 将二维数组中的行列互换 53 实例044 利用数组随机抽取幸运观众 54 实例045 用数组设置JTable表格的列名与列宽 55 3.2 数组操作 57 实例046 数组的下标界限 57 实例047 按钮控件数组实现计数器界面 58 实例...

Global site tag (gtag.js) - Google Analytics