转载:String的一些细节
来源:互联网 发布:怎么查淘宝数据 编辑:程序博客网 时间:2024/05/16 11:47
Original link: http://www.cnblogs.com/tianchi/archive/2012/11/14/2768851.html
String的一些细节
2012-11-14 12:40 by ggzwtj, 1444 阅读, 0 评论, 收藏, 编辑首先说明这里指的是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"
));
// 瀛楃涓?
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"就可以了。
-----------------------------------------------------------------
欢迎拍砖。
- 转载:String的一些细节
- 关于string的一些细节
- String 中 创建对象的一些细节和String对象比较的一些细节
- 学习java应该注意的一些细节(转载)
- 网站SEO技巧应该注意的一些细节问题 转载
- java中的String类型的存储原理和一些细节
- String的细节
- document_getElementById的一些细节
- JS的一些细节
- 一些简单的细节
- dll的一些细节
- C++的一些细节
- Servlet的一些细节
- Servlet的一些细节
- Servlet的一些细节
- Servlet的一些细节
- 一些小小的细节
- Servlet的一些细节
- wmctrl编译与使用(-)
- OpenGL ES2.0---凿岩车钻臂3D案例之加载顶点shader和片元shader类的方法
- windbg - Getting Started with WinDBG - Part3
- 网络编程相关
- 单链表的简单c++实现
- 转载:String的一些细节
- Count Primes
- Android事件分发(三)
- stm32f103vc---ve切换编译
- Vi中的常用操作
- PO、VO、BO、POJO、EJB、JavaBean
- 支持向量机通俗导论(理解SVM的三层境界)
- zip文件格式分析
- Factory工厂模式