Java程序优化_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > Java程序优化

Java程序优化

 2015/1/21 12:53:01  swfe68612  程序员俱乐部  我要评论(0)
  • 摘要:3.1字符串优化处理3.1.1String对象及其特点。在C语言中,对字符串的处理最通常的做法是使用char数组,但这种方式的弊端是数组本身无法封装字符串操作所需的基本方法;在Java语言中,String对象可以认为是char数组的延伸和进一步封装。基本实现主要有3部分组成:char数组,offset偏移量,count长度;(1)char数组:表示String的内容,它是String对象所表示字符串的超集;(2)offset偏移量和count长度
  • 标签:程序 Java 优化

3.1 字符串优化处理

?

3.1.1 String对象及其特点。

在C语言中,对字符串的处理最通常的做法是使用char数组,但这种方式的弊端是数组本身无法封装字符串操作所需的基本方法;

在Java语言中,String对象可以认为是char数组的延伸和进一步封装。基本实现主要有3部分组成:char数组,offset偏移量,count长度;

(1)char数组:表示String的内容,它是String对象所表示字符串的超集;

(2)offset偏移量和count长度:String的真实内容需要有这两个值在char数组中进行定位和截取;

String对象3个基本特点:

(1)不变性:是指String对象一旦生成,则不能再对它进行改变。String的这个特性可以泛化成不变模式,即一个对象的状态在对象被创建之后就不再发生变化。不变模式的主要作用在于当一个对象需要被多线程共享,并且频繁访问时,可以省略同步和锁等待的时间,从而大幅提高系统性能。

(2)针对常量池优化:当两个String对象拥有相同的值时,它们只引用常量池中的同一个拷贝。当同一个字符串反复出现时,这个技术可以大幅节省内存空间。

(3)类的final定义:作为final类的String对象在系统中不可能有任何子类,这是对系统安全的防护。同时,对于JDK 1.5之前,使用final定义,有助于帮助虚拟机寻找机会,内联所有的final方法,从而提高效率。但这种优化方法在JDK 1.5之后,效果并不明显。

?

3.1.2 subString的内存泄漏

subString方法源码如下

public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen);
}
// 内存泄漏是因为调用了该构造方法,这个问题JDK 1.7中已修复
String (char value[], int offset, int count) {
    this.value = value;
    this.offset = offset;
    this.count = count;
}

以上是一个包作用域的构造方法,其目的是为了能高效且快速地共享String内的char数组对象。但在这种通过偏移量来截取字符串的方法中,String的原始内容value数组被复制到新的子字符串中。设想,如果原始字符串特别大,截取的字符串长度特别小,那么截取的子字符串中包含了原生字符串的所有内容,并占据了相应的内存空间,而仅仅通过偏移量和长度来决定实际值。这种算法提高了运算速度却浪费大量空间。【String的这个构造函数使用了以空间换时间的策略,浪费了内存空间,却提高了字符串的生成速度】

JDK 1.7中已把该问题修复,源码如下:

public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
}

?

3.1.3 字符串分割和查找

split()方法:支持正则表达式,恰当地使用,可以起到事半功倍。但是,就简单的字符串分割而言,它的性能表现却不尽人意,因此,在性能敏感的系统中频繁使用这个方法是不可取的。

StringTokenizer类:是JDK中提供的专门用来处理字符串分割子串的工具类,性能明显高于split()方法;使用方式如下:

StringTokenizer st = new StringTokenizer(orgStr, ";");
for (int i = 0; i < 10000; i ++) {
    while (st.hasMoreTokens()) {
        st.nextToken();
    }
    st = new StringTokenizer(orgStr, ";");
}

更优化的字符串分割方式:其性能远远超过split()和StringTokenizer,适合高频率调用。

String tmp = orgStr;
for (int i = 0; i < 10000; i++) {
    while (true) {
        String splitStr = null;
        int j = tmp.indexOf(";");
        if (j < 0) break;
        splitStr = tmp.substring(0, j);
        tmp = tmp.substring(j+1);
    }
    tmp = orgStr;
}

高效率的charAt()方法:它的功能和indexOf()正好相反,但是它的效率却和indexOf()一样高。判断字符串orgStr是否以“abc”开始和结束,单纯使用charAt()方法更高效:

int len = orgStr.length();
if (orgStr.charAt(0) == 'a' && orgStr.charAt(1) == 'b' && orgStr.charAt(2) == 'c');
if (orgStr.charAt(len-1) == 'a' && orgStr.charAt(len-2) == 'b' && orgStr.charAt(len-3) == 'c');

?

3.1.4 StringBuffer和StringBuilder

String常量的累加

对于静态字符串的连接操作,Java在编译时会进行彻底的优化,将多个连接操作的字符串在编译时合成一个单独的长字符串。

String变量的累加

对于变量字符串的累加,Java也做了相应的编译优化,使用了StringBuilder对象来实现字符串的累加。

在代码实现中直接对String对象做的累加操作会在编译时被优化,因此其性能比理论值好很多,但是仍建议在代码实现中,显示地使用StringBuilder或者StringBuffer对象来提升性能,而不是依靠编译器对程序进行优化。

for (int i = 0; i < 10000; i++) {
    str = str + i;
}

