?
在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之后,效果并不明显。
?
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);
}
?
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');
?
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的大小,将能够有效地节省这些操作,从而提高系统的性能。
?
?
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 全新高大尚后台框架_集成代码生成器