Java 字节流与字符流的字符集编码及乱码(4)--字符

怎样算是"一个字符"?

在这一篇, 我们谈论最后一个话题, 就是"到底怎样才算一个‘字符’"?

其实这个话题在 字符集与编码(五)--代码单元及 length 方法 中和 文本在内存中的编码(1)--乱码探源(4) 也有所涉及, 这里结合字符流的话题再综合深入探讨它一下, 并且还将涉及一个 unicode 组合字符及正规化的话题. (这在前面也没有涉及过的)

怎样算是一个字符?

初看起来, 这是个很 naive 的问题. 以前面经常举的例子来说:

"h" 是一个字符;

"i" 是一个字符;

"你" 是一个字符;

"好" 也是一个字符.

继续阅读

Java 字节流与字符流的字符集编码及乱码(1)--对比

Java 的 IO 系统是比较庞杂的, 各种流特别多, 其中有一种就是字符流.

在本系列前面的一些文章中, 也曾涉及过字符流的话题, 不过没有详细展开讨论, 这次准备具体综合地谈一谈.

在层次方面的对比

你可能听过不少关于字节流与字符流对比的介绍, 不过严格地说, 我认为把"字节流"和"字符流"去对比这种说法不是特别妥当, 为什么呢?

首先, 这两种流实际上处在不同的层次, 字节流是基础, 而字符流是构建在其上的:

字节流 字符流 层级关系

对于不同层次上的事物, 我认为用"对比"这个词是不太恰当的.

继续阅读

非 BMP 字符判断及判断字符串中是否包含非 BMP 字符

判断一个字符是 BMP 字符还是非 BMP 字符(增补字符), 以及判断字符串中是否包含非 BMP 字符.

在 Java 中, 要判断一个字符是否为增补字符, 也即所谓的非 BMP 字符, 可以综合使用 String 类中的 codePointAtCharacter 类中的 isSupplementaryCodePoint 方法, 具体如下:

@Test
public void testSupplementaryCodePoint() throws Exception {
	// 一个非 BMP 字符
	String s = "𧿹";
	// 长度为 2
	assertThat(s.length()).isEqualTo(2);
	// 属于增补字符
	assertThat(Character.isSupplementaryCodePoint(s.codePointAt(0))).isTrue();
}

当然也可以简单地尝试将这个字符赋值给一个 char 变量, char 变量只能接受一个 BMP 字符, 如果是非 BMP 字符, 则编译不通过. 如果是一串的字符, 想知道其中是否存在非 BMP 字符, 则可以使用 codePointCount 方法:

@Test
public void testCodePointCount() throws Exception {
	String normalStr = "hi你";
	String strContainNoneBMP = "h𧿹你";
	
	// 两者的 length 不同
	assertThat(normalStr.length()).isEqualTo(3);
	assertThat(strContainNoneBMP.length()).isEqualTo(4);
	
	// 两者的码点数( code point count)却是相同的
	assertThat(normalStr.codePointCount(0, normalStr.length())).isEqualTo(3);
	assertThat(strContainNoneBMP.codePointCount(0, strContainNoneBMP.length())).isEqualTo(3);
}

继续阅读

Java 语言中一个字符占几个字节?

一个字符占几个字节? 很多人喜欢问这个问题, 遗憾的是他们没有意识到这其实是一个糟糕的问题, 因为它缺乏了必要的前提...

这是一个来自知乎上的问题, https://www.zhihu.com/question/27562173 , 提问者问:

Java 中理论说是一个字符(汉字 字母)占用两个字节. 但是在 UTF-8 的时候 new String("字").getBytes().length 返回的是 3 表示 3 个字节 小白求回答. .

严格地讲, 这个问题本身就是有问题的, 因为它缺少了前提, 在我的回答中我也强调了这一点, 回答具体如下:

首先, 你所谓的"字符"具体指什么呢? 如果你说的"字符"就是指 Java 中的 char, 那好, 那它就是 16 位, 2 字节.

如果你说的"字符"是指我们用眼睛看到的那些"抽象的字符", 那么, 谈论它占几个字节是没有意义的.

具体地讲, 脱离具体的编码谈某个字符占几个字节是没有意义的.

继续阅读

Java 大法好,GC 保平安——关于性能权衡(tradeoff)的一些思考

Java 之父 James Gosling 在一次接受采访谈到 C 语言时说:C 语言的一个大问题之一是“一切都会尖叫着停止”,"Everything comes to a screeching halt" is one of the big issues in C, because of the way that they do pointers……

