一丶概述

还记得那会的“Hello World”,第一个程序,输出的String,下面介绍String源码,颇有计算机二级考试习题的感觉。

二丶源码及案例

StringUtils import import java 远程 java string源代码_字符串

1.String是final类型的

在Java中,被 final 类型修饰的类不允许被其他类继承,被final修饰的变量赋值后不允许被修改。

什么是不可变类?

所谓不可变类,就是创建该类的实例后,该实例的属性是不可改变的,java提供的包装类和java.lang.String类都是不可变类。当创建它们的实例后,其实例的属性是不可改变的。

需要注意的是,对于如下代码

String s="abc";
s="def";

你可能会感到疑惑,不是说String是不可变类吗,这怎么可以改变呢,平常我也是这样用的啊。请注意,s是字符串对象的”abc”引用,即引用是可以变化的,跟对象实例的属性变化没有什么关系,这点请注意区分。

2.String类实现了Serializable, Comparable, CharSequence接口。

Comparable接口有compareTo(String s)方法,CharSequence接口有length(),charAt(int index),subSequence(int start,int end)方法,后面详解

3.成员变量

//用于存储字符串
private final char value[];
//缓存String的hash值
private int hash; // Default to 0

String类中包含一个不可变的char数组用来存放字符串,一个int型的变量hash用来存放计算后的哈希值。

4.构造函数

//不含参数的构造函数,一般没什么用,因为value是不可变量
public String() {
this.value = new char[0];
}
//参数为String类型
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
//参数为char数组,使用java.utils包中的Arrays类复制
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
//从bytes数组中的offset位置开始,将长度为length的字节,以charsetName格式编码,拷贝到value
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}
//调用public String(byte bytes[], int offset, int length, String charsetName)构造函数
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
//StringBuffer 和 StringBuider 也可以被当做构造 String 的参数。
//这两个构造方法是很少用到的,因为当我们有了 StringBuffer 或者 StringBuilfer 对象之后可以直接使用他们的 toString 方法来得到 String。
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

相关问题:

String两种不同的赋值方式

String str = new String("abc");
String str = "abc";

为什么String可以不用new就可以创建对象?这两种赋值方式有什么不同?

例:

public class Test {
public static void main(String[] args) {
String   a   =   "ok";   // 新建了一个String对象
String   b   =   "ok";   // 从缓冲池找
String   c   =   new   String("ok");   // 新建一个String对象
String   d   =   new   String("ok");   // 不从缓冲池找,新建一个
System.out.println(a==b);//将输出"true";因为两个变量指向同一个对象。
System.out.println(c==d);//将输出"flase";因为两个变量不指向同一个对象。虽然值相同,只有用c.equals(d)才能返回true.
String e = "a"+"b"+1;
String f = "ab1";
System.out.println(e == f);//将输出"true";因为编译器识别 “e = "a"+"b"+1”等同于“e = "ab1"”
String g = new String("ab1");
String h = "ab1";
System.out.println(g == h);将输出"flase";因为两个变量不指向同一个对象
}
}

5.常用方法

boolean equals(Object anObject)
boolean equals(Object anObject)
public boolean equals(Object anObject) {
//如果引用的是同一个对象,返回真
if (this == anObject) {
return true;
}
//如果不是String类型的数据,返回假
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
//如果char数组长度不相等,返回假
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;
}

equals方法经常用得到,它用来判断两个对象从实际意义上是否相等,String对象判断规则:

1. 内存地址相同,则为真。

2. 如果对象类型不是String类型,则为假。否则继续判断。

3. 如果对象长度不相等,则为假。否则继续判断。

4. 从后往前,判断String类中char数组value的单个字符是否相等,有不相等则为假。如果一直相等直到第一个数,则返回真。

