在java中最常見的使用就是對字符串的操作:首先先說一下對字符串的理解:字符串就是一連串字符序列,Java提供了String和StringBuffer兩個類來封裝字符串,並提供一系列方法來操作字符串對象。接下來對它們一一描述:
String類是不可變類:
即一旦一個String對象被創建以後,包含在這個對象中的字符串是不可改變的,直到這個對象被銷燬。
StringBuffer類:
在java中則代表一個字符序列可變的字符串,即一個StringBuffer被創建以後,StringBuffer提供的append()、insert()、reverse()、setChatAt()、setLength()等方法可以改變這個字符串對象的字符序列。一旦通過StringBuffer生成了最終想要的字符串,就可以調用它的toString()方法將其轉換爲一個String對象。
StringBuilder類:
它是在jdk1.5新增的一個StringBuilder類,它也代表字符串對象,在這裏重點說一下StringBuilder和StringBuffer基本相似,它們底層的構造器基本相同,不同的是StringBuffer是線程安全的,而StringBuilder則沒有實現線程安全功能的,正因爲這個原因,它的性能較好一些。。。
來點刺激的String的源碼:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
private static final long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
...
可以看出String是由char[]來實現的,String是final類,也就意味着String類是不可以繼承的
注意:剛開始說String是不可以變的就是因爲char[]在是private的,並且String類沒有提供setter方法,導致無法改變這個String對象 String s = “123456”中的s是對象的引用,對象的引用指向對象。對象是不可變的,但是對象的引用是可變的。
下面分析看一下它們中的方法的源碼:
substring
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
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);
}
concat
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
replace
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}
可以發現subString、concat、replace操作都是不是在原字符串進行的,而是創建了一個新的字符串。也就是說,進行了上述操作後,本身的字符串並沒有改變。只是返回了一個新的對象的引用。。。
對String操作的任何改變都不會改變原對象,而任何改變String對象的操作都會產生新對象。
如:
String str1 = "java";
str1 = str1 + "struts";
str1 = str1 + "spring";
在這裏會產生五個字符串直接量不但有原先的3個還會產生額外兩個字符串直接量“javastruts”和“javastrutsspring”。程序中的str1依次指向3個不同的字符串對象。
因爲String是不可變的,所以會產生很多的臨時變量,在這裏就會選擇StringBuffer或者StringBuilder,因爲它們就會解決這個問題
StringBuffer、StringBuilder有兩個屬性:length和capacity。
其中length屬性表示其包含的字符序列的長度。與String對象length不同的是,StringBuffer、StringBuilder的length是可以改變的,可以通過length()、setLength(int len)方法來訪問和修改其字符串序列的長度,capacity屬性表示StringBuilder容量,capacity通常比length大,程序通常無須任何關心capacity屬性。
public static void main(String[] args)
{
String str = "";
for(int i = 0;i < 10000;i++)
str += "Hello";
}
通過反編譯可以看出每次循環一次都會new 一個StringBuilder對象,然後進行append操作,最後用toString方法返回String對象。試想一下如果這些對象沒有被JVM回收,則會造成多大的資源浪費。實際上jvm不傻,他會自動優化爲:
StringBuilder sb = new StringBuilder(string);
sb.append("Hello");
str.toString();
再看一段代碼:
public static void main(String[] args)
{
StringBuilder sb = new StringBuilder();
for(int i = 0;i < 10000;i++)
sb.append("Hello");
}
反編譯其字節碼文件,可以看出new操作只進行了一次,也就是說只生成一個對象,append操作是在原有對象上進行的,因此在循環10000次之後,資源消耗要小得多。
再講述一下StringBuffer,比StringBuilder多了一個關鍵字: synchronize。這個關鍵字在多線程操作的時起到安全保護的作用。
總結:如果在對字符串修改較少的情況下,建議使用String str = “Hello”;這種形式;如果在對字符串修改較多,則用StringBuilder;涉及到多線程,則用StringBuffer。