首先你要有spring.java的源码。耐心读完收获很大。
1、看class头
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
final,从安全角度来说,通过final修饰后的String类是不能被其他类继承的,在最大程度上的保护了该类,从效率上来说,提高了该类的效率,因为final修饰后会比没有用final修饰的快。
实现Serializable接口,可序列化
实现Comparable接口,可比较
实现CharSequence接口,也就是字符序列,String是通过字符数组实现的
2、成员变量
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
value为char类型数组,而且用final表示,一旦初始化不可改变,当然它是地址。private说明外部不能访问。被final修饰,在编译期间就被确定下来,并且在运行期间不能再被修改了。因此,每次我们每次进行字符串修改、拼接的时候,并不能直接修改当前String对象的值,只能重新创建一个新的对象。
hash为String自己定义规则的hashCode,默认值为零。private说明外部不能访问。String自己的定义hashCode规则下面会说。
serialVersionUID为序列化号,为保证序列化和反序列化时候,保证数据的正确性。不懂得可以搜索一下他的作用。
3、构造函数(@Deprecated进行注解的我们不看,因为放弃使用了)
先浏览下13种:
(1)String():默认构造函数
(2)String(String s):一种,可以理解为字符串转为字符串
(3)String(char[]):两种,char数组转换为字符串
(4)String(int[]):一种,ASCII数组转换为字符串
(5)String(byte[]):六种,byte数组以默认编码和指定编码转换为字符串
(6)String(StringBuffer):一种,StringBuffer转换为字符串
(7)String(StringBuilder):一种,StringBuilder转换为字符串
(1)String()
public String() {
this.value = new char[0];
}
生成一个空的字符序列,且hash为默认值0。
(2)String(String original):
拷贝字符串
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
这个新创建的String相当于原始的参数String original的拷贝。这样会导致hashCode一样,但是他们不==。
对于下面的知识,需要知道编译期和运行时,需要看下,地址:
对于该创建引发的四个问题的思考:
a、String s = new String("xyz");创建了几个String 对象?
两个或一个,”xyz”对应一个对象,这个对象放在字符串常量缓冲区,常量”xyz”不管出现多少遍,都是缓冲区中的那一个,类似与缓存Byte数值的-128~127数值。New String每写一遍,就创建一个新的对象,那个常量”xyz”对象的内容来创建出一个新String对象。如果以前就用过’xyz’,这句代表就不会创建”xyz”自己了,直接从缓冲区拿。
所以,凡是通过构造器创建的对象都会进行内存分配,所以他就不会指向缓存池中已有的对象而指向新的对象,这样就会造成缓存池中存在多个值相同的字符串对象,浪费资源。所以一般要直接为字符串指定值即可。
这里需要介绍一下缓存池:为了节省内存,提高资源的复用,jvm引入了常量池这个概念,它属于方法区的一部分,作用之一就是存放编译期间生产的各种字面量和符号引用。方法区的垃圾回收行为是比较少出现的,该区中的对象基本不会被回收,可以理解成是永久存在的。
b、String s="a"+"b"+"c"+"d";创建了几个String对象?
一个,因为Javac在做编译时已经对这些字符串进行了合并操作,预先做了优化处理。
c、String name = "ab"; name = name + "c";两条语句总共创建了多少个字符串对象?
创建了两个对象,这两个对象都会放到缓存池中,只是name的引用由"ab"改变为"abc"了。我们在这样用的时候,还需要考虑其他问题,如这个程序会造成内在泄漏,因为缓存池中的在缓存池中的字符串是不会被垃圾回收机制回收的,基本都是常驻内存,所以过多使用String类,可能会出现内存溢出。
d、字符串比较
String s1 = "a";
String s2 = s1 + "b";
String s3 = "ab";
System.out.println(s2 == s3);//false
可以看到s2与s3的引用并不相同。由于s2字符串在编译时并不能进行确定,所以首先进入缓存池中的有s1和s3,随后会创建一个新的s2字符串对象,两者当然不一样了。
如果程序的字符串连接表达式中没有使用变量或者调用方法,那么该字符串变量的值就能够在编译期间确定下来,并且将该字符换缓存在缓冲区中,同时让该变量指向该字符串;否则将无法利用缓冲区,因为使用了变量和调用了方法之后的字符串变量的值只能在运行期间才能确定连接式的值,也就无法在编译期间确定字符串变量的值,从而无法将字符串变量增加到缓冲区并加以利用。
如果要对s1与s2加上final关键字后,结果就为true了。因为会在编译期进行优化处理。
所以如果有字符串拼接之类的操作,建议使用StringBuilder类型或StringBuffer类。
(3)String(char [] value):
拷贝数组
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
这个新创建的String相当于数组的拷贝。运用Arrays.copyOf(数组,数组长度)。
(4)String(char[] value,int offset,int count):
拷贝数组的某一段或者叫子数组的拷贝,从某个位置开始,长度是多少。
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
这个新创建的String相当于数组的拷贝。首先判断参数是否合法,然后运用Arrays.copyOfRange(数组,拷贝的开始位置,拷贝结束的位置)。
(5)String(int[] codePoints, int offset, int count),
不是将整型数组换成字符串,而是将十进制的ascii码数组转换成字符串。
public String(int[] codePoints, int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > codePoints.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
final int end = offset + count;
// Pass 1: Compute precise size of char[]
int n = count;
for (int i = offset; i < end; i++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
continue;
else if (Character.isValidCodePoint(c))
n++;
else throw new IllegalArgumentException(Integer.toString(c));
}
// Pass 2: Allocate and fill in char[]
final char[] v = new char[n];
for (int i = offset, j = 0; i < end; i++, j++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
v[j] = (char)c;
else
Character.toSurrogates(c, v, j++);
}
this.value = v;
}
这个新创建的String是对ASCII数组进行转换。首先判断参数是否正确,然后计算ASCII数组有意义字符的精确长度,最后将有效的ASCII转化为字符串(char数组)。
(6)String(byte bytes[], int offset, int length, String charsetName)
对byte数组子序列以不同的编码方式进行解码转化为String
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);
}
这个新创建的String是按charset解码方式对byte数组子序列解码并转换为String,首先判断解码方式名称是否为null,在用checkBounds函数对其他参数进行检测取值是否正确,用StringCoding.decode函数对将byte数组解码为char数组(字符串)。
(7)String(byte bytes[], int offset, int length, Charset charset)
跟上面那个一样,只是把charsetName变为charset
public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charset, bytes, offset, length);
}
(8)String(byte bytes[], String charsetName) 和 String(byte bytes[], Charset charset)
对byte数组全部以不同的编码方式进行解码转化为String,调用的是以byte数组子序列的方式解码。
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
public String(byte bytes[], Charset charset) {
this(bytes, 0, bytes.length, charset);
}
(9)String(byte bytes[], int offset, int length) 和 String(byte bytes[]):
对byte数组子序列进行系统的编码方式转换为char数组,第二个是调用第一个
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
//下面代码是这个类里面的,StringCoding.decode(bytes, offset, length)会调用
static char[] decode(byte[] ba, int off, int len) {
String csn = Charset.defaultCharset().name();
try {
// use charset name decode() variant which provides caching.
return decode(csn, ba, off, len);
} catch (UnsupportedEncodingException x) {
warnUnsupportedCharset(csn);
}
try {
return decode("ISO-8859-1", ba, off, len);
} catch (UnsupportedEncodingException x) {
// If this code is hit during VM initialization, MessageUtils is
// the only way we will be able to get any kind of error message.
MessageUtils.err("ISO-8859-1 charset not available: "
+ x.toString());
// If we can not find ISO-8859-1 (a required encoding) then things
// are seriously wrong with the installation.
System.exit(1);
return null;
}
}
//StringCoding.decode(bytes, offset, length)会调用另一个类的这个方法
public static Charset defaultCharset() {
if (defaultCharset == null) {
synchronized (Charset.class) {
String csn = AccessController.doPrivileged(
new GetPropertyAction("file.encoding"));
Charset cs = lookup(csn);
if (cs != null)
defaultCharset = cs;
else
defaultCharset = forName("UTF-8");
}
}
return defaultCharset;
}
先看最后一个defaultCharset函数,先去配置文件中找file.encoding编码方式,如果没有就用UTF-8编码方式
再看倒数第二个decode函数,加载完系统的编码方式后,尝试去进行解码,若解码失败,则用ISO-8859-1解码,若在失败就抛异常,退出系统。
(10)String(StringBuffer buffer)
将StringBuffer类型转换为char数组(字符串)
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
buffer.getValue(),因为buffer本身也是个char数组,通过getValue变为char数组,然后指定长度为buffer的长度,再用Arrays.copy(char[],int )进行拷贝。
有synchronized可知该方法是线程安全的,同时,StringBuffer是线程安全的。
(11)String(StringBuilder builder)
将StringBuilder类型转换为char数组(字符串)
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
跟上面那个一样。
线程不安全,没有synchronized修饰,同时StringBuilder是线程不安全的。