int compareTo(String anotherString)
public int compareTo(String anotherString) {
//自身对象字符串长度len1
int len1 = value.length;
//被比较对象字符串长度len2
int len2 = anotherString.value.length;
//取两个字符串长度的最小值lim
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
//从value的第一个字符开始到最小长度lim处为止,如果字符不相等,返回自身(对象不相等处字符-被比较对象不相等字符)
while (k 
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
//如果前面都相等,则返回(自身长度-被比较对象长度)
return len1 - len2;
}

理解:

java中的compareto方法,返回参与比较的前后两个字符串的asc码的差值,看下面一组代码

String a="a",b="b";
System.out.println(a.compareto.b);

则输出-1;

若a="a",b="a"则输出0;

若a="b",b="a"则输出1;

单个字符这样比较,若字符串比较长呢??

若a="ab",b="b",则输出-1;

若a="abcdef",b="b"则输出-1;

也就是说,如果两个字符串首字母不同,则该方法返回首字母的asc码的差值;

如果首字母相同呢??

若a="ab",b="a",输出1;

若a="abcdef",b="a"输出5;

若a="abcdef",b="abc"输出3;

若a="abcdef",b="ace"输出-1;

即参与比较的两个字符串如果首字符相同,则比较下一个字符,直到有不同的为止,返回该不同的字符的asc码差值,如果两个字符串不一样长,可以参与比较的字符又完全一样,则返回两个字符串的长度差值

int hashCode()
int hashCode()
public int hashCode() {
int h = hash;
//如果hash没有被计算过,并且字符串不为空,则进行hashCode计算
if (h == 0 && value.length > 0) {
char val[] = value;
//计算过程
//s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
for (int i = 0; i 
h = 31 * h + val[i];
}
//hash赋值
hash = h;
}
return h;
}

String类重写了hashCode方法,Object中的hashCode方法是一个Native调用。String类的hash采用多项式计算得来,我们完全可以通过不相同的字符串得出同样的hash,所以两个String对象的hashCode相同,并不代表两个String是一样的。

boolean startsWith(String prefix,int toffset)
boolean startsWith(String prefix,int toffset)
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.
//如果起始地址小于0或者(起始地址+所比较对象长度)大于自身对象长度,返回假
if ((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);
}

起始比较和末尾比较都是比较经常用得到的方法,例如在判断一个字符串是不是http协议的,或者初步判断一个文件是不是mp3文件,都可以采用这个方法进行比较

String concat(String str)
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);
}

concat方法也是经常用的方法之一,它先判断被添加字符串是否为空来决定要不要创建新的对象。

