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

2017-02-20  本文已影响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(书籍)

其他

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

上一篇下一篇

猜你喜欢

热点阅读