手机
当前位置:查字典教程网 >编程开发 >Java >java string的一些细节剖析
java string的一些细节剖析
摘要:首先说明这里指的是Java中的String,虽然我已经决定转战C/C++了,但是因为今天碰到一个问题,还是来看一下。String的定义如下:...

首先说明这里指的是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的时候可以看到保存的值如下:

java string的一些细节剖析1

需要说明一下的是:如果没有调用过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"));

// 瀛楃涓?

System.out.println(new String(str.getBytes("utf-8"), "gbk"));

// 字符??

System.out.println(new String("瀛楃涓?".getBytes("gbk"), "utf-8"));

// -41 -42 -73 -5 63 63

printArray(new String("瀛楃涓?".getBytes("gbk"), "utf-8").getBytes());

}

public static void printArray(byte[] bs){

for(int i = 0; i < bs.length; i++){

System.out.print(bs[i] + " ");

}

System.out.println();

}

在程序中的注释中说明了输出结果:

因为GBK中2个byte表示一个汉字,所以就有了6个byte;

因为UTF-8中3个byte表示一个汉字,所以就有了9个byte;

因为通过无法通过GBK生成的byte数组再根据UTF-8的规则去生成字符串,所以显示???;

这个是经常遇到乱码的原因,GBK使用UTF-8生成的byte能生成字符串;

虽然上面生成的是乱码,但是电脑并不这么认为,所以还是能通过getBytes得到字节数组,而这个数组中是utf-8是可以识别的;

最后的两个63(?)应该是encode填充的(或者是字节不够直接填充的,这个地方没有细看);

GBK和UTF-8对于因为字母和数字的编码是相同的,所以在这几种字符的处理上是不会出现乱码的,但是他们对汉字的编码确实不一样的,这就是很多问题的起源,看下面代码:

new String(new String("我们".getBytes("UTF-8"), "GBK").getBytes("GBK"), "UTF-8);

显然这段代码的结果是“我们”,但是对我们有什么用?首先我们注意到:

new String("我们".getBytes("UTF-8"), "GBK");

这段代码的结果是乱码,而且很多的乱码都是“乱成这样的”。但是要记住:这里的乱是对我们而言,对电脑来说无所谓“乱”与“不乱”,它在我们几乎放弃的时候还能从乱码中通过“getBytes("GBK")”得到它的“主心骨”,然后我们就可以用“主心骨”还原出原来的字符串。

貌似上面的这段代码能解决“GBK”和“UTF-8”之间的乱码问题,但是这种解决方法也只限于一种特殊情况:所有连续汉字的个数都是偶数个!原因在上面已经说过了,这里就不赘述了。

那么怎么解决这个问题呢?

第一种解决方法:encodeURI

为什么要用这种方法呢?原因很简单:GBK和UTF-8对于%、数字、字母的编码是统一的,所以在传输encode之后的串可以100%保证在这两种编码下得到的是同一个东西,然后再decode得到字符串就可以。根据String的格式可以猜测encode和decode的效率是非常非常高的,所以这也算是一种很好的解决方法了。

第二种解决方法:统一编码格式

这边使用的是Webx矿建,只需要将webx.xml中设置defaultCharset="UTF-8"就可以了。

【java string的一些细节剖析】相关文章:

java stringbuffer的用法示例

java中final与finally的使用介绍

基于JVM 调优的技巧总结分析

Java的关键字与保留字小结

Java的正则表达式深入分析

java 使用线程做的一个简单的ATM存取款实例代码

使用java获取md5值的两种方法

Java中的访问修饰符详细解析

java中File类的使用方法

基于java math API 的详细解释说明

精品推荐
分类导航