String replace(char oldChar,char newChar)
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 
if (val[i] == oldChar) {
break;
}
}
//从那个位置开始,直到末尾,用新值代替出现的旧值
if (i 
char buf[] = new char[len];
for (int j = 0; j 
buf[j] = val[j];
}
while (i 
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}

这个方法也有讨巧的地方,例如最开始先找出旧值出现的位置,这样节省了一部分对比的时间。replace(String oldStr,String newStr)方法通过正则表达式来判断。

String trim()
public String trim() {
int len = value.length;
int st = 0;
char[] val = value;    /* avoid getfield opcode */
//找到字符串前段没有空格的位置
while ((st 
st++;
}
//找到字符串末尾没有空格的位置
while ((st 
len--;
}
//如果前后都没有出现空格,返回字符串本身,左右有空格则去掉空格
return ((st > 0) || (len 
}
ASCII值比较,“”为32,字母都大于64,可参考ASCII值表
String substring()
// 截取字符串
public String substring(int beginIndex) {
if (beginIndex 
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex; // 获取截取长度
if (subLen 
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
// 截取字符串
public String substring(int beginIndex, int endIndex) {
if (beginIndex 
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) { // 如果末尾下标>字符数组长度
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex; // 获取截取长度
if (subLen 
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}

如:

"hamburger".substring(4) returns "urger"
"hamburger".substring(4, 8) returns "urge"
"smiles".substring(1, 5) returns "mile"
indexOf():
//该字符跟数组中的每个字符从左往右比较
//lastIndexOf一样只不过是从右往左比较
public int indexOf(int ch, int fromIndex) {
final int max = value.length;
if (fromIndex 
fromIndex = 0;
} else if (fromIndex >= max) {
// Note: fromIndex might be near -1>>>1.
return -1;
}
if (ch 
// handle most cases here (ch is a BMP code point or a
// negative value (invalid code point))
final char[] value = this.value;
for (int i = fromIndex; i 
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
return indexOfSupplementary(ch, fromIndex);
}
}
//indexOf最终是调用下面的第二个static方法来进行求解的
//求解步骤大概是:
//首先搜索到第一个字符所在的位置,之后逐个比较;
//这里并没有使用kmp算法因此是一个可以优化的地方
public int indexOf(String str, int fromIndex) {
return indexOf(value, 0, value.length,
str.value, 0, str.value.length, fromIndex);
}
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
if (fromIndex 
fromIndex = 0;
}
if (targetCount == 0) {
return fromIndex;
}
char first = target[targetOffset];
int max = sourceOffset + (sourceCount - targetCount);
for (int i = sourceOffset + fromIndex; i <= max; i++) {
/* Look for first character. */
if (source[i] != first) {
while (++i <= max && source[i] != first);
}
/* Found first character, now look at the rest of v2 */
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
for (int k = targetOffset + 1; j 
== target[k]; j++, k++);
if (j == end) {
/* Found whole string. */
return i - sourceOffset;
}
}
}
return -1;
}
split
public String[] split(String regex, int limit) {
char ch = 0;
//1. 如果regex只有一位,且不为列出的特殊字符;
//2.如regex有两位,第一位为转义字符且第二位不是数字或字母,“|”表示或,即只要ch小于0或者大于9任一成立,小于a或者大于z任一成立,小于A或大于Z任一成立
//3.第三个是和编码有关,就是不属于utf-16之间的字符
if (((regex.value.length == 1 &&
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
(regex.length() == 2 &&
regex.charAt(0) == '\\' &&
(((ch = regex.charAt(1))-'0')|('9'-ch)) 
((ch-'a')|('z'-ch)) 
((ch-'A')|('Z'-ch)) 
(ch 
ch > Character.MAX_LOW_SURROGATE))
{
int off = 0;
int next = 0;
boolean limited = limit > 0;
ArrayList list = new ArrayList<>();
//如果这个字符串中包含了用于分割的ch,则进行下一步操作
while ((next = indexOf(ch, off)) != -1) {
if (!limited || list.size() 
list.add(substring(off, next));
off = next + 1;
} else {
list.add(substring(off, value.length));
off = value.length;
break;
}
}
// If no match was found, return this
if (off == 0)
return new String[]{this};
// Add remaining segment
if (!limited || list.size() 
list.add(substring(off, value.length));
// Construct result
int resultSize = list.size();
if (limit == 0)
while (resultSize > 0 && list.get(resultSize - 1).length() == 0)
resultSize--;
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result);
}
return Pattern.compile(regex).split(this, limit);
}

详解见:

String intern()
public native String intern();

intern方法是Native调用,它的作用是在方法区中的常量池里通过equals方法寻找等值的对象,如果没有找到则在常量池中开辟一片空间存放字符串并返回该对应String的引用,否则直接返回常量池中已存在String对象的引用

例:

String a = new String("ab1");
String b = new String("ab1").intern();
a == b就为true
int hash32()
int hash32()
private transient int hash32 = 0;
int hash32() {
int h = hash32;
if (0 == h) {
// harmless data race on hash32 here.
h = sun.misc.Hashing.murmur3_32(HASHING_SEED, value, 0, value.length);
// ensure result is not zero to avoid recalcing
h = (0 != h) ? h : 1;
hash32 = h;
}
return h;
}

在JDK1.7中,Hash相关集合类在String类作key的情况下,不再使用hashCode方式离散数据,而是采用hash32方法。这个方法默认使用系统当前时间,String类地址,System类地址等作为因子计算得到hash种子,通过hash种子在经过hash得到32位的int型数值。

其他方法:

public int length() {
return value.length;
}
public String toString() {
return this;
}
public boolean isEmpty() {
return value.length == 0;
}
public char charAt(int index) {
if ((index = value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}