首先向搞懂常量池的知识点:触摸Java常量池 常量池技术
java.lang包的最后一篇基础篇。搞完这篇就开始集合框架和并发包等内容。Sting、StirngBuilder、StringBuffer的内容很早之前写过 String、StringBuffer和StringBuilder的区别和应用场景 ,但写的太简单了。这次再重新梳理一下这部分内容,留作以后复习。
在 java 语言中, 用来处理字符串的的类常用的有 3 个: String、StringBuffer、StringBuilder。
它们的异同点:
1) 都是 final 类, 都不允许被继承;
2) String 长度是不可变的(内部实现是:private final char value[];), StringBuffer、StringBuilder 长度是可变的(内部也是利用char[]存储实现的);
3) StringBuffer 是线程安全的(通过在方法前面加了synchronized关键字实现的线程同步), StringBuilder 不是线程安全的。
String 字符串是常量, 它们的值在创建之后不能够被更改,它在jdk1.7之前是存储在方法区的常量池中的,jdk1.8实现了去永久代,原先的永久代信息放入了元数据区。
String str1 = "abc";
String str2 = new String("cde");
上边的两种创建方式,其中第1种是在常量池存储了字符对象char[]a,b,c,然后str1指向了此常量。第二种方式相当于创建了两个string对象,一个是cde本身,另一种是以new关键字在堆中开辟的内存空间。
当利用连接符+来改变字符串常量时,实际上是jdk1.5后jvm利用了StirngBuilder来实现的。当进行下边的代码时:
String str1 = "abc";
str1 += "cde";
jvm是采用下图中的方式实现的:
这实际上式jvm对+操作符的重载优化,但是这也有效率问题,当有多个连接符+时,会创建多个StringBuilder。所以对于可变的字符串,一般要采用StringBuilder和StringBuffer来实现。
String方法
string实现的方法有:implements java.io.Serializable, Comparable, CharSequence ,表明它是可序列化,以及重写了compareTo方法。
首先来看它的构造方法:
可以看到,我们可以利用char[],byte[]等来创建字符串对象。
由于String对象的内部是利用char数组来存储的,所以很多方法如length(),isEmpty(),charAt(),equals()等方法都是通过操作char数组来实现的,如:
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
startsWith(prefix) 测试字符串是否是以指定的前缀 prefix 开始, endsWith(suffix) 测试字符串是否是以指定的后缀 suffix 结束
String重写了equals方法和hashCode方法,equals方法比较的是字符对象是否一一相等,其中hashCode方法为:
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;
}
它是针对每一个字符来操作的。为什么选31,可以查看此解释为什么string类的hashCode方法选31作为31位数乘因子 。
indexOf 用于返回指定的子字符串在主字符串中第一次出现处的索引值; lastIndexOf 用于返回指定的子字符串在主字符串中最后一次出现处的索引值。这里并不是采用的经典的字符串匹配算法KMP算法,而是采用的暴力匹配方法。其原因可以查看为什么java String.contains 没有使用类似KMP字符串匹配算法进行优化? 。
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);
}
string还提供了很多替换和匹配算法:
它是利用正则来实现的,比如:
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
另外,string类还提供了例如:转换大小写,得到char[]数组,getByte方法,valueOf方法。trim()方法,intern方法(此方法与jvm知识有关,后边要看一下)等。字符串操作时我们程序中应用非常多的,jdk为我们做了较好的封装。
因为String是不可变的,所以提供了StringBuilder和StringBuffer这两种可变得字符串操作类。两者都实现了AbstractStringBuilder,其内部依然是利用char[]来实现的,不过此char数组是可变的。StringBuilder 与 StringBuffer 支持的所有操作基本上是一致的, 不同的是, StringBuilder 不需要执行同步。同步操作意味着要耗费系统的一些额外的开销, 或时间, 或空间, 或资源等, 甚至可能会造成死锁。从理论上来讲, StringBuilder 的速度要更快一些。下边以StingBuilder方法为例:
其构造方法为:
//默认char[]容量为16
public StringBuilder() {
super(16);
}
//指定容量大小
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
append方法:
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
//扩容 满足的最小容量count + len
ensureCapacityInternal(count + len);
//完成添加
str.getChars(0, len, value, count);
count += len;
return this;
}
/**
* For positive values of {@code minimumCapacity}, this method
* behaves like {@code ensureCapacity}, however it is never
* synchronized.
* If {@code minimumCapacity} is non positive due to numeric
* overflow, this method throws {@code OutOfMemoryError}.
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
//返回新容量的char[]
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;
}
//返回新的char[]
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
* @param srcBegin index of the first character in the string
* to copy.
* @param srcEnd index after the last character in the string
* to copy.
* @param dst the destination array.
* @param dstBegin the start offset in the destination array.
* @exception IndexOutOfBoundsException If any of the following
* is true:
* - {@code srcBegin} is negative.
*
- {@code srcBegin} is greater than {@code srcEnd}
*
- {@code srcEnd} is greater than the length of this
* string
*
- {@code dstBegin} is negative
*
- {@code dstBegin+(srcEnd-srcBegin)} is larger than
* {@code dst.length}
*/
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
扩容的方法最终是由newCapacity()实现的,在这个方法中首先把容量扩大为原来的容量加2,如果此时仍小于指定的容量,那么就把新的容量设为minimumCapacity(原来的长度+添加的字符串长度)。然后判断是否溢出,如果溢出了,把容量设为Integer.MAX_VALUE。最后把value值进行拷贝,这显然是耗时操作。然后采用的字符拷贝操作。
append()是最常用的方法,它有很多形式的重载。上面是其中一种,用于追加字符串。如果str是null,则会调用appendNull()方法。这个方法其实是追加了’n’、’u’、’l’、’l’这几个字符。如果不是null,则首先扩容,然后调用String的getChars()方法将str追加到value末尾。最后返回对象本身,所以append()可以连续调用。
复制是最后调用的System的native方法实现的:
* @param src the source array.
* @param srcPos starting position in the source array.
* @param dest the destination array.
* @param destPos starting position in the destination data.
* @param length the number of array elements to be copied.
* @exception IndexOutOfBoundsException if copying would cause
* access of data outside array bounds.
* @exception ArrayStoreException if an element in the src
* array could not be stored into the dest
array
* because of a type mismatch.
* @exception NullPointerException if either src
or
* dest
is null
.
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
字符串翻转:
public AbstractStringBuilder reverse() {
boolean hasSurrogates = false;
int n = count - 1;
for (int j = (n-1) >> 1; j >= 0; j--) {
int k = n - j;
char cj = value[j];
char ck = value[k];
value[j] = ck;
value[k] = cj;
if (Character.isSurrogate(cj) ||
Character.isSurrogate(ck)) {
hasSurrogates = true;
}
}
if (hasSurrogates) {
reverseAllValidSurrogatePairs();
}
return this;
}
toString()方法返回一个字符串:
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}