写在前面

String 算是 Java 源码中先要学习的,今天就从源码的角度来重新认识一下

1.存储结构

看主流的 JDK 版本 1.8 ,String 内部实际存储结构为 char 数组,源码如下:

1
2
3
4
5
6
7
8
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
//其他内容......

2.常用方法

2.1.构造方法

其中 StringBuffer 和 StringBuilder 为参数的构造函数用的比较少,但也要知道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* String 为参数的构造方法
* @param original
* A {@code String}
*/
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}

/**
* char[] 为参数构造方法
* @param value
* The initial value of the string
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}

/**
* StringBuffer 为参数的构造方法
* @param buffer
* A {@code StringBuffer}
*/
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}

/**
* StringBuilder 为参数的构造方法
* @param builder
* A {@code StringBuilder}
* @since 1.5
*/
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

2.2.equals()

String 中 equals() 是比较两个字符串的值是否相等,== 才是比较字符串的引用是否相等,equals() 重写了父类 Object 方法,传参也为 Object 类型,方法中会通过 instanceof 判断,是 String 类型才进行下一步。

这里提一下,Object 父类中 equals() 和 == 对于引用类型的作用是一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @param anObject
* The object to compare this {@code String} against
*
* @return {@code true} if the given object represents a {@code String}
* equivalent to this string, {@code false} otherwise
*
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
public boolean equals(Object anObject) {
if (this == anObject) {// 对象引用相同直接返回 true
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])//转化为字符数组,对比每个字符,有一个不相同就是 fasle
return false;
i++;
}
return true;
}
}
return false;
}

有一个和 equels 相似的方法 equalsIgnoreCase(String),忽略大小写对比,传参为 String 类型

2.3.compareTo()

compareTo() 和 equels() 处理方式类似,都是字符对比,不同的是,equels() 比较两字符串相同返回 true,不相同返回 false;compareTo() 比较两字符串相同返回 0,不相同返回 其他 int 类型数值。并且 compareTo() 只能接收 String 类型。

还有一个和 compareTo() 比较类似的方法 compareToIgnoreCase(),用于忽略大小写后比较两个字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* @param anotherString the {@code String} to be compared.
* @return the value {@code 0} if the argument string is equal to
* this string; a value less than {@code 0} if this string
* is lexicographically less than the string argument; and a
* value greater than {@code 0} if this string is
* lexicographically greater than the string argument.
*/
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;
}

2.4.其他重要方法

  • indexOf():查询字符串首次出现的下标位置
  • lastIndexOf():查询字符串最后出现的下标位置
  • contains():查询字符串中是否包含另一个字符串
  • toLowerCase():把字符串全部转换成小写
  • toUpperCase():把字符串全部转换成大写
  • length():查询字符串的长度
  • trim():去掉字符串首尾空格
  • replace():替换字符串中的某些字符
  • split():把字符串分割并返回字符串数组
  • join():把字符串数组转为字符串

3.常遇问题

3.1.String 和 StringBuilder、StringBuffer 的区别

String 是不可变的,在字符串拼接的时候使用 String 会很耗性能,因此有了 StringBuilder 和 StringBuffer,它们有 2 方法 append 和 insert 可以实现字符串拼接,唯一不同的是 StringBuffer 使用 synchronized 来保证线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//StringBuffer 截取片段,具体可以看 StringBuffer 类源码

@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}

@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
//其他......


/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public synchronized StringBuffer insert(int offset, Object obj) {
toStringCache = null;
super.insert(offset, String.valueOf(obj));
return this;
}

/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public synchronized StringBuffer insert(int offset, String str) {
toStringCache = null;
super.insert(offset, str);
return this;
}
//其他......
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//StringBuilder截取片段,具体可以看 StringBuilder类源码

@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}

@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
//其他......


/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder insert(int offset, Object obj) {
super.insert(offset, obj);
return this;
}

/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder insert(int offset, String str) {
super.insert(offset, str);
return this;
}
//其他......

StringBuffer 保证线程安全,所以性能不是很高,JDK 1.5 就有了 StringBuilder

3.2.String 为什么用 final 修饰

使用 final 修饰的第一个好处是安全;第二个好处是高效,例如

1
2
String s1 = "java";
String s2 = "java";

只有字符串是不可变时,我们才能实现字符串常量池,字符串常量池可以为我们缓存字符串,提高程序的运行效率,如下图所示:
在这里插入图片描述

3.3.JVM 中存储

String 常用的 2 种创建方式,有 String a1 = “java” 和 String a2 = new Strring(“java”),但他们在内存中的存放方式不同,JDK1.8 中创建啊变量 a1,会先从常量池中找字符串 “java”,如果有直接返回,如果没有则先在常量池中创建该字符串再返回,而变量 a2会直接在堆内存上创建,a2 调用方法 intern() 会把字符串保存到常量池,例如

1
2
3
4
5
6
String a1 = "java";
String a2 = new Strring("java");
String a3 = "a2.intern();

System.out.println(a1 == a2); // false
System.out.println(a1 == a3); // true

JVM 存储位置如图
在这里插入图片描述
PS:JDK 1.7 之后把永生代换成的元空间,把字符串常量池从方法区移到了 Java 堆上。

结束……

如果有哪些不对的地方烦请指认,先行感谢