java string的一些细节剖析_java

分享到:
首先说明这里指的是Java中的String,虽然我已经决定转战C/C++了,但是因为今天碰到一个问题,还是来看一下。String的定义如下:

public final class String
{
private final char value[]; // 保存的字符串
private final int offset; // 开始的位置
private final int count; // 字符数目
private int hash; // 缓存的hash值
......
}

在Debug的时候可以看到保存的值如下:
 
需要说明一下的是:如果没有调用过hashCode(),那么hash的值为0。容易知道这里的value也就是真正保存的字符串的值(也就是“字符串测试”)的char数组,而每个char的值是多少呢?很容易验证:Unicode。
到这里大家也就猜到我们常用的subString是怎么实现的了:如果是让我们实现的话让new String使用相同的value(char数组),只修改offset和count就可以了。这样的话既省空间又快(不需要拷贝),而事实上也是这样的:

public String substring(int beginIndex) {
return substring(beginIndex, count);
}
public String substring(int beginIndex, int endIndex) {
......
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}

既然是在讨论字符串,JVM默认使用的是什么编码呢?通过调试可以发现:

public static Charset defaultCharset() {
if (defaultCharset == null) {
synchronized (Charset.class) {
java.security.PrivilegedAction pa = new GetPropertyAction("file.encoding");
String csn = (String)AccessController.doPrivileged(pa);
Charset cs = lookup(csn);
if (cs != null)
defaultCharset = cs;
else
defaultCharset = forName("UTF-8");
}
}

其中defaultCharset的值可以通过:
  -Dfile.encoding=utf-8
进行设置。当然如果你想设置为“abc”也可以,但会默认设置为UTF-8。可以通过System.getProperty("file.encoding")来看具体的值。看defaultCharset是为什么呢?因为网络传输的过程中应该都是byte数组,不同的编码方式得到的byte数组可能是不相同的。所以,我们得知道编码方式是怎么得到的吧?具体得到byte数组的方法也就是我们下面重点要看的getBytes了,它最终要调用的是CharsetEncoder的encode方法,如下:

public final CoderResult encode(CharBuffer in, ByteBuffer out, boolean endOfInput) {
int newState = endOfInput ? ST_END : ST_CODING;
if ((state != ST_RESET) && (state != ST_CODING) && !(endOfInput && (state == ST_END)))
throwIllegalStateException(state, newState);
state = newState;
for (;;) {
CoderResult cr;
try {
cr = encodeLoop(in, out);
} catch (BufferUnderflowException x) {
throw new CoderMalfunctionError(x);
} catch (BufferOverflowException x) {
throw new CoderMalfunctionError(x);
}
if (cr.isOverflow())
return cr;
if (cr.isUnderflow()) {
if (endOfInput && in.hasRemaining()) {
cr = CoderResult.malformedForLength(in.remaining());
} else {
return cr;
}
}
CodingErrorAction action = null;
if (cr.isMalformed())
action = malformedInputAction;
else if (cr.isUnmappable())
action = unmappableCharacterAction;
else
assert false : cr.toString();
if (action == CodingErrorAction.REPORT)
return cr;
if (action == CodingErrorAction.REPLACE) {
if (out.remaining() < replacement.length)
return CoderResult.OVERFLOW;
out.put(replacement);
}
if ((action == CodingErrorAction.IGNORE) || (action == CodingErrorAction.REPLACE)) {
in.position(in.position() + cr.length());
continue;
}
assert false;
}
}

当然首先会根据需要的编码格式选择对应的CharsetEncoder,而最主要的是不同的CharsetEncoder实现了不同的encodeLoop方法。这里可能会不明白为什么这里有个for(;;)?其实看CharsetEncoder所处的包(nio)和它的参数也就大概明白了:这个函数是可以处理流的(虽然我们这里使用的时候不会循环)。
在encodeLoop方法中会将尽可能多的char转换为byte,new String差不多就是上面的逆过程。
在实际的开发过程中经常会遇到乱码问题:
在上传文件的时候取到文件名;
JS传到后端的字符串;
首先先尝试下下面代码的的运行结果:

public static void main(String[] args) throws Exception {
String str = "字符串";
// -41 -42 -73 -5 -76 -82
printArray(str.getBytes());
// -27 -83 -105 -25 -84 -90 -28 -72 -78
printArray(str.getBytes("utf-8"));
// ???
System.out.println(new String(str.getBytes(), "utf-8"));
// 瀛
昵    称:
验证码:

相关文档: