Java 字符编码(二)Java 中的编解码
Java 字符编码(二)Java 中的编解码
java.nio.charset 包中提供了一套处理字符编码的工具类,主要有 Charset、CharsetDecoder、CharsetEncoder、CoderResult、StandardCharsets 这几个类。
Java 中的字符使用 Unicode 编码,每个字符占用两个字节,16 个二进制位,向 ByteBuffer 中存放数据的时候需要考虑字符的编码,从中读取的时候也需要考虑字符的编码方式,也就是编码和解码。
一、CharSet
1.1 获取字符集
// 返回指定的字符集 CharSet
Charset charset = Charset.forName("utf8");
// 返回虚拟机默认的字符集 CharSet
Charset charset = Charset.defaultCharset();
forName -> lookup -> lookup2,其中 lookup 会缓存正在使用的 Charset 编码方式,lookup2 则真正用于获取对应的编码方式。
private static Charset lookup2(String charsetName) {
...
Charset cs;
if ((cs = standardProvider.charsetForName(charsetName)) != null ||
(cs = lookupExtendedCharset(charsetName)) != null ||
(cs = lookupViaProviders(charsetName)) != null) {
return cs;
}
return null;
}
可以看到 Charset 有三种定义方式:
standardProviderJDK 定义的标准格式,如 UTF-8,UTF-16extendedProviderJDK 扩展的标准格式CharsetProviderSPI,通过 java.nio.charset.spi.CharsetProvider 自定义的格式
1.2 编码和解码
// ThreadLocalCoders.decoderFor(this) 调用 newDecoder 缓存了正在使用的缓解码器
public final CharBuffer decode(ByteBuffer bb) {
try {
return ThreadLocalCoders.decoderFor(this)
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.decode(bb);
} catch (CharacterCodingException x) {
throw new Error(x); // Can't happen
}
}
public final ByteBuffer encode(CharBuffer cb) {
try {
return ThreadLocalCoders.encoderFor(this)
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.encode(cb);
} catch (CharacterCodingException x) {
throw new Error(x); // Can't happen
}
}
1.3 编码器和解码器
newEncoder 和 newDecoder 是 Charset 中最重要的两个抽象方法:
//编码器
CharsetEncoder encoder = charset.newEncoder();
//解码器
CharsetDecoder decoder = charset.newDecoder();
CharsetDecoder 将 ByteBuffer 解码为 CharBuffer,而 CharsetEncoder 则相反,将 CharBuffer 编码为 ByteBuffer。
在讲解 CharsetDecoder 前先了解一下 CoderResult,这个类封装了编解码的结果,主要用于处理异常。
二、CoderResult
2.1 CoderResult 有以下几种状态
# ByteBuffer 解码完成
private static final int CR_UNDERFLOW = 0;
# CharBuffer 空间不足,解码完成
private static final int CR_OVERFLOW = 1;
private static final int CR_ERROR_MIN = 2;
# ByteBuffer 解码异常
private static final int CR_MALFORMED = 2;
# ByteBuffer 超出了 Unicode 定义的范围,eg: 0xD800-0xDFFF 或 >0x10FFFF
# Unicode 在编写的时候已经考虑了编解码的问题
private static final int CR_UNMAPPABLE = 3;
其中 CR_UNDERFLOW(解码完成) 和 CR_OVERFLOW(CharBuffer 空间不足) 属于正常状态,CR_MALFORMED(字节码出错) 和 CR_UNMAPPABLE(不能映射到 Unicode 上) 属性异常状态。
2.2 CoderResult 重要属性及方法
// 对应 CR_UNDERFLOW、CR_OVERFLOW、CR_MALFORMED、CR_UNMAPPABLE
private final int type;
// 如果解码正常(也就是前两种状态)为 0,如果异常(后两种状态)对应解码出错的长度
private final int length;
isUnderflow()、isOverflow()、isError()、isMalformed()、isUnmappable() 等方法用于判断解码的状态。下面四个静态方法则用于构建解码的结果。
// 解码正常
public static final CoderResult UNDERFLOW = new CoderResult(CR_UNDERFLOW, 0);
public static final CoderResult OVERFLOW = new CoderResult(CR_OVERFLOW, 0);
// 解码异常
public static CoderResult malformedForLength(int length) {
return malformedCache.get(length);
}
public static CoderResult unmappableForLength(int length) {
return unmappableCache.get(length);
}
三、CharsetDecoder
CharsetDecoder 最重要的方法是 decode(in, out, endOfInput) ,控制了解码的流程,最重要的模板方法是 decodeLoop(in, out) ,由子类实现。CharsetDecoder 的实现类位于 sun.nio.cs 下,如 UTF_8、UTF_16、UTF_32 等。
CharsetDecoder 解码的流程如下,包含了四种状态:
- 首先执行 reset() 方法,将 state=ST_RESET
- (可选)多次执行 decode(in, out, false) 方法,注意此时传入 false
- 执行 decode(in, out, true) 方法,注意此时传入 true,此时解码完成
- (可选)执行 flush(out) 方法,更新内部状态
3.1 decode(in, out, endOfInput)
这个方法有 3 个参数,前两个参数不用解释,第三个参数用来控制解码是否完成,如按 UTF-8 解码时最后只剩下一下字节,这时可能是流读取还未结束,需要继续读取,此时不能算做异常,这时就需要有 endOfInput 来控制。针对流的编解码见 StreamDecoder。
public final CoderResult decode(ByteBuffer in, CharBuffer out,
boolean endOfInput) {
// 1. state 只能为 ST_RESET、ST_CODING 或 ST_END(endOfInput=true)
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 {
// 2. decodeLoop 完成解码
cr = decodeLoop(in, out);
} catch (BufferUnderflowException x) {
throw new CoderMalfunctionError(x);
} catch (BufferOverflowException x) {
throw new CoderMalfunctionError(x);
}
// 2. CharBuffer 空间不足
if (cr.isOverflow())
return cr;
// 3. 解码完成,如果 endOfInput=false 则会先返回,由调用者继续调用该方法解码
if (cr.isUnderflow()) {
if (endOfInput && in.hasRemaining()) {
cr = CoderResult.malformedForLength(in.remaining());
// Fall through to malformed-input case
} else {
return cr;
}
}
// 3. 解码出现异常时的处理,默认为 REPORT,即由上层处理异常
CodingErrorAction action = null;
if (cr.isMalformed())
action = malformedInputAction;
else if (cr.isUnmappable())
action = unmappableCharacterAction;
else
assert false : cr.toString();
// 3.1 REPORT 由上层处理异常
if (action == CodingErrorAction.REPORT)
return cr;
// 3.2 REPLACE 追加了 replacement 字符
if (action == CodingErrorAction.REPLACE) {
if (out.remaining() < replacement.length())
return CoderResult.OVERFLOW;
out.put(replacement);
}
// 3.3 IGNORE 和 REPLACE 都忽略这种异常继续解码
if ((action == CodingErrorAction.IGNORE)
|| (action == CodingErrorAction.REPLACE)) {
// Skip erroneous input either way
in.position(in.position() + cr.length());
continue;
}
assert false;
}
}
3.2 decode(ByteBuffer in)
CharsetDecoder 的 decode(ByteBuffer in) 方法将 ByteBuffer 解码为 CharBuffer,这个方法调用了上面的方法,传入的 endOfInput=true。但需要注意的是如果数据是从文件流等读取时,ByteBuffer 数据可能并不完整,出现半包的情况,进而导致解码异常,这时就需要使用上面的方法(参数 endOfInput)精确控制解码的流程了。
public final CharBuffer decode(ByteBuffer in) throws CharacterCodingException {
int n = (int)(in.remaining() * averageCharsPerByte());
CharBuffer out = CharBuffer.allocate(n);
if ((n == 0) && (in.remaining() == 0))
return out;
reset();
for (;;) {
CoderResult cr = in.hasRemaining() ?
decode(in, out, true) : CoderResult.UNDERFLOW;
// 1. 解码完成,调用 flush 并结束
if (cr.isUnderflow())
cr = flush(out);
if (cr.isUnderflow())
break;
// 2. CharBuffer 空间不足,扩容,继续解码
if (cr.isOverflow()) {
n = 2*n + 1; // Ensure progress; n might be 0!
CharBuffer o = CharBuffer.allocate(n);
out.flip();
o.put(out);
out = o;
continue;
}
// 3. 解码异常,直接抛出
cr.throwException();
}
out.flip();
return out;
}
每天用心记录一点点。内容也许不重要,但习惯很重要!
Java 字符编码(二)Java 中的编解码的更多相关文章
- JAVA字符编码二:Unicode,ISO-8859,GBK,UTF-8编码及相互转换
第二篇:JAVA字符编码系列二:Unicode,ISO-8859-1,GBK,UTF-8编码及相互转换 1.函数介绍 在Java中,字符串用统一的Unicode编码,每个字符占用两个字节,与编码有 ...
- Java 字符编码(三)Reader 中的编解码
Java 字符编码(三)Reader 中的编解码 我们知道 BufferedReader 可以将字节流转化为字符流,那它是如何编解码的呢? try (BufferedReader reader = n ...
- 【JAVA编码专题】 JAVA字符编码系列三:Java应用中的编码问题
这两天抽时间又总结/整理了一下各种编码的实际编码方式,和在Java应用中的使用情况,在这里记录下来以便日后参考. 为了构成一个完整的对文字编码的认识和深入把握,以便处理在Java开发过程中遇到的各种问 ...
- 【JAVA编码】 JAVA字符编码系列二:Unicode,ISO-8859,GBK,UTF-8编码及相互转换
http://blog.csdn.net/qinysong/article/details/1179489 这两天抽时间又总结/整理了一下各种编码的实际编码方式,和在Java应用中的使用情况,在这里记 ...
- JAVA字符编码三:Java应用中的编码问题
第三篇:JAVA字符编码系列三:Java应用中的编码问题 这部分采用重用机制,引用一篇文章来完整本部分目标. 来源: Eceel东西在线 问题研究--字符集编码 地址:http://china.e ...
- 【JAVA编码专题】JAVA字符编码系列一:Unicode,GBK,GB2312,UTF-8概念基础
这两天抽时间又总结/整理了一下各种编码的实际编码方式,和在Java应用中的使用情况,在这里记录下来以便日后参考. 为了构成一个完整的对文字编码的认识和深入把握,以便处理在Java开发过程中遇到的各种问 ...
- Java 字符编码(一)Unicode 字符编码
Java 字符编码(一)Unicode 字符编码 Unicode(http://www.unicode.org/versions/#TUS_Latest_Version) 是一个编码方案,说白了希望给 ...
- 【字符编码】Java字符编码详细解答及问题探讨
一.前言 继上一篇写完字节编码内容后,现在分析在Java中各字符编码的问题,并且由这个问题,也引出了一个更有意思的问题,笔者也还没有找到这个问题的答案.也希望各位园友指点指点. 二.Java字符编码 ...
- Java 字符编码归纳总结
String newStr = new String(oldStr.getBytes(), "UTF-8"); java中的String类是按照unicode进行编码的 ...
随机推荐
- JSTL的使用
使用JSTL前的准备 想要使用JSTL,首先需要给工程导入JSTL的包(JSTL.jar和standard.jar). JSTL标签库 在JSTL中分为以下五个标签 核心标签 格式化标签 SQL标签 ...
- Java8 Base64
转自:https://www.runoob.com/java/java8-base64.html 在Java 8中,Base64编码已经成为Java类库的标准. Java 8 内置了 Base64 编 ...
- Java开发体系
蓦然回首自己做开发已经十年了,这十年中我获得了很多,技术能力.培训.出国.大公司的经历,还有很多很好的朋友.但再仔细一想,这十年中我至少浪费了五年时间,这五年可以足够让自己成长为一个优秀的程序员,可惜 ...
- nohup 写法
nohup 空格 php程序路径 空格 php脚本路径 >> .out日志路径 2>&1 &
- mysql 增加时间字段
alter table sign_customer add COLUMN update_time timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE ...
- Java JDK动态代理解析
动态代理虽不常自己实现,但在Spring或MyBatis中都有重要应用.动态代理的意义在于生成一个占位(又称代理对象),来代理真实对象,从而控制真实对象的访问.Spring常JDK和CGLIB动态代理 ...
- ORM项目中小知识点积累
申明:一下内容均建立在零基础小白的角度上,大佬们求放过~ 1.如何通过类建立外键关联 2.模板语言固定搭配 3.浏览器报错处理 4.后台取值方式 5.外键管理修改相关操作 6.两种(给后台偷偷传递消息 ...
- mysql导入太慢解决方法
半调子数据科学家又要折腾数据,拿到数据一看,3.6G的zip文件,解压看看,卧槽12个G的sql文件.好吧,又要折腾sql数据了.第一件事,肯定是搭一个数据库,导入数据咯. 折腾过sql导入的亲们都知 ...
- Android:得到WebView当前页的html源码
WebView没有提供直接的API,需要用JavaScript变通处理一下.本文试图总结一个最简单.优雅的代码. 有两步: 1.先创建一个JavaScript接口类: class MyJavaScri ...
- PeopleSoft查看所有translate value
下面sql可以列出PS中所有translate value SELECT FIELDNAME, FIELDVALUE, EFFDT, EFF_STATUS, XLATLONGNAME, XLATSHO ...