共计 2846 个字符,预计需要花费 8 分钟才能阅读完成。
不晓得大家有没有这样得经验,就是无心中点进去得一个业面,而后钻到外面浏览了良久,我就是这样得,明天无心中,ctrl+ 左键,就点进了 string 得源码,正好今天下午没啥事,就在外面看一下,没想到,下次缓过去,就是我共事拍我让我去吃饭,哈哈哈哈,不过益处就是,我这边也整顿了一些 string 类得知识点,也分享给大家,整顿得不好还望海涵
文章首发集体公众号:Java 架构师联盟,每日更新技术好文
一、String 类
想要理解一个类,最好的方法就是看这个类的实现源代码,来看一下 String 类的源码:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence{
/** The value is used for character storage. */
private final char value[];
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;
/** 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;
........
}
从下面能够看出几点:
1)String 类是 final 类,也即意味着 String 类不能被继承,并且它的成员办法都默认为 final 办法。在 Java 中,被 final 润饰的类是不容许被继承的,并且该类中的成员办法都默认为 final 办法。
2)下面列举出了 String 类中所有的成员属性,从下面能够看出 String 类其实是通过 char 数组来保留字符串的。
上面再持续看 String 类的一些办法实现:
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {return this;}
char buf[] = new char[count + otherLen];
getChars(0, count, buf, 0);
str.getChars(0, otherLen, buf, count);
return new String(0, count + otherLen, buf);
}
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = count;
int i = -1;
char[] val = value; /* avoid getfield opcode */
int off = offset; /* avoid getfield opcode */
while (++i < len) {if (val[off + i] == oldChar) {break;}
}
if (i < len) {char buf[] = new char[len];
for (int j = 0 ; j < i ; j++) {buf[j] = val[off+j];
}
while (i < len) {char c = val[off + i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(0, len, buf);
}
}
return this;
}
从下面的三个办法能够看出,无论是 sub 操、concat 还是 replace 操作都不是在原有的字符串上进行的,而是从新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被扭转。
在这里要永远记住一点:“String 对象一旦被创立就是固定不变的了,对 String 对象的任何扭转都不影响到原对象,相干的任何 change 操作都会生成新的对象”。
二、字符串常量池
咱们晓得字符串的调配和其余对象调配一样,是须要耗费昂扬的工夫和空间的,而且字符串咱们应用的十分多。JVM 为了进步性能和缩小内存的开销,在实例化字符串的时候进行了一些优化:应用字符串常量池。每当咱们创立字符串常量时,JVM 会首先查看字符串常量池,如果该字符串曾经存在常量池中,那么就间接流量交易返回常量池中的实例援用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。因为 String 字符串的不可变性咱们能够非常必定常量池中肯定不存在两个雷同的字符串(这点对了解下面至关重要)。
Java 中的常量池,实际上分为两种状态:动态常量池和运行时常量池。所谓动态常量池,即 *.class 文件中的常量池,class 文件中的常量池不仅仅蕴含字符串 (数字) 字面量,还蕴含类、办法的信息,占用 class 文件绝大部分空间。而运行时常量池,则是 jvm 虚拟机在实现类装载操作后,将 class 文件中的常量池载入到内存中,并保留在办法区中,咱们常说的常量池,就是指办法区中的运行时常量池。
来看上面的程序:
String a = “chenssy”;String b = “chenssy”;
a、b 和字面上的 chenssy 都是指向 JVM 字符串常量池中的 ”chenssy” 对象,他们指向同一个对象。
String c = new String(“chenssy”);
new 关键字肯定会产生一个对象 chenssy(留神这个 chenssy 和下面的 chenssy 不同),同时这个对象是存储在堆中。所以下面应该产生了两个对象:保留在栈中的 c 和保留堆中 chenssy。然而在 Java 中基本就不存在两个齐全截然不同的字符串对象。故堆中的 chenssy 应该是援用字符串常量池中 chenssy。所以 c、chenssy、池 chenssy 的关系应该是:c—>chenssy—> 池 chenssy。