编译器优化后,进行反编译后的代码:

for (int i = 0; i < 10000; i++) {
    str = (new StringBuilder(String.valueOf(str))).append(i).toString();
}

以上反编译代码,虽然String的加法运行被编译成StringBuilder的实现,但在这种情况下,编译器并没有做出足够聪明的判断,每次循环都生成了新的StringBuilder实例从而大大降低了系统性能。

在无需考虑线程安全的情况下可以使用性能相对比较好的StringBuilder,但若系统有线程安全要求,只能选择StringBuffer。

StringBuilder和StringBuffer在不指定容量参数时,默认是16个字符。两者的扩容策略是将原来容量大小翻倍,以新的容量申请内存空间,建立新的char数组,然后将原来数据中的内容复制到这个新的数组中。因此,对于大对象的扩容会涉及大量的内存复制操作。所以,如果能够预先评估StringBuilder的大小,将能够有效地节省这些操作,从而提高系统的性能。

?

3.2 核心数据结构

?

3.2.1 List接口

List是重要的数据结构之一。包括3种List实现:ArrayList,Vector和LinkedList。3种List均继承自AbstractList的实现。而AbstractList直接实现了List接口,并扩展自AbstractCollection。

ArrayList和Vector使用了数组实现,可以认为封装了对内部数组的操作,几乎使用了相同的算法,它们的唯一区别可认为是对多线程的支持。ArrayList没有对任何一个方法做线程同步,因此不是线程安全的。Vector中绝大部分方法都做了线程同步,是一种线程安全的实现。

LinkedList使用了循环双向链表数据结构。LinkedList链表由一系列表项连接而成。一个表项包含3个部分:元素内容,前驱表项和后驱表项。在JDK的实现中,无论LinkedList是否为空,链表内都有一个header表项,它既表示链表的开始,也表示链表的结尾。表项header的后驱表项是链表中的第一个元素,表项header的前驱表项便是链表中最后一个元素。

增加元素到列表尾端

只要ArrayList的当前容量足够大,add()操作的效率非常高的。当ArrayList进行扩容时,才会数组复制,最终会调用System.arraycopy()方法,因此,add()操作的效率还是相当高的。

public boolean add(E e) {
    ensureCapacity(size + 1); // 确保内部数组有足够空间,扩容时会发生数组复制
    elementData[size + 1] = e;
    return true;
}

LinkedList由于使用了链表的结构,因此不需要维护容量的大小。从这点上说,它比ArrayList有一定的性能优势,然而,每次的元素增加都需要新建一个Entry对象,并进行更多的赋值操作。在频繁的系统调用中,对性能会产生一定的影响。使用LinkedList对堆内存和GC的要求更高。

private Entry<E> addBefore(E e, Entry<E> entry) {
    Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
    newEntry.previous.next = newEntry;
    newEntry.next.previous = newEntry;
    size ++;
    modCount ++;
    return newEntry;
}

增加元素到列表任意位置

由于ArrayList是基于数组实现的,而数组是一块连续的内存空间,如果在数组的任意位置插入元素,必然导致在该位置后的所有元素需要重新排列,因此,其效率相对会比较低。

public void add(int index, E element) {
    if (index > size || index < 0) {
        throw new IndexOutOfBoundsException ("Index: " + index + ", Size: " + size);
    }
    ensureCapacity(size + 1);
    System.arraycopy(elementData, index, elementData, index+1, size - index);
    element[index] = element;
    size ++;
}

每次插入操作,都会进行一次数组复制。而这个操作在增加元素到List尾端的时候是不存在的。大量的数组重组操作会导致系统性能低下。并且,插入的元素在List中的位置越靠前,数组重组的开销也越大。尽可能地将元素插入到List的尾端附近,有助于提高该方法的性能。

对于LinkedList来说,在List尾端插入数据与在任意位置插入数据是一样的。并不会因为插入的位置靠前而导致插入方法的性能降低。

public void add(int index, E element) {
    addBefore(element, (index == size ? header : entry(index)));
}

删除任意位置元素

对ArrayList来说,remove()方法和add()方法是雷同的。在任意位置移除元素后,都要进行数组的重组。

public E remove(int index) {
    RangeCheck(index);
    modCount++;
    E oldValue = (E) elementData[index];
    int numMoved = size - index - 1;
    if (numMoved > 0) {
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    }
    elementData[--size] = null;
    return oldValue;
}

在LinkedList的实现中,首先要通过循环找到要删除的元素。如果删除的位置处于List的前半段,则从前往后找;若其位置处于后半段,则从后往前找。因此无论要删除较为靠前或者靠后的元素都是非常高效的;但要移除List中间的元素却几乎要遍历完半个List,在List拥有大量元素的情况下,效率很低。

public E remove(int index) {
    return remove(entry(index));
}

private Entry<E> entry(int index) {
    if (index < 0 || index >= size) {
        throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
    }
    Entry<E> e = header;
    if (index < (size >> 1)) {
        for (int i = 0; i <= index; i++)
            e = e.next; 
    } else {
        for (int i = size; i > index; i--)
            e = e.previous;
    }
    return e;
}

SpringMVC+mybatis HTML5 全新高大尚后台框架_集成代码生成器
发表评论
用户名: 匿名