String类
String类是一种比较特殊的类型,不是包装类,没有所谓的自动装箱,拆箱,也存在缓存机制(主要是接着这篇博客来写的)。不同于Integer等类的缓存机制(偏向于上层,由IntegerCache类实现),String的缓存机制与字符串常量池(Java内存模型里面的一部分)有关,关于字符串常量池可以看这篇博客。
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println("s1 == s2: " + (s1 == s2));
System.out.println("s1 == s3: " + (s1 == s3));
在通过双引号创建字符串变量时,首先去字符串常量池中寻找,如果该字符串已经存在,直接返回该字符串的地址(引用),否则需要将该字符串放入常量池中。
此外,String类声明的是不可变的字符串对象,原因是其字符串数组value被final修饰,不可改变(即指向其它新的数组),这里的hash直接缓存了hashcode,避免了重复计算hash值
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
Java对于字符串运算符“+”的重载
既然String类是不可以修改的,那我们平常使用的时候为什么可以直接使用“+”来拼接字符串,比如
public class StringDemo {
public static void main(String[] args) {
String string1 = "a" + "b" + "c";
String string2 = "123";
string2 += "456";
System.out.println(string1);
System.out.println(string2);
}
}
这是官方文档给出的解释,Java本身对于字符串拼接操作符“+”进行了一个重载,通过可变的StringBuilder类(或者StringBuffer)及其方法append方法来实现字符串拼接,最后使用toString()方法返回一个String对象。
The Java language provides special support for the string concatenation operator ( + ), and for conversion of other objects to strings. String concatenation is implemented through the StringBuilder(or StringBuffer) class and its append method. String conversions are implemented through the method toString, defined by Object and inherited by all classes in Java. For additional information on string concatenation and conversion, see Gosling, Joy, and Steele, The Java Language Specification.
首先通过javac命令获得字节码文件StringDemo.class,直接用文本编辑工具打开字节码.class文件是看不出什么东西来的,但有一些工具可以帮忙解析.class文件,比如Idea就可以自动将.class文件尽可能还原成原本的java文件,比如下面是在idea中直接查看.class文件,这里可以看到原本的"a" + “b” + “c"变成了"abc”,这里在后面也会说到。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class StringDemo {
public StringDemo() {
}
public static void main(String[] var0) {
String var1 = "abc";
String var2 = "123";
var2 = var2 + "456";
System.out.println(var1);
System.out.println(var2);
}
}
然后是使用javap命令对.class文件进行反解析,有关jvm指令可以查看这篇博客
首先对于String string1 = “a” + “b” + "c"这种写法,编译器会直接将其优化成String var1 = “abc”。
转化为代码可能是这个样子
String string2 = "123";
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("123");
stringBuilder.append("456");
string2 = stringBuilder.toString();
官方推荐是用StringBuilder或者StringBuffer,而不是直接使用String类,比如for循环中直接使用String频繁拼接字符串,则意味着每次拼接都要声明一个StringBuilder变量,然后进行append,最后调用toString方法,效率太低,因此操作字符串还是使用StringBuilder或者StringBuffer。
常用函数如下所示
- 返回字符串的长度length()。
public int length() {
return value.length;
}
- 字符串是否为空isEmpty()(不是null)。
public boolean isEmpty() {
return value.length == 0;
}
- 返回返回指定索引处的字符。
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
- getBytes(String charsetName)和getBytes(Charset charset):根据指定字符集,将字符串编码为byte数组。
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
}
- 字符串是否相等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;
}
6.字符串比较compareTo():实现了Comparable接口,根据字典序返回大小,此外也可以忽略字母大小写(自定义了Comparator类型的CASE_INSENSITIVE_ORDER)
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;
}
- 计算字符串的hashcode,计算公式为 s [ 0 ] ∗ 3 1 ( n − 1 ) + s [ 1 ] ∗ 3 1 ( n − 2 ) + . . . + s [ n − 1 ] s[0]*31^{(n-1)} + s[1]*31^{(n-2)} + ... + s[n-1] s[0]∗31(n−1)+s[1]∗31(n−2)+...+s[n−1]
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
- 返回子字符串,可以返回它本身。
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
- 字符串替换,新字符等于原字符时返回原字符串否则返回。
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 < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}
此外还有如下函数等等,比较多,可以直接查看官方文档
静态函数String.valueOf根据传进来的参数,比如int、long、char、float等等返回相应的字符串。
indexOf():返回指定字符的索引。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
StringBuffer和StringBuilder(先挖坑)
首先说一下StringBuffer和StringBuilder的最大区别:单线程环境下使用StringBuilder,多线程环境下推荐使用StringBuffer。
StringBuffer(JDK1.0),其中很多方法都被synchronized关键词修饰,因此是线程安全的
StringBuilder(JDK1.5),没有锁的频繁获取或者释放,效率更高,但线程不安全。
两个类声明如下,且都继承了AbstractStringBuilder类,但感觉比较奇怪的是,StringBuffer为什么还更早出现了。
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
AbstractStringBuilder内部实现了一个自动扩容机制,newCapacity函数代码如下:
当发现数组长度不够时(初始为16),扩展为原长度的2倍加2。
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}