String对象的存放位置:大家都知道java中的对象大都是存放在堆中的,但是String对象是一个特例,它被存放在常量池中。
当创建一个字面量String对象时,首先会去检查常量池中这个对象的存在与否。
java本地方法:一个本地方法就是一个java调用非java代码的接口。该方法是非java实现,由C或C++语言实现。形式是:
修饰符 native 返回值类型 本地方法名(); 如public native String intern();
在我们看java源码时如果追溯到了本地方法,在java层面上就到头了,如果需要更深层次的了解本地方法的实现,就需要下载openjdk源码然后看它是如何实现的了。
public final class String implements java.io.Serializable, Comparable, CharSequence {}
可以看到String类是final修饰的不能被继承,同时它实现了Serializable接口可以序列化和反序列化,实现了Comparable支持字符串的比较,实现了CharSequence接口说明它是一个字符序列。
private final char value[];//存储字符串
private int hash; //字符串的hash code 默认是0
private static final long serialVersionUID = -6849794470754667710L;//序列化id
String对象的字符串实际是维护在一个字符数组中的。操作字符串实际上就是操作这个字符数组,而且这个数组也是final修饰的不能够被改变。
public String() {
this.value = "".value;
}
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
参数String对象参数来构造String对象,该构造函数经常被用来做面试题。问new String("abc");共创建了几个对象。答案是两个,字面量"abc"创建一个对象放在常量池中,new String()又创建一个对象放在堆中。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
通过整个char数组参数来构造String对象,实际将参数char数组值复制给String对象的char数组。
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// 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对象,具体哪一部分由offset和count决定,其中做了些参数检查,传入非法参数会报数组越界异常StringIndexOutOfBoundsException
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);
}
通过byte数组构造String对象,将入参byte数组中指定内容,用指定charsetName的字符集转换后构造String对象。
其中StringCoding.decode(charsetName, bytes, offset, length)是根据指定编码对byte数组进行解码,解码返回char数组。
checkBounds(bytes, offset, length)是对参数进行检查(源码如下),该方法是私有的只能在String类中调用。
private static void checkBounds(byte[] bytes, int offset, int length) {
if (length < 0)
throw new StringIndexOutOfBoundsException(length);
if (offset < 0)
throw new StringIndexOutOfBoundsException(offset);
if (offset > bytes.length - length)
throw new StringIndexOutOfBoundsException(offset + length);
}
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);
}
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
通过StringBuffer构造String对象,StringBuffer内部也是维护了一个char数组,这里将StringBuffer数组中的内容复制给String对象中的数组。而且StringBuffer是线程安全的,所以这里也加了synchronized块保证线程安全。
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
通过StringBuilder构造String对象,原理同StringBuffer一样,只不过StringBuilder是线程不安全的,所在这里没有加synchronized块。基础面试中面试官经常询问StringBuffer与StringBuilder的区别
private String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
乍一看不知道这个构造函数是用来干嘛的,仔细分析就知道这个函数大有作用。首先它同String(char[] value)函数相比多了个参数share,虽然在方法本身没有用到share,目前是只支持true,注释也说了不支持false。这个方法定义成这样应该是为了同String(char[] value)进行区分。否则没办法构成方法重载。再来看下这个方法的作用。它是直接将参数的地址传给了String对象,这样要比直接使用String(char[] value)的效率要高,因为String(char[] value)是逐一拷贝。有人会问这样Stirng对象和参数传过来的char[] value共享同一个数组,不就破坏了字符串的不可变性。设计都也考虑到了,所以它设置了保护用protected修饰而没有公开出去。所以从安全性角度考虑,他也是安全的。在java中也有很多地方用到了这种性能好的、节约内存的、安全的构造函数。如replace、concat、valueOf等方法。
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,否则返回false。
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;
}
该方法是实现Comparable接口的方法,用于对字符串进行比较大小。逻辑是对两个字符串中的数组进行逐位比较大小,从第一位开始比较,大的字符串就大,如果相同就继续向下比较,直到比较出大小为止。这里取了两个字符串中长度较小的作为循环次数。从源码也可以看出字符串比较并不是我们表面上认为的先进行长度比较,长度不一样再进行每个位置的比较。