StringBuffer是线程安全的,另外两个都不是线程安全的。因为StringBuffer所有的方法都用synchronized修饰了。
这个三个中StringBuilder在字符串拼接上,StringBuilder最快,StringBuffer次之,String最慢。为什么?
String是不可变的,所以在拼接字符串都是通过StringBuffer新建临时变量,进行拼接再将原来的变量指向拼接好的字符串。所以String比StringBuffer慢。
StringBuilder和StringBuffer之中的方法是一样的,但是StringBuilder的方法没有synchronized修饰,考虑的线程安全,一定会牺牲一些性能的。所以StringBuilder比StringBuffe快。
public StringBuilder() {
super(16);
}
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
super(str.length() + 16)方法就是AbstractStringBuilder的构造函数,因为StringBulider是继承这个类的。调用的是父类的构造方法进行初始化。
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
append()方法是我们用的最多的一个方法,进行字符串的构造基本都需要它。之前我在做项目中要拼接sql语句,都是用的append()来操作的,这个方法可以识别空格,可以使用+来连接连个字符串,拼接字符串很方便。
append()方法重载很多,因为拼接的组件有很多类型的,系统肯定要提供完整的方法体系。但是方法的结构都是一样的。如下:
public StringBuilder append(String str) {
super.append(str);
return this;
}
其实吧真正实现是在AbstractStringBuilder类中。count是已经初始化的容量中,实际使用了多少容量。每一字符占一个容量。
思路:
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}
添加boolean类型的
public AbstractStringBuilder append(boolean b) {
if (b) {
ensureCapacityInternal(count + 4);
value[count++] = 't';
value[count++] = 'r';
value[count++] = 'u';
value[count++] = 'e';
} else {
ensureCapacityInternal(count + 5);
value[count++] = 'f';
value[count++] = 'a';
value[count++] = 'l';
value[count++] = 's';
value[count++] = 'e';
}
return this;
}
public AbstractStringBuilder delete(int start, int end) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
end = count;
if (start > end)
throw new StringIndexOutOfBoundsException();
int len = end - start;
if (len > 0) {
System.arraycopy(value, start+len, value, start, count-end);
count -= len;
}
return this;
}
6 获取子字符串
从源代码中我们可以看到,StringBuilder最终返回的是String字符串对象,也就是不变得。 在构造中是可变的,一旦构造完成,就返回一个不可变的字符对象。
public String substring(int start, int end) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
throw new StringIndexOutOfBoundsException(end);
if (start > end)
throw new StringIndexOutOfBoundsException(end - start);
return new String(value, start, end - start);
}
撸了一会的源码,个人觉得吧,大多数方法都是先构造长度,长度构造完毕之后使用以下两个方法进行拼接就行了。不必深究这个源码,得到的东西还是很有限的。
System.arraycopy(value, start+len, value, start, count-end)
getChars(0, len, value, count);
String str=new String("tarena");
char[]ch={'a','b','c'};
public static void main(String args[]){
Example ex=new Example();
ex.change(ex.str,ex.ch);
System.out.println(ex.str+" and ");
System.out.println(ex.ch);
}
public void change(String str,char ch[]){
//引用类型变量,传递的是地址,属于引用传递。
System.out.println("数组:"+ch);
str="test ok";
ch[0]='g';
}
}
//输出的结果如下
数组:[C@39a054a5
tarena and
gbc
从上面代码能够得到知识点
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
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
}
以上是String源码中的属性,private final char value[]就是String用来存储字符的,用一个字符数组就可以存储一串字符串了。
其中最重要的就是,这个字符数组是final类型,也就是不可变类型。不可引用类型:一旦初始化,所引用的地址不会改变
public String() {
this.value = "".value;
}
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
从上面的构造函数可知,String字符串的结构如下图
图片来源
其它辅助知识
在jvm中,方法区有个常量池,常量池中存储包括已经初始化的字符常量。不可变的字符常量的好处就是,一旦初始化,存储在常量池中,那这个常量就是可复用的。也就是说,我们新建一个字符变量时,会先在常量池中搜索字符串常量是否存在,存在的话将引用连接到该字符常量,不需要新建对象。只有当不存在时才新建对象。
关于为什么可以对字符串进行拼接,替换。
其实本质上对字符串的操作,不是对其本身进行操作,而是新建一个String变量,对副本进行操作。
这个方法思路还是很简单,就是将两个字符串封装的字符数组每一位进行比较,如何每一位的字符都相同,那么这两个字符串就相等。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
这一类方法就是比较两个字符串的每一位,区别就是有的忽略大小写,如果有哪一位不同,就返回两个字符在顺序上的差值。
实现思想都是比较两个字符串的字符数组,因为只有字符数据才能一位一位的进行比较。再这之前肯定还有些额外的判断,比如两个字符串的长度之类的。
public boolean startsWith(String prefix, int toffset) {
char ta[] = value;
int to = toffset;
char pa[] = prefix.value;
int po = 0;
int pc = prefix.value.length;
// Note: toffset might be near -1>>>1.
if ((toffset < 0) || (toffset > value.length - pc)) {
return false;
}
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
这个方法就是从头开始比较了。
public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}
思路:
public boolean endsWith(String suffix) {
return startsWith(suffix, value.length - suffix.value.length);
}
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
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);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
最后调用了String的构造方法new了一个字符串。
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);
}
void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
关于复制,不得不说道这个native方法,这个方法直接调用了Java底层方法,我们也看不到源代码。所以这里只能看看怎么使用该方法,以后要赋值时都可以调用这个方法,这个方法会快很多。
我之前测试这个方法,好像只能转成数组,不能直接用String对象
测试代码
char [] aaa= new String("abcdef").toCharArray();
char [] bbb= new String("123456").toCharArray();
System.arraycopy(aaa, 0, bbb,0 , 6);
for (char c: aaa) {
System.out.println("aaa值:"+c);
}
for (char c: bbb) {
System.out.println("bbb值:"+c);
}
测试结果:
//aaa的值
aaa值:a
aaa值:b
aaa值:c
aaa值:d
aaa值:e
aaa值:f
//bbb的值
bbb值:a
bbb值:b
bbb值:c
bbb值:4
bbb值:5
bbb值:6
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;
}
public String trim() {
int len = value.length;
int st = 0;
char[] val = value; /* avoid getfield opcode */
while ((st < len) && (val[st] <= ' ')) {
st++;
}
while ((st < len) && (val[len - 1] <= ' ')) {
len--;
}
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
功能:将字符串转换成字符数组
这个方法运用的是很多的,当我们要对字符串的每一位进行处理时,就要用到这个方法来将字符串转换成字符数组。
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
功能:将对选哪个转换成字符串类型,我们经常用这个方法将其他类型的转换成字符串类型。这个方法重载了很多类型。
思路:其实都是调用toString()方法进行转换,我们可以直接调用toString()方法就行了。
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
String源码暂时就撸到这里了,其实感觉收回不是特别大,这个源码比较简单,就当学习人家编程习惯吧。
“” 是字符常量,是可以调用字符串的所有方法的,但是null不是字符常量,调用方法会报错,但是null可以赋值给所有引用对象。
int s1=1;
String string= String.valueOf(s1)