String类的特点
- 字符串不变:字符串的值在创建后不能被更改。
String s = "abc";
s += "d";
System.out.println(s); // "abcd"
// 内存中有"abc","abcd"两个对象,s从指向"abc",改变指向,指向了"abcd"。
- 因为String对象是不可变的,所以它们可以被共享。
- "abc" 等效于 char[] data={ 'a' , 'b' , 'c' } 。
String str = "abc";
等价于:
char data[] = {'a', 'b', 'c'};
String str = new String(data);
// String底层是靠字符数组实现的。
构造方法
String类的构造方法很多

首先是无参的构造方法,String源码中声明了一个final的byte数组,这意味着这个数组一旦被初始化就无法改变值,这也解释了String的第一个特性(不可变),然后看第一个构造方法,会把当前的String对象赋值为空(非null),同时由于字符串是不可变的,因此不需要使用此构造函数。
private final byte[] value;
public String() {
this.value = "".value;
this.coder = "".coder;
}
然后是带参的,在源码的注释中说除非需要参数的显式副本,否则这个构造方法也是用不到的O__O "…
public String(String original) {
this.value = original.value;
this.coder = original.coder;
this.hash = original.hash;
}
这个就是传入一个char数组为将要创建的对象中的value数组属性赋值
public String(char value[]) {
this(value, 0, value.length, null);
}
然后我发现这个的源码在jdk11版本中也跟网上看到的(不知名版本源码)不一样了,这个是调用了另一个构造方法:
String(char[] value, int off, int len, Void sig) {
if (len == 0) {
this.value = "".value;
this.coder = "".coder;
return;
}
if (COMPACT_STRINGS) {
byte[] val = StringUTF16.compress(value, off, len);
if (val != null) {
this.value = val;
this.coder = LATIN1;
return;
}
}
this.coder = UTF16;
this.value = StringUTF16.toBytes(value, off, len);
}
原来的是直接调用Arrays.copyof()方法进行复制
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
这个指定起始值的当然也是
public String(char value[], int offset, int count) {
this(value, offset, count, rangeCheck(value, offset, count));
}
然后java指定上下界的左闭右开 [a,b) 规律应该是与右界作为 i<b 有关吧(乱猜)

再来看看字节数组的
public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBoundsOffCount(offset, length, bytes.length);
StringCoding.Result ret =
StringCoding.decode(charset, bytes, offset, length);
this.value = ret.value;
this.coder = ret.coder;
}
这种构造方法是通过操作字节数组来实现对字符串对象的创建,这些操作就会涉及到编码的问题,像这个方法首先就是确保 charset 不为空,然后调用checkBoundsOffCount()方法判断offset、count不为0以及offset+count<length

