美文网首页
【优雅编程之道】之数组的7点建议

【优雅编程之道】之数组的7点建议

作者: 阿_毅 | 来源:发表于2017-02-20 19:08 被阅读115次

    开心一笑

    【小明喜欢上一个刚来的女同事,她是医院里的检验师,为了套近乎就经常跑到她科室去倒开水喝,熟了后就发现她随身的小包包里总放着一把小水果刀,于是我问她:你总放把小刀包里干嘛?她答道:下夜班防身啊!虽然刀小,但是我清楚的知道人的大动脉在哪里!我去!这特么以后还敢撩吗?】

    提出问题

    如何优雅的使用数组???

    唯美爱情图片

    解决问题

    初始化数组格式选择

    程序清单 2-1        
    
    @Test
    public void test(){
    
        //第一种初始化数组格式
       String[] a = new String[5];
    
        //第二种初始化数组格式
       String b[] = new String[5];
    
    }
    

    通常情况下,大部分的程序员都会选择用第一种初始化数组的格式。但我确实碰到过有少数的开发人员用第二种初始化数组格式。这里我建议使用第一种,主要原因是:大多数人都使用第一种,自然,我们也要少数服从多数。而且从字面意思来看,String[] a 表示:初始化一个字符串(String)数组([]) a,读起来更加顺口。

    数组是性能调优的一大法宝

    数组和集合,我们都可以简单的理解为篮子。不同的篮子可以装不同的东西。
    对于基本类型,建议你使用数组篮子,相对于集合篮子,它效率更高。具体实例如下:

    程序清单 2-1    
    
    @Test
    public void test(){
    
        int sum = 0;
        //数组装int基本类型
        int[] baseTypeArray = new int[10000];
        for(int i=0,len = baseTypeArray.length;i < len;i++){
            //不存在自动装箱和拆箱的操作
            sum = sum + baseTypeArray[i];
        }
        //集合装包装类型
        List<Integer> objectTypeList = new ArrayList<>(10000);
        for(int i=0,len = objectTypeList.size();i < len;i++){
            //在这里有自动装箱和拆箱的操作,效率低
            sum = sum + objectTypeList.get(i);
        }
    }
    

    四种数组复制方式的性能比较和抉择

    数组copy有很多种方法,效率不一。我们先看下面具体实例:

    程序清单 2-1    
    
    /**
     * 测试4种数组复制效率比较
     * @author 阿毅
     * @date 2017/2/7.
     */
    public class AyTest {
    
        private static final byte[] buffer = new byte[1024*10];
        static {
            for (int i = 0; i < buffer.length; i++) {
                buffer[i] = (byte) (i & 0xFF);
            }
        }
        private static long startTime;
    
        public static void main(String[] args) {
            startTime = System.nanoTime();
            byte[] newBuffer = new byte[buffer.length];
            for(int i=0;i<buffer.length;i++) {
                newBuffer[i] = buffer[i];
            }
            calcTime("forCopy");
    
            startTime = System.nanoTime();
            byte[] newBuffer2 = buffer.clone();
            calcTime("cloneCopy");
    
            startTime = System.nanoTime();
            byte[] newBuffer3 = Arrays.copyOf(buffer, buffer.length);
            calcTime("arraysCopyOf");
    
            startTime = System.nanoTime();
            byte[] newBuffer4 = new byte[buffer.length];
            System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
            calcTime("systemArraycopy");
        }
    
        private static void calcTime(String type) {
            long endTime = System.nanoTime();
            System.out.println(type + " cost " +(endTime-startTime)+ " nanosecond");
        }
    }
    

    运行结果:

    forCopy cost 711576 nanosecond
    cloneCopy cost 53490 nanosecond
    arraysCopyOf cost 119946 nanosecond
    systemArraycopy cost 39712 nanosecond
    

    多运行几次,我们得出数组复制效率:

    System.arraycopy > clone > Arrays.copyOf > for

    综上所述,当复制大量数据时,使用System.arraycopy()命令。

    警惕二维数组的拷贝

    因为java中没有二维数组的概念,只有数组的数组。所以对数组的数组进行拷贝,无论对clone或者arraycopy,都只是拷贝其中的引用。事实上,还是指向同一个值。具体实例如下:

    程序清单 2-1    
    
    @Test
    public  void test(){
        
        //一维数组的拷贝
        int[] a = {5,2,0,1,3,1,4};
        int[] b = a.clone();
        b[0]=10;
        System.out.println(a[0] + " copy " + b[0]);
        //打印结果:5 copy 10
    
        //二维数组的拷贝:{5,2,0} 和 {1,3,1,4}这两个一维数组都有引用指向它们
        int[][] c = {{5,2,0},{1,3,1,4}};
        
        int[][] d = c.clone();
        d[0][0]=10;
        System.out.println(c[0][0] + " coyp " + d[0][0]);
        //打印结果:10 coyp 10
    
        //二维数组的拷贝:{5,2,0} 和 {1,3,1,4}这两个一维数组都有引用指向它们
        int[][] e = {{5,2,0},{1,3,1,4}};
        int[][] f = {{},{}};
        System.arraycopy(e, 0, f, 0, e.length);
        f[0][0]=10;
        System.out.println(e[0][0] + " coyp " + f[0][0]);
        //打印结果:10 coyp 10
    }
    

    从上面的例子可以看出,利用clone和System.arraycopy进行二维数组的拷贝,都是浅拷贝。在真实项目中,我们要警惕使用。

    数组的深复制及优雅实现

    在上面的警惕二维数组的拷贝中我们已经知道,clone和System.arraycopy都是浅拷贝,下面提供一些深拷贝的优雅方法:

    例:

    程序清单 2-1    
    
    @Test
    public void test(){
        Boy[] boys = new Boy[1];
        Boy boy = new Boy("60","ay",new Girl("30","al"));
        boys[0] = boy;
        //利用org.apache.commons.lang3.SerializationUtils进行深度clone
        Boy[] coypBoys = (Boy[])SerializationUtils.clone(boys);
        //clone是浅clone
        //Boy[] coypBoys = boys.clone();
        coypBoys[0].id = "change_60";
        coypBoys[0].name = "change_ay";
        coypBoys[0].girl.id = "change_girl_30";
        coypBoys[0].girl.name = "change_girl_al";
    
        for(int i=0,len = boys.length;i<len;i++){
            System.out.println("the origin of id :" + boys[i].id);
            System.out.println("the origin of name :" + boys[i].name);
            System.out.println("the origin of girl id :" + boys[i].girl.id);
            System.out.println("the origin of girl name :" + boys[i].girl.name);
        }
    
        for(int i=0,len = coypBoys.length;i<len;i++){
            System.out.println("the copy of id :" + coypBoys[i].id);
            System.out.println("the copy of name :" + coypBoys[i].name);
            System.out.println("the copy of girl id :" + coypBoys[i].girl.id);
            System.out.println("the copy of girl name :" + coypBoys[i].girl.name);
        }
    }
    

    执行结果:

    the origin of id :60
    the origin of name :ay
    the origin of girl id :30
    the origin of girl name :al
    the copy of id :change_60
    the copy of name :change_ay
    the copy of girl id :change_girl_30
    the copy of girl name :change_girl_al
    

    上面例子中,我们利用 apache 提供的工具类 SerializationUtils 进行深度clone,避免重复造轮子。有一点需要注意的是,Boy 和 Girl 类都要实现 Serializable 接口。具体如下:

    程序清单 2-1    
    
    class Boy implements Serializable{
        public String id;
        public String name;
        public Girl girl;
        Boy(String id,String name,Girl girl){
            this.id = id;
            this.name = name;
            this.girl = girl;
        }
    }
    
    class Girl implements Serializable{
        public String id;
        public String name;
        Girl(String id,String name){
            this.id = id;
            this.name = name;
        }
    }
    

    我们把 //Boy[] coypBoys = boys.clone();注释打开,运行程序,结果如下:

    the origin of id :change_60
    the origin of name :change_ay
    the origin of girl id :change_girl_30
    the origin of girl name :change_girl_al
    the copy of id :change_60
    the copy of name :change_ay
    the copy of girl id :change_girl_30
    the copy of girl name :change_girl_al
    

    可以证实,普通的clone只是浅拷贝而已。

    一维数组替换二维数组,提高性能

    在项目开发中,如果有碰到二维数组,我们要机智的把它转化为一维数组。因为一维数组比较容易理解,而且性能会比二维数组高

    @Test
    public void test(){
        int arr[][] = new int[1000][1000];
        long startTime = System.currentTimeMillis();
        System.out.println("start time is :" + startTime);
        for(int i=0;i<1000;i++){
            //在for循环中进行方法调用,影响性能
            for(int j=0;j<arr[0].length;j++){
                arr[i][j] = j;
            }
        }
        long endTime = System.currentTimeMillis();
        System.out.println("consume time is : " + (endTime - startTime));
        // --------------------------------------------------------------
        startTime = System.currentTimeMillis();
        System.out.println("start time is :" + startTime);
        //这里是重点
        int len = arr[0].length;
        for(int i=0;i<1000;i++){
            for(int j=0;j<len;j++){
                arr[i][j] = j;
            }
        }
        endTime = System.currentTimeMillis();
        System.out.println("consume time is : " + (endTime - startTime));
    }
    

    运行结果:

    start time is :1487563038580
    consume time is : 19
    start time is :1487563038600
    consume time is : 9
    

    如果二维数组没办法转化为一维数组,那么我们要极力避免在循环体内进行方法调用,如上面例子中的arr[0].length,每循环一次都会执行一次length方法,执行时长也多出10ms。因此,我们要把arr[0].length抽出来,减少方法执行次数。

    考虑用集合,栈和队列来代替数组

    我们建议用集合,栈和队列等按顺序存取元素的数据结构来取代数组。在之前,我们提过:对于存放基本类型,优先选择用数组。而对于存放其他类型的数据,我们建议用集合,栈和队列。具体原因如下:在数组里随机访问就像在程序里随机使用goto语句一样,这样的访问很容易变得难以管理且容易出错。要证明其是否正确也困难。

    读书感悟

    来自山下英子《断舍离》

    • 断绝不需要的东西、舍弃多余的废物、脱离对物品的执念。
    • 断=断绝想要进入自己家的不需要的东西。舍=舍弃家里到处泛滥的破烂儿。离=脱离对物品的执念,处于游刃有余的自在的空间。
    • 断舍离,就是透过整理物品了解自己,整理内心的混沌,让人生舒适的行动技术。换句话说,就是利用收拾家里的杂物来整理内心的废物,让人生转而开心的方法。
    • 要是自己能随便凑合着用一个东西,那别人也会用随便的态度来对待你。
    • 断舍离的主角并不是物品,而是自己,而时间轴永远都是现在。选择物品的窍门,不是“能不能用”,而是“我要不要用”,这一点必须铭刻在心。
    • 不管东西有多贵,有多稀有,能够按照自己是否需要来判断的人才够强大。能够放开执念,人才能更有自信。
    • 这就是让物品当了主角的状态……物品原本是因为“我用”才有价值。可多数人都说“眼镜可以用”,“筷子可以用”,拿物品当了主语。这是把主角的位子拱手让给了物品,把焦点聚集在物品上的状态……收纳这些东西,不过是在做垃圾分类。

    经典故事

    【甲向乙诉苦:“上星期,一粒沙子钻进了我妻子的眼睛,花50元请医生才把它清理出来。”乙不屑地说:“那算得了什么,上星期,一件皮大衣入了我妻子的眼,我花费了3000元。”

    永远不要以为自己的境遇是最值得说的,你的听众会认为他的境遇更值得说,因为人是以自我为中心的动物。】

    大神文章

    【1】Thinking in Java (书籍)
    【2】Agile Java(书籍)
    【3】编写高质量代码:改善Java程序的151个建议(书籍)
    【4】Java程序性能优化 让你的Java程序更快、更稳定(书籍)
    【5】Effective Java中文版 第2版.Joshua Bloch(书籍)

    其他

    如果有带给你一丝丝小快乐,就让快乐继续传递下去,欢迎点赞、顶、欢迎留下宝贵的意见、多谢支持!

    相关文章

      网友评论

          本文标题:【优雅编程之道】之数组的7点建议

          本文链接:https://www.haomeiwen.com/subject/febewttx.html