public final class String
implments java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
// 用来存储字符串的值
private final char value[];
/** Cache the hash code for the string */
// 用来缓存hash code 调用hashCode方法时会首先对hash的值进行判断,若是已经存在值, 因为String是不可变的,直接返回hash便可,不用从新对该String对象的hash code的进行计算
// 默认值为0
private int hash; // Default to 0
// 其余属性和方法...
}
从源码String
源码能够看出,String
是final
修饰符修饰的类,表示String
不能被继承。String
底层使用final
修饰的char[]
数组来储存字符串的值。java
构造方法数组
String
有多种构造构造,能够传入char[]
、String
、StringBuffer
、StringBuilder
等属性构造String
对象。缓存
因为String
为不可变类型,当调用String
中方法对String
修改操做时,都会调用相应的构造方法生成一个新的对象并返回,原String
对象并不会产生改变。下面以replace()
方法为例:安全
public String replace(char oldChar, char newChar) {
// 只有old != new才进行处理 不然直接返回
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
// 经过while循环找到第一个oldChar的位置
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
// 建立一个新的char数组 用来存新的String对象的值
char buf[] = new char[len];
// 将第一个oldChar前的全部char都存入buf数组中
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
// 将剩余val数组中等于oldChar的值修改成newChar后存入buf中
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
// 根据buf数组生成新的String对象
return new String(buf, true);
}
}
return this;
}
equals方法
并发
//Object中的equals方法
public boolean equals(Object obj) {
return (this == obj);
}
//String重写Object的equals方法
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;
}
能够看出Object
中的equals
方法直接使用“==
”判断两个对象是否相等,"==
"对基本类型进行判断是,比较的是基本类型的值,而对引用类型的对象进行判断时,比较的是两个对象的引用的值是否相等,也就是说"==
"判断两个对象与对象的内容无关,只与对象的地址有关,当且仅当两个对象的地址一致时(为同一个对象)才会返回true。如:app
String s1 = new String("string");
String s2 = new String("string");
s1 == s2; // false "=="只判断地址 s1 s2是单独的两个对象
String
重写了equals
方法,从源码能够看出,equals方法传入的参数类型为Object
,调用equals
时,首先会使用"==
''比较this
和待比较对象anObject
的地址是否相等,若相等则表示这个两个对象为同一个对象,直接返回true
便可。若是不相等,使用instanceof
判断anObject
是否为String
类型,若不是则返回false
,不然进行下一步操做,先判断两个String
对象的value
数组的长度是否相等,再循环比较数组中的每一个元素是否相等。String
的equals
比较的是两个String
对象的内容——也就是value
数组是否相等。性能
String s1 = new String("string");
String s2 = new String("string");
s1.equals(s2); // true s1 s2是单独的两个对象 可是他们的内容都是 "string"
除equals
方法外,因为String
实现了comparable
接口,也能够经过comparable
的compareTo
方法判断两个字符串是否相等ui
compareTo
this
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;
}
从源码能够看出,compareTo
方法的参数类型为String
与equals
的Object
不一样,返回值为int
与equals
的boolean
也不同,compareTo
方法首先挨个比较两个字符串中较短字符串的全部元素与较长字符串中对应位置的元素的大小,当对应元素不相等时返回差值,当较短字符串都比较完毕后返回两个字符串的长度的差值。当返回值为0时代表这两个元素相等。返回值<0时,表示按字典顺序this
在anotherString
的前面,反之亦然。atom
因为String
实现Comparable
接口,String
列表或数组能够经过Collections.sort
或者Arrays.sort
方法进行自动排序;同时String
对象也能够做为有序映射(TreeMap
)中的键或者有序集合(TreeSet
)中的元素,无需指定比较器。
其余
equals
和compareTo
都用对应的xxxIgnoreCase
方法,equalsIngoreCase
与equals
相似,先比较地址再比较长度最后经过regionMatches
比较两个字符串忽略小写以后的内容是否相等
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}
compareToIgnoreCase
经过比较器静态内部类CaseInsensitiveComparator
的compare
方法来实现,比较逻辑与compareTo
基本相同只是多了一些大小写转换后判断的操做
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;
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;
}
/** Replaces the de-serialized object. */
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}
其余方法:
indexOf()
lastIndexOf()
contains()
trim()
split()
...
final
修饰String
类,String
类不可继承。
final
修饰char
类型的value
数组,初始化事后,value
指向的数组不能修改,而且每次对对象进行修改时都会经过构造器建立一个新的对象,保证了String
的不可变。
设计成不可变的缘由
安全
高效
使用JVM字符串常量池来缓存字符串,只有当字符串为不可变时,才能实现字符串常量池,因为String类型使用很频繁,字符串常量池的存在能有效提升程序的运行效率。
(JDK1.7以后永久代换成了元空间,将字符串常量池从方法区移到了堆上)
String
的常见的建立方式有两种:
经过字面量的方式建立 编译时决定
String s1 = "string";
String s2 = "string";
s1 == s2 // true 字面量建立的String s1 s2都指向字符串常量池中的"string"
经过new String的方式建立 运行时决定
String s3 = new String("string");
String s4 = new String("string");
s3 == s4 // false s3 s4 指向的是 堆上的内容为"string"的对象,此时有两个这种对象
字面量方式建立首先会查找字符串常量池中是否已经存在该字符串,有则直接指向该字符串,不然先在常量池中建立该字符串,而后将引用指向建立的字符串;而经过new建立String,必定会在堆上建立一个字符串对象,而后判断常量池是否已经存在该字符串的值,若是不存在则会在常量池中建立该字符串,而后将引用的值指向该字符串(s3
和 s4
指向堆中对象的地址 而堆中保存字符串常量池中“string“的地址)。
弄清字符串是在编译时 仍是 运行时 进入常量池
String s1 = "Hello World";
String s2 = "Hello ";
String s3 = "World";
s1 == "Hello " + "World"; //true 字面量相加 直接在编译期彻底肯定 而且放入字符串常量池中
s1 == s2 + s3; // false 引用相加 不能在编译期肯定 s2 + s3的值
final String s4 = "Hello ";
final String s5 = "World";
s1 == s4 + s5; // true 在编译器可以肯定final修饰的s4和s5指向的值
intern
public native String intern();
intern
是一个native
方法,当使用intern方法时,首先会检查字符串常量池中,是否已经存在该字符串,若是已存在,直接返回该字符串,不然建立以后再返回。
String s1 = "String";
String s2 = new String("String");
s1 == s2; // false
s1 == s2.intern() // true
String
、StringBuffer
StringBuilder
因为String
为不可变类型,每次对String进行修改时都会产生新的String
对象,在拼接字符串的时候可能会出现不少无用的String
对象,性能会很低,此时就须要StringBuffer
来对字符串进行拼接。StringBuffer
和StringBuilder
继承自AbstractStringBuilder
,提供了append
、insert
等方法对字符串进行拼接和修改。
StringBuffer
为线程安全,使用sychronized
对方法加锁实现线程安全,相应的效率也会变低,在非并发的条件下可使用StringBuilder
提升效率。
以上为我的总结的Java String相关知识,若有不对之处,敬请批评指正!