资讯专栏INFORMATION COLUMN

字符编码问题记录

SexySix / 3367人阅读

摘要:需求问题需要对序列化以后的对象中的在中进行存取由于声称只支持作为暴露出来的最基本的数据类型形式的存取所以需要在存取前后将与互相转换发现从出来的跟之前的不一样即使强制指定了一致的编码解码方式结果仍不符合预期猜测尝试怀疑是系统的默认编码方式与解

需求&问题

需要对序列化以后的对象 (java中的byte[]) 在redis中进行存取
由于redis声称只支持String(作为redis暴露出来的最基本的数据类型)形式的存取 (ref: https://redis.io/topics/internals, https://redis.io/topics/internals )
所以需要在存取前后将byte[]与String互相转换

发现从string decode出来的byte[]跟encode之前的byte[]不一样
即使强制指定了一致的编码解码方式, 结果仍不符合预期

byte[] origin = eh.toBytes(event); // serialized event

String str1 = new String(origin);
byte[] new1 = str1.getBytes();
System.out.println(Arrays.equals(origin, new1));
// output: false

String str2 = new String(origin, StandardCharsets.US_ASCII);
byte[] new2 = str2.getBytes(StandardCharsets.US_ASCII);
System.out.println(Arrays.equals(origin, new2));
// output: false

String str3 = new String(origin, StandardCharsets.UTF_8);
byte[] new3 = str3.getBytes(StandardCharsets.UTF_8);
System.out.println(Arrays.equals(origin, new3));
// output: false
猜测&尝试

怀疑是系统的默认编码方式与解码时指定的不同, 如上所示 强制指定后未果

照理说编码解码的算法是对称的, 对一个byte[]编码解码后的到byte[]理应也是一样的. 尝试使用apache的StringUtils编码解码, 结果徒然

原因&解释

经搜索试验后发现原因既与这个byte[]本身有关又与编码方式有关:

该场景中event结构中包含一个UUID, 未序列化前在java中以一个长度为32个字符的字符串表示, 例子“ce4326f3694b479dad472f250b975ee7”, 序列化后在java中为一个长度16个字节的字节数组

为了节省空间, UUID序列化的规则为: 依次将每2个字符视为一个16进制数, 将其转成对应的10进制数, 并写入一个字节空间中. 总共占16字节

一个字节占8个位, 范围为 0000 0000 ~ 1111 1111 (2进制), 00 ~ FF (16进制), 0 ~ 255 (10进制). java里的一个byte变量也能表示256种状态 (刚好相当于16进制数) 然而它的值(10进制)的范围是 -128 ~ 127, 而不是 0 ~ 255. 其中 -128 ~ -1 对应 128 ~ 255

这就导致了将序列化成byte[]以后的event encode成String的时候出现问题, 因为常用的 ASCII, UTF-8等字符集中均没有负数对应的字符. 这意味着event中UUID部分中 80 ~ FF 的值都会被无效encode

比如ASCII中这些值会默认被encode成’?’ (字符), decode成java的byte的时候就变成了63(10进制) ; 在UTF-8中更常见的情况是byte[]中的 byte序列不合法 (Invalid byte sequences) 也就是说该序列所代表的值不在UTF-8字符集支持的index范围之内. 导致了原始的byte[]和经过encode decode后的byte[]不同

Reference:
java - Encoding and decoding UTF-8 byte arrays from and to strings - Stack Overflow
java - Why are the lengths different when converting a byte array to a String and then back to a byte array? - Stack Overflow

解决方案

使用Base64安全的转换二进制与字符串, 但会使payload增加33%, 原因点此

使用 Latin-1 编码, 最大缺点是解码时对于UTF-8不兼容

直接传输二进制数据(java中的byte[]), 具体方式为使用jedis中的BinaryClient类, 其中的方法支持 byte[] 类型的参数


For anyone who’s curious enough:

显然方案3是比较理想的. 看到这里记性好的人不免发出疑问: 开头不是说redis只支持String形式的存取吗?

这里引用一段jedis的文档:

A note about String and Binary - what is native?

Redis/Jedis talks a lot about Strings. And here http://redis.io/topics/internals it says Strings are the basic building block of Redis. However, this stress on strings may be misleading. Redis" "String" refer to the C char type (8 bit), which is incompatible with Java Strings (16-bit). Redis sees only 8-bit blocks of data of predefined length, so normally it doesn"t interpret the data (it"s "binary safe"). Therefore in Java, byte[] data is "native", whereas Strings have to be encoded before being sent, and decoded after being retrieved by the SafeEncoder. This has some minor performance impact. In short: if you have binary data, don"t encode it into String, but use the binary versions.

上文提到其实redis官方文档中多次提到的string是一种误导, 原来redis所说的”String”指的是它的实现语言C中的char (8bit), 对应java中的byte (8bit), 而不是java中的String或char (16bit). Redis只按8位8位地去裸读数据, 而不去解析(所谓的”二进制安全”). 所以, 从java的角度看redis, byte[]类型才是”原生”的

Redis实现中“String”的源码:

struct sdshdr {
    long len;
    long free;
    char buf[];
};

后来想了下, 从传输层面/角度来讲, 根本就没有什么类型, 都是1 0. 应时时提醒自己跳出问题之外, 从源头思考, 避免陷入本本主义

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/36294.html

相关文章

  • 字符编码问题记录

    摘要:需求问题需要对序列化以后的对象中的在中进行存取由于声称只支持作为暴露出来的最基本的数据类型形式的存取所以需要在存取前后将与互相转换发现从出来的跟之前的不一样即使强制指定了一致的编码解码方式结果仍不符合预期猜测尝试怀疑是系统的默认编码方式与解 需求&问题 需要对序列化以后的对象 (java中的byte[]) 在redis中进行存取由于redis声称只支持String(作为redis暴露出...

    doodlewind 评论0 收藏0
  • 你真的了解 Unicode 和 UTF-8 吗?

    摘要:编码就是为了解决编码的问题而生的,它扩展自基本多文种平面中,与编码完全一致,使用两个字节表示到范围使用个字节表示编码的市场份额和比很小,在页面中只占。 引言 一直以来总是对 unicode, UTF-8 等编码知识懵懵懂懂的,尤其是在做项目过程中只要涉及到几个编码之间的转换,都得到网上搜索一番,根据别人的经验照葫芦画瓢,才能解决问题,但是私底下却完全不懂在做什么。 我再也不愿意重复这种...

    littlelightss 评论0 收藏0
  • 你真的了解 Unicode 和 UTF-8 吗?

    摘要:编码就是为了解决编码的问题而生的,它扩展自基本多文种平面中,与编码完全一致,使用两个字节表示到范围使用个字节表示编码的市场份额和比很小,在页面中只占。 引言 一直以来总是对 unicode, UTF-8 等编码知识懵懵懂懂的,尤其是在做项目过程中只要涉及到几个编码之间的转换,都得到网上搜索一番,根据别人的经验照葫芦画瓢,才能解决问题,但是私底下却完全不懂在做什么。 我再也不愿意重复这种...

    JellyBool 评论0 收藏0
  • 你真的了解 Unicode 和 UTF-8 吗?

    摘要:编码就是为了解决编码的问题而生的,它扩展自基本多文种平面中,与编码完全一致,使用两个字节表示到范围使用个字节表示编码的市场份额和比很小,在页面中只占。 引言 一直以来总是对 unicode, UTF-8 等编码知识懵懵懂懂的,尤其是在做项目过程中只要涉及到几个编码之间的转换,都得到网上搜索一番,根据别人的经验照葫芦画瓢,才能解决问题,但是私底下却完全不懂在做什么。 我再也不愿意重复这种...

    iOS122 评论0 收藏0
  • 你真的了解 Unicode 和 UTF-8 吗?

    摘要:编码就是为了解决编码的问题而生的,它扩展自基本多文种平面中,与编码完全一致,使用两个字节表示到范围使用个字节表示编码的市场份额和比很小,在页面中只占。 引言 一直以来总是对 unicode, UTF-8 等编码知识懵懵懂懂的,尤其是在做项目过程中只要涉及到几个编码之间的转换,都得到网上搜索一番,根据别人的经验照葫芦画瓢,才能解决问题,但是私底下却完全不懂在做什么。 我再也不愿意重复这种...

    afishhhhh 评论0 收藏0

发表评论

0条评论

SexySix

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<