欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java string的一些细节剖析

程序员文章站 2023-01-10 18:08:54
首先说明这里指的是java中的string,虽然我已经决定转战c/c++了,但是因为今天碰到一个问题,还是来看一下。string的定义如下: 复制代码 代码如下: publ...
首先说明这里指的是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的一些细节剖析 
需要说明一下的是:如果没有调用过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"就可以了。