者:君山,出处已不可考,我看到的都是转的,但是确实是好文
用户从浏览器端发起一个 HTTP 请求,需要存在编码的地方是 URL、Cookie、Parameter。服务器端接受到 HTTP 请求后要解析 HTTP 协议,其中 URI、Cookie 和 POST 表单参数需要解码,服务器端可能还需要读取数据库中的数据,本地或网络中其它地方的文本文件,这些数据都可能存在编码问题,当 Servlet 处理完所有请求的数据后,需要将这些数据再编码通过 Socket 发送到用户请求的浏览器里,再经过浏览器解码成为文本。这些过程如下图所示:
图 3. 一次 HTTP 请求的编码示例(查看大图)
如上图所示一次 HTTP 请求设计到很多地方需要编解码,它们编解码的规则是什么?下面将会重点阐述一下:
URL 的编解码
用户提交一个 URL,这个 URL 中可能存在中文,因此需要编码,如何对这个 URL 进行编码?根据什么规则来编码?有如何来解码?如下图一个 URL:
图 4.URL 的几个组成部分
上图中以 Tomcat 作为 Servlet Engine 为例,它们分别对应到下面这些配置文件中:
Port 对应在 Tomcat 的 <Connector port="8080"/> 中配置,而 Context Path 在 <Context path="/examples"/> 中配置,Servlet Path 在 Web 应用的 web.xml 中的
<servlet-mapping> <servlet-name>junshanExample</servlet-name> <url-pattern>/servlets/servlet/*</url-pattern> </servlet-mapping>
<url-pattern> 中配置,PathInfo 是我们请求的具体的 Servlet,QueryString 是要传递的参数,注意这里是在浏览器里直接输入 URL 所以是通过 Get 方法请求的,如果是 POST 方法请求的话,QueryString 将通过表单方式提交到服务器端,这个将在后面再介绍。
上图中 PathInfo 和 QueryString 出现了中文,当我们在浏览器中直接输入这个 URL 时,在浏览器端和服务端会如何编码和解析这个 URL 呢?为了验证浏览器是怎么编码 URL 的我们选择 FireFox 浏览器并通过 HTTPFox 插件观察我们请求的 URL 的实际的内容,以下是 URL:HTTP://localhost:8080/examples/servlets/servlet/ 君山 ?author=君山在中文 FireFox3.6.12 的测试结果
图 5. HTTPFox 的测试结果
君山的编码结果分别是:e5 90 9b e5 b1 b1,be fd c9 bd,查阅上一届的编码可知,PathInfo 是 UTF-8 编码而 QueryString 是经过 GBK 编码,至于为什么会有“%”?查阅 URL 的编码规范 RFC3986 可知浏览器编码 URL 是将非 ASCII 字符按照某种编码格式编码成 16 进制数字然后将每个 16 进制表示的字节前加上“%”,所以最终的 URL 就成了上图的格式了。
默认情况下中文 IE 最终的编码结果也是一样的,不过 IE 浏览器可以修改 URL 的编码格式在选项 -> 高级 -> 国际里面的发送 UTF-8 URL 选项可以取消。
从上面测试结果可知浏览器对 PathInfo 和 QueryString 的编码是不一样的,不同浏览器对 PathInfo 也可能不一样,这就对服务器的解码造成很大的困难,下面我们以 Tomcat 为例看一下,Tomcat 接受到这个 URL 是如何解码的。
解析请求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,这个方法把传过来的 URL 的 byte[] 设置到 org.apache.coyote.Request 的相应的属性中。这里的 URL 仍然是 byte 格式,转成 char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的:
1 protected void convertURI(MessageBytes uri, Request request) 2 throws Exception { 3 ByteChunk bc=uri.getByteChunk(); 4 int length=bc.getLength(); 5 CharChunk cc=uri.getCharChunk(); 6 cc.allocate(length, -1); 7 String enc=connector.getURIEncoding(); 8 if (enc !=null) { 9 B2CConverter conv=request.getURIConverter(); 10 try { 11 if (conv==null) { 12 conv=new B2CConverter(enc); 13 request.setURIConverter(conv); 14 } 15 } catch (IOException e) {...} 16 if (conv !=null) { 17 try { 18 conv.convert(bc, cc, cc.getBuffer().length - 19 cc.getEnd()); 20 uri.setChars(cc.getBuffer(), cc.getStart(), 21 cc.getLength()); 22 return; 23 } catch (IOException e) {...} 24 } 25 } 26 // Default encoding: fast conversion 27 byte[] bbuf=bc.getBuffer(); 28 char[] cbuf=cc.getBuffer(); 29 int start=bc.getStart(); 30 for (int i=0; i < length; i++) { 31 cbuf[i]=(char) (bbuf[i + start] & 0xff); 32 } 33 uri.setChars(cbuf, 0, length); 34 }
从上面的代码中可以知道对 URL 的 URI 部分进行解码的字符集是在 connector 的 <Connector URIEncoding=”UTF-8”/> 中定义的,如果没有定义,那么将以默认编码 ISO-8859-1 解析。所以如果有中文 URL 时最好把 URIEncoding 设置成 UTF-8 编码。
QueryString 又如何解析? GET 方式 HTTP 请求的 QueryString 与 POST 方式 HTTP 请求的表单参数都是作为 Parameters 保存,都是通过 request.getParameter 获取参数值。对它们的解码是在 request.getParameter 方法第一次被调用时进行的。request.getParameter 方法被调用时将会调用 org.apache.catalina.connector.Request 的 parseParameters 方法。这个方法将会对 GET 和 POST 方式传递的参数进行解码,但是它们的解码字符集有可能不一样。POST 表单的解码将在后面介绍,QueryString 的解码字符集是在哪定义的呢?它本身是通过 HTTP 的 Header 传到服务端的,并且也在 URL 中,是否和 URI 的解码字符集一样呢?从前面浏览器对 PathInfo 和 QueryString 的编码采取不同的编码格式不同可以猜测到解码字符集肯定也不会是一致的。的确是这样 QueryString 的解码字符集要么是 Header 中 ContentType 中定义的 Charset 要么就是默认的 ISO-8859-1,要使用 ContentType 中定义的编码就要设置 connector 的 <Connector URIEncoding=”UTF-8” useBodyEncodingForURI=”true”/> 中的 useBodyEncodingForURI 设置为 true。这个配置项的名字有点让人产生混淆,它并不是对整个 URI 都采用 BodyEncoding 进行解码而仅仅是对 QueryString 使用 BodyEncoding 解码,这一点还要特别注意。
从上面的 URL 编码和解码过程来看,比较复杂,而且编码和解码并不是我们在应用程序中能完全控制的,所以在我们的应用程序中应该尽量避免在 URL 中使用非 ASCII 字符,不然很可能会碰到乱码问题,当然在我们的服务器端最好设置 <Connector/> 中的 URIEncoding 和 useBodyEncodingForURI 两个参数。
当客户端发起一个 HTTP 请求除了上面的 URL 外还可能会在 Header 中传递其它参数如 Cookie、redirectPath 等,这些用户设置的值很可能也会存在编码问题,Tomcat 对它们又是怎么解码的呢?
对 Header 中的项进行解码也是在调用 request.getHeader 是进行的,如果请求的 Header 项没有解码则调用 MessageBytes 的 toString 方法,这个方法将从 byte 到 char 的转化使用的默认编码也是 ISO-8859-1,而我们也不能设置 Header 的其它解码格式,所以如果你设置 Header 中有非 ASCII 字符解码肯定会有乱码。
我们在添加 Header 时也是同样的道理,不要在 Header 中传递非 ASCII 字符,如果一定要传递的话,我们可以先将这些字符用 org.apache.catalina.util.URLEncoder 编码然后再添加到 Header 中,这样在浏览器到服务器的传递过程中就不会丢失信息了,如果我们要访问这些项时再按照相应的字符集解码就好了。
在前面提到了 POST 表单提交的参数的解码是在第一次调用 request.getParameter 发生的,POST 表单参数传递方式与 QueryString 不同,它是通过 HTTP 的 BODY 传递到服务端的。当我们在页面上点击 submit 按钮时浏览器首先将根据 ContentType 的 Charset 编码格式对表单填的参数进行编码然后提交到服务器端,在服务器端同样也是用 ContentType 中字符集进行解码。所以通过 POST 表单提交的参数一般不会出现问题,而且这个字符集编码是我们自己设置的,可以通过 request.setCharacterEncoding(charset) 来设置。
另外针对 multipart/form-data 类型的参数,也就是上传的文件编码同样也是使用 ContentType 定义的字符集编码,值得注意的地方是上传文件是用字节流的方式传输到服务器的本地临时目录,这个过程并没有涉及到字符编码,而真正编码是在将文件内容添加到 parameters 中,如果用这个编码不能编码时将会用默认编码 ISO-8859-1 来编码。
当用户请求的资源已经成功获取后,这些内容将通过 Response 返回给客户端浏览器,这个过程先要经过编码再到浏览器进行解码。这个过程的编解码字符集可以通过 response.setCharacterEncoding 来设置,它将会覆盖 request.getCharacterEncoding 的值,并且通过 Header 的 Content-Type 返回客户端,浏览器接受到返回的 socket 流时将通过 Content-Type 的 charset 来解码,如果返回的 HTTP Header 中 Content-Type 没有设置 charset,那么浏览器将根据 Html 的 <meta HTTP-equiv="Content-Type" content="text/html; charset=GBK" /> 中的 charset 来解码。如果也没有定义的话,那么浏览器将使用默认的编码来解码。
除了 URL 和参数编码问题外,在服务端还有很多地方可能存在编码,如可能需要读取 xml、velocity 模版引擎、JSP 或者从数据库读取数据等。
xml 文件可以通过设置头来制定编码格式
<?xml version="1.0" encoding="UTF-8"?>
Velocity 模版设置编码格式:
services.VelocityService.input.encoding=UTF-8
JSP 设置编码格式:
<%@page contentType="text/html; charset=UTF-8"%>
访问数据库都是通过客户端 JDBC 驱动来完成,用 JDBC 来存取数据要和数据的内置编码保持一致,可以通过设置 JDBC URL 来制定如
因为中文的博大精深,以及早期文件编码的不统一,造成了现在可能碰到的文件编码有GB2312、GBk、GB18030、UTF-8、BIG5等。因为编解码的知识比较底层和冷门,一直以来我对这几个编码的认知也很肤浅,很多时候也会疑惑编码名到底是大写还是小写,英文和数字之间是不是需要加“-”,规则到底是谁定的等等。
我肤浅的认知如下:
编码说明GB2312最早的简体中文编码,还有海外版的HZ-GB-2312BIG5繁体中文编码,主要用于台湾地区。些繁体中文游戏乱码,其实都是因为BIG5编码和GB2312编码的错误使用导致GBK简体+繁体,我就当它是GB2312+BIG5,非国家标准,只是中文环境内基本都遵守。后来了解到,K居然是“扩展”的拼音首字母,这很中国。。。GB18030GB家族的新版,向下兼容,最新国家标准,现在中文软件都理应支持的编码格式,文件解码的新选择UTF-8不解释了,国际化编码标准,html现在最标准的编码格式。
经过长时间的踩坑,我终于对这类知识有了一定的认知,现在把一些重要概念重新整理如下:
首先要消化整个字符编解码知识,先要明确两个概念——字符集和字符编码。
字符集
顾名思义就是字符的集合,不同的字符集最直观的区别就是字符数量不相同,常见的字符集有ASCII字符集、GB2312字符集、BIG5字符集、 GB18030字符集、Unicode字符集等。
字符编码
字符编码决定了字符集到实际二进制字节的映射方式,每一种字符编码都有自己的设计规则,例如是固定字节数还是可变长度,此处不一一展开。
常提到的GB2312、BIG5、UTF-8等,如果未特殊说明,一般语义上指的是字符编码而不是字符集。
字符集和字符编码是一对多的关系,同一字符集可以存在多个字符编码,典型代表是Unicode字符集下有UTF-8、UTF-16等等。
BOM(Byte Order Mark)
当使用windows记事本保存文件的时候,编码方式可以选择ANSI(通过locale判断,简体中文系统下是GB家族)、Unicode、Utf-8等。
为了清晰概念,需要指出此处的Unicode,编码方式其实是UTF-16LE。
有这么多编码方式,那文件打开的时候,windows系统是如何判断该使用哪种编码方式呢?
答案是:windows(例如:简体中文系统)在文件头部增加了几个字节以表示编码方式,三个字节(0xef, 0xbb, 0xbf)表示UTF-8;两个字节(0xff, 0xfe或者0xfe, 0xff)表示UTF-16(Unicode);无表示GB**。
值得注意的是,由于BOM不表意,在解析文件内容的时候应该舍弃,不然会造成解析出来的内容头部有多余的内容。
LE(little-endian)和BE(big-endian)
这个涉及到字节相关的知识了,不是本文重点,不过提到了就顺带解释下。LE和BE代表字节序,分别表示字节从低位/高位开始。
我们常接触到的CPU都是LE,所以windows里Unicode未指明字节序时默认指的是LE。
node的Buffer API中基本都有相应的2种函数来处理LE、BE,贴个文档如下:
const buf=Buffer.from([0, 5]); // Prints: 5 console.log(buf.readInt16BE()); // Prints: 1280 console.log(buf.readInt16LE());
我第一次接触到该类问题,使用的是node处理,当时给我的选择有:
由于node-iconv涉及node-gyp的build,而开发机是windows,node-gyp的环境准备以及后续的一系列安装和构建,让我这样的web开发人员痛(疯)不(狂)欲(吐)生(嘈),最后自然而然的选择了iconv-lite。
解码的处理大致示意如下:
const fs=require('fs') const iconv=require('iconv-lite') const buf=fs.readFileSync('/path/to/file') // 可以先截取前几个字节来判断是否存在BOM buf.slice(0, 3).equals(Buffer.from([0xef, 0xbb, 0xbf])) // UTF-8 buf.slice(0, 2).equals(Buffer.from([0xff, 0xfe])) // UTF-16LE const str=iconv.decode(buf, 'gbk') // 解码正确的判断需要根据业务场景调整 // 此处截取前几个字符判断是否有中文存在来确定是否解码正确 // 也可以反向判断是否有乱码存在来确定是否解码正确 // 正则表达式内常见的\u**就是unicode码点 // 该区间是常见字符,如果有特定场景可以根据实际情况扩大码点区间 /[\u4e00-\u9fa5]/.test(str.slice(0, 3))
随着ES20151的浏览器实现越来越普及,前端编解码也成为了可能。以前通过form表单上传文件至后端解析内容的流程现在基本可以完全由前端处理,既少了与后端的网络交互,而且因为有界面反馈,用户体验上更直观。
一般场景如下:
const file=document.querySelector('.input-file').files[0] const reader=new FileReader() reader.onload=()=> { const content=reader.result } reader.onprogerss=evt=> { // 读取进度 } reader.readAsText(file, 'utf-8') // encoding可修改
fileReader支持的encoding列表,可查阅此处。
这里有一个比较有趣的现象,如果文件包含BOM,比如声明是UTF-8编码,那指定的encoding会无效,而且在输出的内容中会去掉BOM部分,使用起来更方便。
如果对编码有更高要求的控制需求,可以转为输出TypedArray:
reader.onload=()=> { const buf=new Uint8Array(reader.result) // 进行更细粒度的操作 } reader.readAsArrayBuffer(file)
获取文本内容的数据缓冲以后,可以调用TextDecoder继续解码,不过需要注意的是获得的TypedArray是包含BOM的:
const decoder=new TextDecoder('gbk') const content=decoder.decode(buf)
如果文件比较大,可以使用Blob的slice来进行切割:
const file=document.querySelector('.input-file').files[0] const blob=file.slice(0, 1024)
文件的换行不同操作系统不一致,如果需要逐行解析,需要视场景而定:
**注意:**这个是各系统默认文本编辑器的规则,如果是使用其他软件,比如常用的sublime、vscode、excel等等,都是可以自行设置换行符的,一般是\n或者\r\n。
可以使用TextEncoder将字符串内容转换成TypedBuffer:
const encoder=new TextEncoder() encoder.encode(String)
值得注意的是,从Chrome 53开始,encoder只支持utf-8编码2,官方理由是其他编码用的太少了。这里有个polyfill库,补充了移除的编码格式。
前端编码完成后,一般都会顺势实现文件生成,示例代码如下:
const a=document.createElement('a') const buf=new TextEncoder() const blob=new Blob([buf.encode('我是文本')], { type: 'text/plain' }) a.download='file' a.href=URL.createObjectURL(blob) a.click() // 主动调用释放内存 URL.revokeObjectURL(blob)
这样就会生成一个文件名为file的文件,后缀由type决定。如果需要导出csv,那只需要修改对应的MIME type:
const blob=new Blob([buf.encode('第一行,1\r\n第二行,2')], { type: 'text/csv' })
一般csv都默认是由excel打开的,这时候会发现第一列的内容都是乱码,因为excel沿用了windows判断编码的逻辑(上文提到),当发现无BOM时,采用GB18030编码进行解码而导致内容乱码。
这时候只需要加上BOM指明编码格式即可:
const blob=new Blob([new Uint8Array([0xef, 0xbb, 0xbf]), buf.encode('第一行,1\r\n第二行,2')], { type: 'text/csv' }) // or const blob=new Blob([buf.encode('\ufeff第一行,1\r\n第二行,2')], { type: 'text/csv' })
这里稍微说明下,因为UTF-8和UTF-16LE都属于Unicode字符集,只是实现不同。所以通过一定的规则,两种编码可以相互转换,而表明UTF-16LE的BOM转成UTF-8编码其实就是表明UTF-8的BOM。
点赞+转发,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓-_-)
关注 {我},享受文章首发体验!
每周重点攻克一个前端技术难点。更多精彩前端内容私信 我 回复“教程”
原文链接:https://github.com/ProtoTeam/blog/blob/master/201712/3.md
作者:蚂蚁金服—数据体验技术团队
们来看看在Java 7/8中字符集编码和解码的性能。先看看下面两个String方法在不同字符集下的性能:
/* String to byte[] */
public byte[] getBytes(Charset charset);
/* byte[] to String */
public String(byte bytes[], Charset charset);
我把“Develop with pleasure”通过谷歌翻译为德语、俄语、日语和繁体中文。我们将根据这些短语构建指定大小的块,通过使用“\n”作为分隔符来连接它们直到到达指定的长度(在大多数情况下,结果会稍长一些)。在那之后我们将100M字符的byte[]数据转化为String数据(100M是Java中char字符的总长度)。我们将转换10遍以确保结果更加可靠(因此,在下表中是转换10亿字符的时间)。
我们将使用2个块的大小:100个字符用于测试短字符串转换的性能,100M字符用来测试最初的转换性能,你可以在本文末尾找到文章的源代码。我们会用UTF-8的方式与“本地化的”字符集进行比较(英语US-ASCII、德语ISO-8859-1、俄语windows-1251、日语Shift_JIS、繁体中文GB18030),将UTF-8作为通用编码时这些信息会非常很有(通常意味着更大的二进制转换开销)。我们也会对比Java 7u51和Java 8(的版本特性)。为了避免GC带来的影响,所有测试都是在我搭载Xmx32G的Xeon-2650(2.8Ghz)工作站上运行。
以下是测试结果。每个实例有两个时间结果:Java7的时间(和Java8的时间)。”UTF-8″这一行遵循了每个“本地化的”字符集,它包含从前一行数据的转换时间(例如,最后一行包括了string从繁体中文转为UTF-8的编码、解码的时间)。
Charset | getBytes, ~100 chars (chunk size) | new String, ~100 chars (chunk size) | getBytes, ~100M chars | new String, ~100M chars |
US-ASCII | 2.451 sec(2.686 sec) | 0.981 sec(0.971 sec) | 2.402 sec(2.421 sec) | 0.889 sec(0.903 sec) |
UTF-8 | 1.193 sec(1.259 sec) | 0.974 sec(1.181 sec) | 1.226 sec(1.245 sec) | 0.887 sec(1.09 sec) |
ISO-8859-1 | 2.42 sec(0.334 sec) | 0.816 sec(0.84 sec) | 2.441 sec(0.355 sec) | 0.761 sec(0.801 sec) |
UTF-8 | 3.14 sec(3.534 sec) | 3.373 sec(4.134 sec) | 3.288 sec(3.498 sec) | 3.314 sec(4.185 sec) |
windows-1251 | 5.85 sec(5.826 sec) | 2.004 sec(1.909 sec) | 5.881 sec(5.747 sec) | 1.902 sec(1.87 sec) |
UTF-8 | 5.425 sec(5.256 sec) | 11.561 sec(12.326 sec) | 5.544 sec(4.921 sec) | 11.29 sec(12.314 sec) |
Shift_JIS | 17.343 sec(9.355 sec) | 24.85 sec(8.464 sec) | 16.95 sec(9.24 sec) | 24.6 sec(8.503 sec) |
UTF-8 | 9.398 sec(13.201 sec) | 12.007 sec(16.661 sec) | 9.681 sec(11.801 sec) | 12.035 sec(16.602 sec) |
GB18030 | 18.754 sec(16.641 sec) | 15.877 sec(16.267 sec) | 18.494 sec(16.342 sec) | 16.034 sec(16.406 sec) |
UTF-8 | 9.374 sec(11.829 sec) | 12.092 sec(16.672 sec) | 9.678 sec(12.991 sec) | 12.25 sec(16.745 sec) |
测试结果
我们可以注意到以下事实:
这里几乎没有CPU开销的分块输出——如果你为这个测试分配更少的内存,那么分块结果将变得更糟。
如果是单字节字符集,那么将byte[]转换为String将非常快(US-ASCII、ISO-8859-1和windows-1251):一旦知道输入数据的大小,那么就可以分配结果中char[]的合适大小。同时,如果是在java.lang包中,可以使用一个受保护的String构造函数,这并不需要char[]的拷贝。
同时,String.getBytes(UTF-8)对于non-ASCII编码不能高效地工作——包括更复杂的映射,它分配了最大可能的char[]输出,然后复制实际使用的部分给String的返回结果。UTF-8转换中文/日文的速度确实非常慢。
如果是“本地化的”字符集,String -> byte[]的转换效率通常是低于byte[] -> String的。出人意料的是,在使用UTF-8时会观察到相反的结果:String -> byte[]普遍快于byte[] -> String。
Shift_JIS和ISO-8859-1的转换(可能也包括一些其它字符集)在Java 8中进行了极大的优化(绿色高亮):相比Java 7,Java8对日语转换的速度要快2-3倍。在ISO-8859-1的情况下,只有String -> byte[]进行了优化——它的运行速度比现在要快七倍!这个结果听起来确实令我吃惊(请接着往下看)。
一个更加明显的区别是:byte[] -> String对于windows-1251与UTF-8编码转换时间的比较(红色高亮)。它们大约相差六倍(windows-1251比UTF-8快六倍)。我不确定是否有可能证明它只是由不同的二进制表示:如果使用windows-1251,每个字符你需要1个字节的消耗;而如果使用UTF-8,对于俄语字符集则是每个字符两个字节。ISO-8859-1和UTF-8之间是有大同小异的地方的(蓝色高亮): 在德语字符串中只有一个字符不需要用2个UTF-8字符表示。而在俄语字符串中,(除空格外)几乎每个字符都需要2个UTF-8字符。
直接由 String->byte[]->String 转换为 ASCII / ISO-8859-1 数据
我尝试过研究Java 8中的ISO-8859-1编码器的表现。其算法本身非常简单,ISO-8859-1字符集完全匹配Unicode表中前255个字符的位置,所以看起来像下面这样:
if ( char <=255 )
write it as byte to output
else
skip input char, write Charset.replacement byte
Java 7 和 8中ISO_8859_1.java的不同之处,Java 7在单一方法中包含了各种优先权编码逻辑,但是Java 8提供了帮助方法(Helper Method)。当没有字符大于255时,将输入的char[]进行转换。我认为这种方法使得JIT产生更多高效的代码。
众所周知,US-ASCII或者ISO-8859-1的编码器优于JDK编码器。只需要假设字符串仅包含有效的字符编码并且避免所有的“管道(plumbing)”:
private static byte[] toAsciiBytes( final String str )
{
final byte[] res=new byte[ str.length() ];
for (int i=0; i < str.length(); i++)
res[ i ]=(byte) str.charAt( i );
return res;
}
这种方式取代了Java 8中20-25%的ISO-8859-1编码器,同时效率是Java 7的3到3.5倍。然而,它依赖JIT来进行数据访问和String.charAt的边界检查。
对于这两个数据集,取代byte[] -> String转换几乎是不可能的。因为没有公共的String构造函数或工厂方法,这将使用你提供的char[]类型。它们都进行了保护性的备份(否则将无法保证String的不变性)。性能方面最接近的是一个被弃用的String(byte ascii[], int hibyte, int offset, int count)构造函数。如果你的字符集匹配的是一个255字节的Unicode(US-ASCII, ISO-8859-1),那么对于byte[]->String编码器而言是非常有用的。不幸的是,这个构造函数从字符串结尾开始复制数据,并不像CPU缓存那么友好。
private static String asciiBytesToString( final byte[] ascii )
{
//deprecated constructor allowing data to be copied directly into String char[]. So convenient...
return new String( ascii, 0 );
}
另一方面,String(byte bytes[],int offset, int length, Charset charset)减少了所有可能的边界类型(edge):对于US-ASCII和ISO-8859-1,它分配了char[]所需的大小,进行一次低成本转换(使byte变为 char)同时提供char[]转为String构造函数的结果,在这种情况下就要信任编码器了。
总结
首选windows-1252或者Shift_JIS这样的本地字符集,其次才是UTF-8:(一般来说)它们生产更紧凑的二进制数据,并且速度比编、解码更快(在Java 7中有一些例外,但在Java 8中成为了一条规则)。
ISO-8859-1在Java 7和8中总是快于US-ASCII:如果你没有充足的理由使用US-ASCII,请选择ISO-8859-1。
你可以写一个非常快速的String->byte[]进行US-ASCII/ISO-8859-1的转换,但是你并不能取代Java解码器——它们直接访问并创建String输出。
架构师视频资料分享链接:
data:text/html;charset=UTF-8;base64,
5p625p6E5biI5a2m5Lmg5Lqk5rWB576k5Y+35pivNTc1NzUxODU0Cg==
复制粘贴在网站即可!
*请认真填写需求信息,我们会在24小时内与您取得联系。