再调用decode方法用于将字节数组按照指定的编码方式解析成char数组
常用方法
用于判断的方法
首先是很常用的equals方法,可以看到第一个判断就用了==,是判断两个对象是否指向同一个内存空间地址如果相同的话内容自然也是一样的。
/**
* 将此字符串与指定的对象进行比较。
* 当且仅当参数不是null且是String对象表示与此对象相同的字符序列时,结果为true 。
* @param anObject
* @return
*/
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
然后是忽略大小写的判等
/**
* 将此String与另一个String比较,忽略了大小写。
* 如果两个字符串具有相同的长度并且两个字符串中的相应字符忽略大小写后相等,则认为它们是相等。
* @param anotherString
* @return
*/
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.length() == length())
&& regionMatches(true, 0, anotherString, 0, length());
}
注:2个字符串使用==比较运算符,比较的是地址值,如果使用的是equals方法,比较的是字符串内容是否相等
startsWith方法用于判断是否当前的字符串对象是以指定的子串开头。prefix参数指定了这个字串,toffset参数指定了要从原字符串的哪里开始查找
public boolean startsWith(String prefix, int toffset) {
// Note: toffset might be near -1>>>1.
if (toffset < 0 || toffset > length() - prefix.length()) {
return false;
}
byte ta[] = value;
byte pa[] = prefix.value;
int po = 0;
int pc = pa.length;
if (coder() == prefix.coder()) {
int to = isLatin1() ? toffset : toffset << 1;
while (po < pc) {
if (ta[to++] != pa[po++]) {
return false;
}
}
} else {
if (isLatin1()) { // && pcoder == UTF16
return false;
}
// coder == UTF16 && pcoder == LATIN1)
while (po < pc) {
if (StringUTF16.getChar(ta, toffset++) != (pa[po++] & 0xff)) {
return false;
}
}
}
return true;
}
用于获取的方法
几个获取属性的,11版本的加了很多判断还有断言逻辑比较复杂这个容易懂些
//返回字符串的长度
public int length() {
return value.length;
}
//判断字符串是否为空
public boolean isEmpty() {
return value.length == 0;
}
//获取字符串中指定位置的单个字符
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
将指定的字符串连接到该字符串的末尾
public String concat(String str) {
int olen = str.length();
if (olen == 0) {
return this;
}
if (coder() == str.coder()) {
byte[] val = this.value;
byte[] oval = str.value;
int len = val.length + oval.length;
byte[] buf = Arrays.copyOf(val, len);
System.arraycopy(oval, 0, buf, val.length, oval.length);
return new String(buf, coder);
}
int len = length();
byte[] buf = StringUTF16.newBytesFor(len + olen);
getBytes(buf, 0, UTF16);
str.getBytes(buf, len, UTF16);
return new String(buf, UTF16);
}
查找指定子字符串第一次出现在该字符串内的索引。
/**
*
* @param str
* @return
*/
public int indexOf(String str) {
if (coder() == str.coder()) {
return isLatin1() ? StringLatin1.indexOf(value, str.value)
: StringUTF16.indexOf(value, str.value);
}
if (coder() == LATIN1) { // str.coder == UTF16
return -1;
}
return StringUTF16.indexOfLatin1(value, str.value);
}
这个的话也是经过一系列复杂的判断然后跳转到一个重载的 indexOf 方法,这个还是比较好理解的
public static int indexOf(byte[] value, int valueCount, byte[] str, int strCount, int fromIndex) {
byte first = str[0];
int max = (valueCount - strCount);
for (int i = fromIndex; i <= max; i++) {
// Look for first character.
if (value[i] != first) {
while (++i <= max && value[i] != first);
}
// Found first character, now look at the rest of value
if (i <= max) {
int j = i + 1;
int end = j + strCount - 1;
for (int k = 1; j < end && value[j] == str[k]; j++, k++);
if (j == end) {
// Found whole string.
return i;
}
}
}
return -1;
}
从beginIndex到endIndex截取字符串。含beginIndex,不含endIndex。
/**
* @param beginIndex
* @param endIndex
* @return
*/
public String substring(int beginIndex, int endIndex) {
int length = length();
checkBoundsBeginEnd(beginIndex, endIndex, length);
int subLen = endIndex - beginIndex;
if (beginIndex == 0 && endIndex == length) {
return this;
}
return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
: StringUTF16.newString(value, beginIndex, subLen);
}
截取就是通过 Arrays.copyOfRange 数组复制产生一个新的字符串
public static String newString(byte[] val, int index, int len) {
return new String(Arrays.copyOfRange(val, index, index + len),
LATIN1);
}
用于转换的方法
/**
* 将此字符串转换为新的字符数组
* @return
*/
public char[] toCharArray ()
/**
* 将与target匹配的字符串使用replacement字符串替换
* @param target
* @param replacement
* @return
*/
public String replace (CharSequence target, CharSequence replacement)
用于分隔的方法
/**
* 将此字符串按照给定的regex(规则)拆分为字符串数组
* @param regex
* @return
*/
public String[] split(String regex)