天天看點

string深入之subString

1、應用舉例

  subString方法,例如s="abcdef"  s.subString(2,5)結果為cde,長度為endindex-beginindex

2、實作原理

/**
     * Returns a new string that is a substring of this string. The
     * substring begins at the specified <code>beginIndex</code> and
     * extends to the character at index <code>endIndex - 1</code>.
     * Thus the length of the substring is <code>endIndex-beginIndex</code>.
     * <p>
     * Examples:
     * <blockquote><pre>
     * "hamburger".substring(4, 8) returns "urge"
     * "smiles".substring(1, 5) returns "mile"
     * </pre></blockquote>
     *
     * @param      beginIndex   the beginning index, inclusive.
     * @param      endIndex     the ending index, exclusive.
     * @return     the specified substring.
     * @exception  IndexOutOfBoundsException  if the
     *             <code>beginIndex</code> is negative, or
     *             <code>endIndex</code> is larger than the length of
     *             this <code>String</code> object, or
     *             <code>beginIndex</code> is larger than
     *             <code>endIndex</code>.
     */
    public String substring(int beginIndex, int endIndex) {
	if (beginIndex < 0) {
	    throw new StringIndexOutOfBoundsException(beginIndex);
	}
	if (endIndex > count) {
	    throw new StringIndexOutOfBoundsException(endIndex);
	}
	if (beginIndex > endIndex) {
	    throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
	}
	return ((beginIndex == 0) && (endIndex == count)) ? this :
	    new String(offset + beginIndex, endIndex - beginIndex, value);
    }
           

  通過源代碼可以發現

    11、string内部通過char[]數組實作

    12、subString方法傳回了新的string對象,但是string對象卻指向原來得char數組,如果原來的string很大,即使原來的string釋放,記憶體空間也無法回收

    13、offset值預設為0,第一個參數為偏移,第二個為長度,第三個為char數組

// Package private constructor which shares value array for speed.
    String(int offset, int count, char value[]) {
	this.value = value;
	this.offset = offset;
	this.count = count;
    }
           

      14、預設string構造方法如下:

/**
     * Initializes a newly created {@code String} object so that it represents
     * an empty character sequence.  Note that use of this constructor is
     * unnecessary since Strings are immutable.
     */
    public String() {
	this.offset = 0;
	this.count = 0;
	this.value = new char[0];
    }
           

       15、記憶體洩露測試代碼

package com.string;

import java.util.ArrayList;
import java.util.List;

public class TestSubstringLeak {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		String aa=new String(new char[100000]);
		List<String> list=new ArrayList<String>();
		for (int i = 0; i < 10000000; i++) {
			//雖然截取的字元串占據空間很小,但是由于aa巨大的數組空間被共享,沒有釋放,是以記憶體溢出
//			list.add(aa.substring(2, 3));
			list.add(new String(aa.substring(2,3)));//加一層構造方法後,構造方法内部進行了數組的拷貝,原有的巨大數組空間被回收,不會記憶體溢出
		}
	}

}
           

為了避免記憶體拷貝、加快速度,Sun JDK直接複用了原String對象的char[],偏移量和長度來辨別不同的字元串内容。也就是說,subString出的來String小對象仍然會指向原String大對象的char[],split也是同樣的情況 。這就解釋了,為什麼HashMap中String對象的char[]都那麼大。

原因解釋

    其實上一節已經分析出了原因,這一節再整理一下:

  1. 程式從每個請求中得到一個String大對象,該對象内部char[]的長度達數百K。
  2. 程式對String大對象做split,将split得到的String小對象放到HashMap中,用作緩存。
  3. Sun JDK6對String.split方法做了優化,split出來的Stirng對象直接使用原String對象的char[]
  4. HashMap中的每個String對象其實都指向了一個巨大的char[]
  5. HashMap的上限是萬級的,是以被緩存的Sting對象的總大小=萬*百K=G級。
  6. G級的記憶體被緩存占用了,大量的記憶體被浪費,造成記憶體洩露的迹象。

解決方案

    原因找到了,解決方案也就有了。split是要用的,但是我們不要把split出來的String對象直接放到HashMap中,而是調用一下String的拷貝構造函數String(String original),這個構造函數是安全的,具體可以看代碼: