上次说了字符串的构造方法
String str1 = new String("abcdefg");
String str2 = new String("abcdefg");
这种做法强行在java中开了两个空间,所以str1和str2不相等。
String s1 = "xyz";
String s2 = "xyz";
这种做法下系统会认为你新开内存的欲望没有上一段代码强烈,于是会让s1s2同时指向xyz。所以s1s2相等。
#
那么问题来了,
现在有几个变量指向”xyz”?2个对吧。
如果下面有这样的代码
String s1 = s1 + "w";
之后还有几个变量指向”xyz”?就剩一个(s2)了对吧。
没错,这就是引用计数。
一个对象身上引用变量的数量,叫做引用计数。
一个对象身上的引用计数大于0,那么这个对象是可控的,说明栈中可以向他发指令。
当这个对象引用为0时呢?将失去控制,栈将无法对其继续控制,该对象成为垃圾对象。
垃圾对象还将继续占用堆空间,如果不回收掉这个空间,它将不能再被使用,很有可能被黑客利用,这种现象称为内存泄漏。
Java提供了垃圾回收机制,通过特定算法,在堆空间内存紧张的情况下,对所有的Java对象进行轮询,只要发现引用计数为0。则立即销毁释放空间。
所以理论上说Java不存在内存泄漏,程序员无需关注内存管理。(写C++是不是经常Delete?Java不需要,自动完成)
Java的每个String都提供了一大堆函数用来进行字符串的各种操作
我们可以打开String.class看一下具体的代码,前五行就有这么一句话:
private final char value[];
看到没?String本身就是一个字节数组。
String.length用于求字符串的长度。
这是String.class中的length函数:
public int length() {
return value.length;
}
看到没?其实就是对字节数组求长度。
trim的作用是去掉字符串的头尾空格。
String str = " abcdefg ";
// str.length() = 10;
// str.trim().length() = 7;
头尾切割的代码在String.class里。简单看一下就好,其实也很容易看懂。
public String trim() {
int len = value.length;
int st = 0;
char[] val = value; /* avoid getfield opcode */
while ((st < len) && (val[st] <= ' ')) {
st++;
}
while ((st < len) && (val[len - 1] <= ' ')) {
len--;
}
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
把字符串所有字符转为大写/小写。Java函数写的很复杂,不表。(写那么复杂更多是为了提高兼容性,原理并不难)
获取String字符(串的第一个字符)的位置。
String.class中代码非常简单。就是用for循环寻找一下,找到就返回。
下面这段代码是搜字符的从字符串的第fromIndex个字符开始搜索是否存在字符ch(ch转成了int类型方便计算)。搜字符串的,省篇幅不表。
public int indexOf(int ch, int fromIndex) {
final int max = value.length;
if (fromIndex < 0) {
fromIndex = 0;
} else if (fromIndex >= max) {
// Note: fromIndex might be near -1>>>1.
return -1;
}
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
// handle most cases here (ch is a BMP code point or a
// negative value (invalid code point))
final char[] value = this.value;
for (int i = fromIndex; i < max; i++) {
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
return indexOfSupplementary(ch, fromIndex);
}
}
反过来找相对应的字符(串的第一个字符——注意不是最后一个)。原理和indexOf类似。
比较两个字符串的值是否相等。
查看字符串中有没有这个字符(串)。代码只有三行:
public boolean contains(CharSequence s) {
return indexOf(s.toString()) > -1;
}
所以contains完全是一个装饰方法。就是为了方便不明白的人用,更加书面化。
获得字符串在第x个位置的字符。代码如下:
封装完全是为了多写个if,做安全性考虑(如果超出范围会抛出一个数组上下标越界异常)。
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
比较两个字符串,和普通的equals相比,忽略了大小写。
其实和下面这个式子等价:
strA.toUpperCase().equals(B.toUpperCase());
但是,这样折腾一下就多了两个对象出来,并且代码更长。所以Java也对忽略大小写做了封装。
把字符串转成原始的字符数组。String.class代码如下:
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
System.arraycopy也很好理解对吧,把字符串从0到length的所有字符赋值给字符数组result[]。
什么,你要打开看一下——不好意思,看不到啦。因为它是长这样子的
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
有没有发现一个native方法,这表明这段代码使用了底层的C语言进行编写。在JDK中是看不到了,我们可以离开JDK的范围去更底层看,那就是后话啦。
链接两个字符串。比如:
s = "abc".concat("efg");
s为”abcefg”;
另外为了迎合传统程序员,Java中重载了String类型的加号,使其变为concat方法的另一种形式。简单说,平常看到的
s = "abc"+"efg";
其实就是concat的另一种形式。
s = "abc".concat("efg");
concat的代码具体实现方法:用了Arrays的静态方法copyOf,在字符数组的形态下,才能进行复制操作。然后,重新创建一个new String出来。
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
把字符串转为数字。就是内部封装了Integet.toString(),作用相同。
字符串之间相互比较大小。
strA.compartTo(strB);
A小于B返回-1,等于返回0,大于返回1。
注意,String类并没有重载<、=、>,不要在比较的时候用。
CompareTo的忽略字符串版。
判断字符串是否以”xx”开头/结尾。比如:
"abcdefg".startsWith("abc"); //true
"abcdefg".endsWith("efg"); //true
判断字符串是否为空。封装代码只有一行,就判断下长度是否为0就可以了。也是一个装饰方法。
public boolean isEmpty() {
return value.length == 0;
}
String.replace("a", "bb");
把一段字符串中的所有”a”替换成”bb”。
String.class源代码如下。代码合理运用了内存,一直到出现不相等字符的时候,才在内存中新开辟空间存储替换字符。
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.subString(3, 7);
返回字符串的第3-6位(就是第4-7个)字符。
源代码中可以看到,subString函数首先进行了安全处理,确定不会异常后,重新做了一个new String。(仔细想想其实和引用计数,构造函数的关系都很密切)
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}