语言的鄙视链是一直存在的,有人会以自己使用的语言更灵活,更底层,效率更高而自豪乃至鄙视其它一些语言的使用者,比如 C 语言的使用者可能看不上 Java 语言的使用者。Java 之父有一次谈到了 Java 与 C 语言的对比,他是怎么说的呢?

从 C 语言的一个大问题说起

Java 语言之父 James Gosling(高司令) 在一次接受采访谈到 C 语言时说:

C 语言的一个大问题之一是:“一切都会尖叫着停止”,源于它们使用指针的方式……

"Everything comes to a screeching halt" is one of the big issues in C, because of the way that they do pointers.

image

图片来自 wiki,为挪威表现派画家爱德华·蒙克(Edvard Munch)的作品《尖叫》(The Scream

以上言论来自《编程之魂:与27位编程语言创始人对话》Java 部分:

image

可以在 china-pub 网上免费看到 Java 的这一章节:

http://images.china-pub.com/ebook195001-200000/196657/ch12.pdf

英文版参见:https://archive.org/stream/MastermindsOfProgramming/Masterminds%20of%20Programming_djvu.txt

看待性能的一个新角度

我们经常能看到一些对于 Java 语言性能的诘难,但如果你只是看到它失去了一些性能,却无视它因此得到了什么;又或者你只看到自己喜欢的语言有很好的性能,对于它因此失去了什么却视而不见,那么这样的对比是没有意义的。

关于性能,我曾经读到过一个很不错的观点,它来自于 MIT 公开课《算法导论》中那位光头佬教授。他是这么说的:

performance is like money, it’s like currency...performance is what you use to pay for user-friendliness,it’s what you pay for security...so that’s why you want performance, because you can use it to pay these other things that you want...

性能就像钱或者说货币那样……性能可以用来换取用户友好性,可以用于换取安全……所以这就是我们追求性能的原因,因为它可以用于换取一些其它我们想要的东西……

关于性能与体验的一个具体事例

的确,单独追求性能是没有什么意义的。比如,单纯地从 0.01 秒提升到了 0.001 秒,对于用户的感受来说,他是觉得没有什么区别的,都是“一眨眼”的事情,他的体验还是一样的,0.01 秒对于他已经是足够快了。

当然,这绝不是说,性能的提升是多余的。提升到 0.001 秒后,我们可以用这点性能**交换(tradeoff)**到一些东西。

比方说,原来是命令行窗口,现在就能上 GUI 的图形界面了。这样一来呢,性能又下降了,响应时间可能又从 0.001 秒降到了 0.01 秒,不过用户操作依然感觉很顺畅,但另一方面,从命令行到图形界面,用户体验却得到了极大提升。

从某种意义上说,我们“牺牲”了性能,换来了体验更好的用户界面。这种牺牲是值得的,也是我们想要的。

当然,它的前提是性能的提升。如果在 0.01 秒时就强行上图形界面,那么响应时间可能掉到了 0.1 秒,用户就可能觉察到卡顿了,虽然提升了体验,但操作的顺畅度却下降了,可能得不偿失。

所以,这就是所谓的性能的货币效应。

牺牲部分性能,Java 换来了什么?

那么,从 Java 语言的角度看,它也确实做了很多这种交换。

像垃圾收集(GC: garbage collection)这种就不必说了,可以使得用户从繁琐的内存管理中解脱出来。

又比如数组,Java 额外做了边界检查,牺牲了一些性能,但却使得程序更为安全。如果有人无视这一点而抨击它的性能不如一些语言,则显然是不公平的。

另一方面,许多没有这种检查的语言经常饱受溢出攻击的困扰。

有些人可能会对自己的技术能力有着盲目的自信,他们会说,“我们写的程序都是很严谨的,只有小白写的程序才会出问题。”

可是,他们真的能拍着胸脯说,他们写的程序永远都不会出问题吗?没有问题也许是暂时没有被频繁使用所以没有暴露,又或者是被攻击的价值不大,没人有兴趣去挖掘那些漏洞而已。

又或者说:“没有能力才会去依靠 Java 这种傻瓜的语言”。看到越来越多的菜鸟,甚至门外汉涌入编程世界,他们觉得有些不满又带有些不屑。

在我看来,这种情绪是没有必要的。软件的极大繁荣,却恰恰在于有越来越多简单的语言拉低了编程的门槛。

如果今天还在用汇编语言,以它的高门槛,很难想象我们今天打开手机就有如此丰富多彩甚至过剩的软件可用的局面。

至于说这些语言不够轻量,不够底层,效率不高?这些都不是问题,主不在乎,消费者不在乎,你的老板也不在乎。

我们已经说过,单纯的性能其实是没有意义的。如果我们不能综合地去看待,片面地纠结一个方面的问题,自然只会得出有失偏颇的结论。