IT牛人博客聚合网站 发现IT技术最优秀的内容, 寻找IT技术的价值 http://www.udpwork.com/ zh_CN http://www.udpwork.com/about hourly 1 Tue, 23 Oct 2018 22:28:54 +0800 <![CDATA[Docker日志太多导致磁盘占满]]> http://www.udpwork.com/item/17162.html http://www.udpwork.com/item/17162.html#reviews Tue, 23 Oct 2018 12:07:43 +0800 鸟窝 http://www.udpwork.com/item/17162.html 我有一台服务器上面部署了多个docker容器, 并且每个docker容器都往stderr中源源不断的输出日志,导致今天磁盘被占满了。搜索了一下,docker官方网站上提供了一篇解决方案的文章

Docker容器在启动/重启的时候会往/var/lib/docker中写东西,如果你在启动docker容器遇到No space left on device的问题,可以按照下面的步骤进行清理相关的日志操作。

1、 对/var/lib/docker/containers下的文件夹进行排序,看看哪个容器占用了太多的磁盘空间

$ du -d1 -h /var/lib/docker/containers | sort -h

上面的命令会按照升序的方式对于容器文件夹进行排序,并列出容器文件夹的大小:

1234567891011121314151617181920
[root@dbl14195 testnet]# du -d1 -h /var/lib/docker/containers | sort -h36K	/var/lib/docker/containers/4d91f92dd7604216f2e9e123572e9a80d7bbee3d8c8ce7be2ed241c572816fb640K	/var/lib/docker/containers/374aa0ba92b37d829012282ff15c1bb838d95dedb54589874c4285991be2d4aa40K	/var/lib/docker/containers/7cfdbc453b2f7109b52e974061754266e6cdc0ccaee62ab4a5887865113e114440K	/var/lib/docker/containers/84ee24989ad52383c6e99eaa4d236e600095aa7f855e81fbafe10416b75ceefb40K	/var/lib/docker/containers/aeced3ef3e23df27e52f65743bb05448b46a2c660acc5b0aab12604e060779b440K	/var/lib/docker/containers/c36722baf0d2e1c22b7dde9979665ab62cd8ab85c3f1d0f427bb7a34e0fd977a44K	/var/lib/docker/containers/62477b332d18e192d70c7420435d47a379e6bbd8de13da8a8762e0fd95b341ca44K	/var/lib/docker/containers/78da0cf9743b6940fabbbd8c574b99dc5deb642fa998a8f819a6c6978fc875d744K	/var/lib/docker/containers/9f63daf7caa7c469385bed4b178fbfe662e15b8c569c6644081af090f8e4042644K	/var/lib/docker/containers/e2d1286119a45aac7e58d6dac6e4b44b1d1288799b735943be45abed50244e5656K	/var/lib/docker/containers/ebd1bd211a1b9d02bb39bfb80eec3d0960a5b25e18f54d7371781ec456e7a1e8176K	/var/lib/docker/containers/1fe0a241e5ce9726c547c68739793633f9dd906768a36fe80e8fb80373aa3bfb17M	/var/lib/docker/containers/ac30e68d454b37d22b3964053a2b52ba043baa1add13556a09c0e3e05589104f25M	/var/lib/docker/containers/872ca4e3d005594591ca2df0e832d36eef448981ab2820c69df4ff1399f8423e25M	/var/lib/docker/containers/bd49a0a0368b99a9f69981d8b921ea1830957451577b635a07d5425d48e1144b30M	/var/lib/docker/containers/8f732390a020a6ef647fabb04da32c87d6341b72ac2af6bb4a1cf5743fda54db88M	/var/lib/docker/containers/648e883aa0a93f696f64e4ab76434657f4845769fe1eaaad49c2dc1d7960f2b0171M	/var/lib/docker/containers/8de7ff9f0276586a6ab346c2be1c9dc879bbb0d795fa7776c1d8d1568ea2794a354M	/var/lib/docker/containers

2、选择你要清理的容器进行清理

1
$ cat /dev/null > /var/lib/docker/containers/container_id/container_log_name

上述命令会清空对应的日志,如:

1
cat /dev/null > /var/lib/docker/containers/374aa0ba92b37d829012282ff15c1bb838d95dedb54589874c4285991be2d4aa/374aa0ba92b37d829012282ff15c1bb838d95dedb54589874c4285991be2d4aa-json.log

3、限制日志文件的大小

启动容器时,可以通过参数设置日志文件的大小、日志文件的格式

1
docker run -it --log-opt max-size=10m --log-opt max-file=3 alpine ash
]]>
我有一台服务器上面部署了多个docker容器, 并且每个docker容器都往stderr中源源不断的输出日志,导致今天磁盘被占满了。搜索了一下,docker官方网站上提供了一篇解决方案的文章

Docker容器在启动/重启的时候会往/var/lib/docker中写东西,如果你在启动docker容器遇到No space left on device的问题,可以按照下面的步骤进行清理相关的日志操作。

1、 对/var/lib/docker/containers下的文件夹进行排序,看看哪个容器占用了太多的磁盘空间

$ du -d1 -h /var/lib/docker/containers | sort -h

上面的命令会按照升序的方式对于容器文件夹进行排序,并列出容器文件夹的大小:

1234567891011121314151617181920
[root@dbl14195 testnet]# du -d1 -h /var/lib/docker/containers | sort -h36K	/var/lib/docker/containers/4d91f92dd7604216f2e9e123572e9a80d7bbee3d8c8ce7be2ed241c572816fb640K	/var/lib/docker/containers/374aa0ba92b37d829012282ff15c1bb838d95dedb54589874c4285991be2d4aa40K	/var/lib/docker/containers/7cfdbc453b2f7109b52e974061754266e6cdc0ccaee62ab4a5887865113e114440K	/var/lib/docker/containers/84ee24989ad52383c6e99eaa4d236e600095aa7f855e81fbafe10416b75ceefb40K	/var/lib/docker/containers/aeced3ef3e23df27e52f65743bb05448b46a2c660acc5b0aab12604e060779b440K	/var/lib/docker/containers/c36722baf0d2e1c22b7dde9979665ab62cd8ab85c3f1d0f427bb7a34e0fd977a44K	/var/lib/docker/containers/62477b332d18e192d70c7420435d47a379e6bbd8de13da8a8762e0fd95b341ca44K	/var/lib/docker/containers/78da0cf9743b6940fabbbd8c574b99dc5deb642fa998a8f819a6c6978fc875d744K	/var/lib/docker/containers/9f63daf7caa7c469385bed4b178fbfe662e15b8c569c6644081af090f8e4042644K	/var/lib/docker/containers/e2d1286119a45aac7e58d6dac6e4b44b1d1288799b735943be45abed50244e5656K	/var/lib/docker/containers/ebd1bd211a1b9d02bb39bfb80eec3d0960a5b25e18f54d7371781ec456e7a1e8176K	/var/lib/docker/containers/1fe0a241e5ce9726c547c68739793633f9dd906768a36fe80e8fb80373aa3bfb17M	/var/lib/docker/containers/ac30e68d454b37d22b3964053a2b52ba043baa1add13556a09c0e3e05589104f25M	/var/lib/docker/containers/872ca4e3d005594591ca2df0e832d36eef448981ab2820c69df4ff1399f8423e25M	/var/lib/docker/containers/bd49a0a0368b99a9f69981d8b921ea1830957451577b635a07d5425d48e1144b30M	/var/lib/docker/containers/8f732390a020a6ef647fabb04da32c87d6341b72ac2af6bb4a1cf5743fda54db88M	/var/lib/docker/containers/648e883aa0a93f696f64e4ab76434657f4845769fe1eaaad49c2dc1d7960f2b0171M	/var/lib/docker/containers/8de7ff9f0276586a6ab346c2be1c9dc879bbb0d795fa7776c1d8d1568ea2794a354M	/var/lib/docker/containers

2、选择你要清理的容器进行清理

1
$ cat /dev/null > /var/lib/docker/containers/container_id/container_log_name

上述命令会清空对应的日志,如:

1
cat /dev/null > /var/lib/docker/containers/374aa0ba92b37d829012282ff15c1bb838d95dedb54589874c4285991be2d4aa/374aa0ba92b37d829012282ff15c1bb838d95dedb54589874c4285991be2d4aa-json.log

3、限制日志文件的大小

启动容器时,可以通过参数设置日志文件的大小、日志文件的格式

1
docker run -it --log-opt max-size=10m --log-opt max-file=3 alpine ash
]]>
0
<![CDATA[保罗·艾伦的故事]]> http://www.udpwork.com/item/17163.html http://www.udpwork.com/item/17163.html#reviews Tue, 23 Oct 2018 07:57:27 +0800 阮一峰 http://www.udpwork.com/item/17163.html 上周,保罗·艾伦逝世。《财新周刊》约我写一篇纪念文章,发表在他们杂志上面。

1、

10月2日,微软公司的共同创始人保罗·艾伦(Paul Allen)发了一条推特,宣布他的癌症复发了。

一些个人新闻:最近,我了解到我在2009年与之抗争的非霍奇金淋巴瘤已经复发。我已经开始治疗,我的医生很乐观,认为我会有一个好结果。我很欣慰得到的支持,应对挑战时,我将依靠这些支持。

那时,他显然是乐观的。但是,两周后的10月16日,就传出了他去世的消息,享年65岁。看来两周里,他的病情急转直下。不过也有可能,癌症早就复发了,他只是在两周前宣布而已。

非霍奇金淋巴瘤是一种血液癌症,2009年保罗艾伦得到了成功治疗,但是九年后癌症还是复发了。它导致血液里的淋巴细胞异常,而淋巴细胞是人体的免疫屏障,所以患者的免疫能力很差,特别容易发生感染,大部分死者不是死于癌症本身,而是死于并发症,保罗就是如此。

1982年,保罗29岁的时候,就得过另一种血液癌症----霍奇金淋巴瘤。这种癌症比较容易缓解,对于年轻人,五年生存率可以达到97%。那时,经过几个月的放射治疗和骨髓移植,保罗成功康复了。

据说,由于年轻时就得了癌症,保罗意识到他随时都可能死亡,所以终生没有结婚,也没有子女。全世界富豪排行榜上,他排在第40位,有200多亿美元的遗产,现在都由他的姐姐继承。

2、

1953年1月21日,保罗出生在华盛顿州的首府西雅图。他一生都住在这里,小学、中学和大学都是在西雅图。中间只离开了两年,去波士顿当程序员,后来就回来创建微软公司,微软的总部就在西雅图。

保罗的爸爸是华盛顿州立大学的图书馆副馆长,一直当了22年(1960--1982)。由于这个原因,保罗很小就能接触到大学里面的计算机设备。当时计算机是非常昂贵的机器,个人不可能购买,这对培养他的计算机兴趣起了关键作用。后来,保罗向华盛顿大学捐款4000万美元,新的图书馆大楼就叫做艾伦图书馆,纪念他的爸爸。

初三时,保罗遇到了比尔盖茨。盖茨比保罗小两岁,两人经常一起讨论计算机,研究编程。盖茨后来回忆说:

"我七年级时遇见保罗,这改变了我的人生。他在学校比我高两个年级,个子特别高,是个众所周知的计算机天才。我们开始在一起混,特别是在我们的学校有了第一台电脑之后。我们几乎把所有闲暇时间都消磨在任何我们可以接触到的电脑上。

保罗预见到计算机会改变世界。即使还在高中,在我们中还没人知道个人电脑是什么之前,他就预测到计算机芯片会变得超级强大,而且最终会带来一个崭新的行业。我们一起做的所有事情,都是基于他的这一洞察力。"

中学毕业后,保罗进了他爸爸所在的华盛顿州立大学,学习计算机科学,盖茨进了波士顿的哈佛大学。两年后,保罗辍学,去波士顿的霍尼韦尔公司当程序员。两人又在波士顿相聚了。

3、

1975年1月的一天,盖茨正在宿舍睡觉,保罗闯了进来,把他拉到哈佛大学门口广场的报摊上,给他看刚出版的一月号的《大众电子》(Popular Electronics)杂志,封面是一台崭新的 Altair 8800 电脑。

以前,电脑都是大型设备,体积再小的型号,也有冰柜那样大小。但是,这时集成电路和芯片技术出现了突破,微型电脑开始萌芽,大小跟电视机差不多。这意味着个人电脑的时代很快就要来临。

保罗看到了这个趋势,认定从现在开始,他们就应该为个人电脑写软件。他说服盖茨跟他一起为 Altair 8800 写一个 BASIC 语言解释器,然后又说服 Altair 8800 的制造商 MIPS 公司买下这个 BASIC 解释器。由于这笔交易成功,盖茨就从哈佛大学退学了,跟着保罗一起创业了。

1975年4月4日,他们两人成立了微软公司。1980年11月,IBM 公司跟微软签订合同,让微软供应 IBM 个人机的操作系统,也就是后来的 MS-DOS 系统,同时还允许微软把这个系统卖给其他厂商。从此,微软取得了个人电脑操作系统的垄断地位,直到今天。

盖茨后来说:"我从大学退学,以及微软的成立,它们的发生都是因为保罗。"

4、

1982年,保罗得了癌症,这时他与盖茨发生了冲突。他在回忆录写道:

"1982年12月下旬的一个晚上,我听到比尔(盖茨)和史蒂夫(鲍尔默)在比尔的办公室里激烈地说话,就停下来听。很容易听到谈话的要点,他们正在哀叹我最近缺乏生产力,并讨论如何通过向自己和其他股东发行股票,来稀释我的微软股权。显然一段时间以来,他们一直在考虑这个问题。"

保罗冲进房间,打断了对话,盖茨和鲍尔默只好道歉。保罗感叹道:"我曾帮助创办公司,虽然得病了,仍然是管理层的积极成员。可是,现在我的伙伴和我的同事都计划摆脱我,这是对待雇佣军的做法,就是这么回事。"

据报道,盖茨要求保罗出让一些微软的股票给他,盖茨拿6成,保罗拿4成,以弥补盖茨为公司的更多投入,保罗同意了。后来,盖茨又尝试把股票比例改成,他拿64%,保罗拿34%。

1983年,保罗离开微软公司,但是保留了股票。盖茨试图以每股5美元的价格收购保罗的股票,保罗拒绝了。后来微软股票上市,这个决定对他成为亿万富翁至关重要。此后,保罗一直在微软的董事会任职,直到2000年11月9日退出微软董事会。直到2014年,他仍然是微软高级战略顾问, 持有1亿股微软股份。

他与盖茨的关系,经过一段时间的冷淡,还是和好了。

5、

离开微软公司以后,保罗大量投资,涉入各种产业,他投资房产、橄榄球队、篮球队、足球队、有线电视网络、航空业、电影业等等。同时,他还对科学研究大量捐款,成立了艾伦脑科学研究所、艾伦人工智能研究所、艾伦细胞科学研究所,并支持野生动物和环境保护。

保罗承诺,他死后至少捐掉一半的资产。他是美国当代最大的慈善家之一。

如果你现在去西雅图,到处可以看到保罗的痕迹。他建立了流行文件博物馆、计算机博物馆,创办画廊和音乐节。

(图片说明:保罗艾伦出资建立的西雅图流行文化博物馆)

现在他不在了,但是他支持的这些事业还会长久地存在。正如盖茨在悼念文章中所说:"保罗应当活得更久一些,他一定会充分利用那些多出来的时间。我将非常地怀念他。"

(完)

文档信息

]]>
上周,保罗·艾伦逝世。《财新周刊》约我写一篇纪念文章,发表在他们杂志上面。

1、

10月2日,微软公司的共同创始人保罗·艾伦(Paul Allen)发了一条推特,宣布他的癌症复发了。

一些个人新闻:最近,我了解到我在2009年与之抗争的非霍奇金淋巴瘤已经复发。我已经开始治疗,我的医生很乐观,认为我会有一个好结果。我很欣慰得到的支持,应对挑战时,我将依靠这些支持。

那时,他显然是乐观的。但是,两周后的10月16日,就传出了他去世的消息,享年65岁。看来两周里,他的病情急转直下。不过也有可能,癌症早就复发了,他只是在两周前宣布而已。

非霍奇金淋巴瘤是一种血液癌症,2009年保罗艾伦得到了成功治疗,但是九年后癌症还是复发了。它导致血液里的淋巴细胞异常,而淋巴细胞是人体的免疫屏障,所以患者的免疫能力很差,特别容易发生感染,大部分死者不是死于癌症本身,而是死于并发症,保罗就是如此。

1982年,保罗29岁的时候,就得过另一种血液癌症----霍奇金淋巴瘤。这种癌症比较容易缓解,对于年轻人,五年生存率可以达到97%。那时,经过几个月的放射治疗和骨髓移植,保罗成功康复了。

据说,由于年轻时就得了癌症,保罗意识到他随时都可能死亡,所以终生没有结婚,也没有子女。全世界富豪排行榜上,他排在第40位,有200多亿美元的遗产,现在都由他的姐姐继承。

2、

1953年1月21日,保罗出生在华盛顿州的首府西雅图。他一生都住在这里,小学、中学和大学都是在西雅图。中间只离开了两年,去波士顿当程序员,后来就回来创建微软公司,微软的总部就在西雅图。

保罗的爸爸是华盛顿州立大学的图书馆副馆长,一直当了22年(1960--1982)。由于这个原因,保罗很小就能接触到大学里面的计算机设备。当时计算机是非常昂贵的机器,个人不可能购买,这对培养他的计算机兴趣起了关键作用。后来,保罗向华盛顿大学捐款4000万美元,新的图书馆大楼就叫做艾伦图书馆,纪念他的爸爸。

初三时,保罗遇到了比尔盖茨。盖茨比保罗小两岁,两人经常一起讨论计算机,研究编程。盖茨后来回忆说:

"我七年级时遇见保罗,这改变了我的人生。他在学校比我高两个年级,个子特别高,是个众所周知的计算机天才。我们开始在一起混,特别是在我们的学校有了第一台电脑之后。我们几乎把所有闲暇时间都消磨在任何我们可以接触到的电脑上。

保罗预见到计算机会改变世界。即使还在高中,在我们中还没人知道个人电脑是什么之前,他就预测到计算机芯片会变得超级强大,而且最终会带来一个崭新的行业。我们一起做的所有事情,都是基于他的这一洞察力。"

中学毕业后,保罗进了他爸爸所在的华盛顿州立大学,学习计算机科学,盖茨进了波士顿的哈佛大学。两年后,保罗辍学,去波士顿的霍尼韦尔公司当程序员。两人又在波士顿相聚了。

3、

1975年1月的一天,盖茨正在宿舍睡觉,保罗闯了进来,把他拉到哈佛大学门口广场的报摊上,给他看刚出版的一月号的《大众电子》(Popular Electronics)杂志,封面是一台崭新的 Altair 8800 电脑。

以前,电脑都是大型设备,体积再小的型号,也有冰柜那样大小。但是,这时集成电路和芯片技术出现了突破,微型电脑开始萌芽,大小跟电视机差不多。这意味着个人电脑的时代很快就要来临。

保罗看到了这个趋势,认定从现在开始,他们就应该为个人电脑写软件。他说服盖茨跟他一起为 Altair 8800 写一个 BASIC 语言解释器,然后又说服 Altair 8800 的制造商 MIPS 公司买下这个 BASIC 解释器。由于这笔交易成功,盖茨就从哈佛大学退学了,跟着保罗一起创业了。

1975年4月4日,他们两人成立了微软公司。1980年11月,IBM 公司跟微软签订合同,让微软供应 IBM 个人机的操作系统,也就是后来的 MS-DOS 系统,同时还允许微软把这个系统卖给其他厂商。从此,微软取得了个人电脑操作系统的垄断地位,直到今天。

盖茨后来说:"我从大学退学,以及微软的成立,它们的发生都是因为保罗。"

4、

1982年,保罗得了癌症,这时他与盖茨发生了冲突。他在回忆录写道:

"1982年12月下旬的一个晚上,我听到比尔(盖茨)和史蒂夫(鲍尔默)在比尔的办公室里激烈地说话,就停下来听。很容易听到谈话的要点,他们正在哀叹我最近缺乏生产力,并讨论如何通过向自己和其他股东发行股票,来稀释我的微软股权。显然一段时间以来,他们一直在考虑这个问题。"

保罗冲进房间,打断了对话,盖茨和鲍尔默只好道歉。保罗感叹道:"我曾帮助创办公司,虽然得病了,仍然是管理层的积极成员。可是,现在我的伙伴和我的同事都计划摆脱我,这是对待雇佣军的做法,就是这么回事。"

据报道,盖茨要求保罗出让一些微软的股票给他,盖茨拿6成,保罗拿4成,以弥补盖茨为公司的更多投入,保罗同意了。后来,盖茨又尝试把股票比例改成,他拿64%,保罗拿34%。

1983年,保罗离开微软公司,但是保留了股票。盖茨试图以每股5美元的价格收购保罗的股票,保罗拒绝了。后来微软股票上市,这个决定对他成为亿万富翁至关重要。此后,保罗一直在微软的董事会任职,直到2000年11月9日退出微软董事会。直到2014年,他仍然是微软高级战略顾问, 持有1亿股微软股份。

他与盖茨的关系,经过一段时间的冷淡,还是和好了。

5、

离开微软公司以后,保罗大量投资,涉入各种产业,他投资房产、橄榄球队、篮球队、足球队、有线电视网络、航空业、电影业等等。同时,他还对科学研究大量捐款,成立了艾伦脑科学研究所、艾伦人工智能研究所、艾伦细胞科学研究所,并支持野生动物和环境保护。

保罗承诺,他死后至少捐掉一半的资产。他是美国当代最大的慈善家之一。

如果你现在去西雅图,到处可以看到保罗的痕迹。他建立了流行文件博物馆、计算机博物馆,创办画廊和音乐节。

(图片说明:保罗艾伦出资建立的西雅图流行文化博物馆)

现在他不在了,但是他支持的这些事业还会长久地存在。正如盖茨在悼念文章中所说:"保罗应当活得更久一些,他一定会充分利用那些多出来的时间。我将非常地怀念他。"

(完)

文档信息

]]>
0
<![CDATA[How could it possible to assign an integer to string?]]> http://www.udpwork.com/item/17161.html http://www.udpwork.com/item/17161.html#reviews Mon, 22 Oct 2018 11:12:07 +0800 Robin Dong http://www.udpwork.com/item/17161.html The snippet below could be compiled and run:

#include <map>
#include <string>
#include <iostream>

using namespace std;

int main(void) {
  std::map<std::string, std::string> hmap;
  hmap["a"] = "apple";
  hmap["banana"] = 1;
  for (auto item : hmap) {
    std::cout << "[" << item.first << "]" << "{" << item.second << "}\n";
  }
}

The result is:

[a]{apple}
[banana]{}

I noticed that the corresponding value of key ‘banana’ is empty. The reason is I assign an integer directly to key ‘banana’ by mistake. But how could c++ compiler allow me to do this? Why doesn’t it report a compiling error?
To reveal the truth, I write another snippet:

std::string hello;
hello = 123;

This code could also be compiled correctly!
Then I change my code to:

std::string hello = 123;

This time, the compiler complained that

map.cpp:6:23: error: invalid conversion from ‘int’ to ‘const char*’ [-fpermissive]

Seems the std::string ‘s constructor and assignment operator have totally different behavier.
After checking the document, I found the reason: std::string has assignment operator for ‘char’ !(ref)

string& operator= (char c);

Thus we should be much more carefully when assign number to std::string.

]]>
The snippet below could be compiled and run:

#include <map>
#include <string>
#include <iostream>

using namespace std;

int main(void) {
  std::map<std::string, std::string> hmap;
  hmap["a"] = "apple";
  hmap["banana"] = 1;
  for (auto item : hmap) {
    std::cout << "[" << item.first << "]" << "{" << item.second << "}\n";
  }
}

The result is:

[a]{apple}
[banana]{}

I noticed that the corresponding value of key ‘banana’ is empty. The reason is I assign an integer directly to key ‘banana’ by mistake. But how could c++ compiler allow me to do this? Why doesn’t it report a compiling error?
To reveal the truth, I write another snippet:

std::string hello;
hello = 123;

This code could also be compiled correctly!
Then I change my code to:

std::string hello = 123;

This time, the compiler complained that

map.cpp:6:23: error: invalid conversion from ‘int’ to ‘const char*’ [-fpermissive]

Seems the std::string ‘s constructor and assignment operator have totally different behavier.
After checking the document, I found the reason: std::string has assignment operator for ‘char’ !(ref)

string& operator= (char c);

Thus we should be much more carefully when assign number to std::string.

]]>
0
<![CDATA[先胜后战之道 - 读《华杉讲透孙子兵法》]]> http://www.udpwork.com/item/17160.html http://www.udpwork.com/item/17160.html#reviews Sun, 21 Oct 2018 23:38:12 +0800 唐巧 http://www.udpwork.com/item/17160.html 引言

最近读完了《华杉讲透孙子兵法》,作者华杉是华与华营销咨询公司的董事长。

我们其实身边有不少华与华的营销作品,比如西北莜面村的「I love 莜」的设计,以及前阵子引起话题争议的「得到 App」的新 Logo 设计,都是出自华与华公司。

先胜后战

对于我个人来说,这本书的价值在于纠正了我多年以来对于《孙子兵法》的误解,我想这个误解也在很多人心中,即:认为孙子兵法是讲打仗时的各种奇技淫巧的计谋。

确实,在《孙子兵法》中有不少打仗时的战术。但是,华杉告诉我们,其实比起那些计谋来说,《孙子兵法》更强调的是战争之前的准备,所谓 “先胜而后战”,确定这个仗能打赢,才打。打不赢的仗,坚决不打。

什么样的仗打不赢呢?最常见的就是以少对多的仗。所以别看历史上有不少以少胜多的典故,那些故事能够成为典故,本身就说明是例外的情况,大部分的情况都是以多胜少。所以,侥幸心理不能有。

看完华杉的解说,我发现其实《孙子兵法》其实是一本介绍如何管理军队的管理学著作。其中的打仗部分,只是管理军队这个团队的一个业务。军队能够打好仗,很多细节都是在战斗环节之外的。

比如后勤管理,在古代战争中就特别关键。因为送两车粮食到前线,成本是八车粮食。为什么这么多?因为运粮食的车一路运一路需要吃粮食,运到前线,已经吃掉四车了,还需要留四车,这样他们才能一路回去。你看,十几万大军远征,如果管理不好后勤,可能都不需要战争军队就因为没有粮食而哗变了。

类似的这种基本的军队管理工作,《孙子兵法》里面分为「五事七计」,五事,是道、天、地、将、法。七计,是:主孰有道?将孰有能?天地孰得?法令孰行?兵众孰强?士卒孰练?赏罚孰明?

有了以上这些计算,判断战争有得打,打得赢,才会兴帅动众,去战场上见分晓。

如何评价一个创业公司

其实拿《孙子兵法》的「五事七计」,我们也可以用来评价一个创业公司的能力:

  • 主孰有道?就是判断这家公司的 CEO 及联合创始人是否靠谱。
  • 将孰有能?核心管理团队是否靠谱。
  • 天地孰得?创业的环境怎么样。竞争对手如何,融资环境怎么样。
  • 法令孰行?团队的内部规章制度、人事架构是否规范。
  • 兵众孰强?员工的能力如何。
  • 士卒孰练?员工对于工作是否熟练。
  • 赏罚孰明?团队的激励是否做得恰当。

这就和我们现在做的 SWOT 分析一样,在商业竞争上做详细的分析判断,然后看成功的机率有多大。

学会等待

如果在战争中用 SWOT 分析,发现胜算不大怎么办?那最好的办法就是等。作为防守的一方,可以坚守城门而不出,作为进攻的一方,可以围城而不攻。双方消耗资源,慢慢地形势就会发生变化,等确定能打赢了,再开战。

历史当中,没有耐心等待的人,都会导致失败。安史之乱,哥舒翰守潼关,他坚守半年不战,唐玄宗却要逼他出战。结果哥舒翰被迫出战,全军覆没,长安失守,唐玄宗南逃四川。

我们创业也是这样,时机很重要。有些时候就得耐心地等待好的机会。

炒股也是这样,一支股票能够放几年不卖的,如果不是被套牢了,都是高手。

领导要自己负决策责任

古代的有些皇帝,决策错误了,就怪谋臣,把谋臣杀了。但是其实很多事情,决策权其实都是在皇帝手中,他心中的想法,还是自己决定的。

曾国藩总结说:「大抵失败而归咎于谋主者,庸人之恒情也。」

在公司当负责人也一样,如果一件事情你是负责人,那么决策错了就是错了。如果是因为你不知情,那你应该反醒看看是不是没有建立好沟通机制,或者是否授权过多给了能力不够强的人。如果你知情,那你应该反思为什么当时没有做好判断,不管别人怎么说,毕竟负责人是有最终决策权的。

差的负责人总是把责任推给别人,自己没有任何错误。

好的负责人应该把所有错误都揽在自己身上,从自己身上找原因。即使是别人犯的错,也是你招聘、用人的问题。

皇上可以不懂军事,将军不能不懂政治

CEO 可以不懂技术,CTO 不能不懂得沟通。

对于 CEO 来说,不懂技术就最好不要插手技术。最著名的例子,就是长平之战,赵王不懂军事,但是却把廉颇换成了只会纸上谈兵的赵括。结果断送了赵国四十万人的性命。

对于 CTO 来说,应该需要学会沟通。人们都知道一山不容二虎,岳飞却有「迎还二帝」的雄心,这让高宗怎么能接受?回来两个皇帝,难道让高宗退位吗?岳飞虽然留得一世美名,但是他还是死了,宋朝还是亡了,从结果来看没有意义。

我身边有一些朋友创业,遇到 CEO 不能理解 CTO,于是两年换一个 CTO。CTO 一换,技术核心班子跟着换,于是技术的积累完全做不起来。技术债务越来越深,最终谁也搞不定了。

很多 CTO 也有问题,不懂得向上管理,明明做得很好,但是因为 CEO 很难理解技术细节,只能以线上事故数量、团队加不加班、技术团队的薪资成本来考量 CTO 的水平。最终即使什么事故都没出,也会被认为没有感受到什么价值但是花很多钱而被换掉。技术部门通常拿的薪资确实偏高,如果 CTO 不能很好地和 CEO 相处,那么整个业务的风险都很大。

所以,各位 CTO 还是应该想办法,和 CEO 建立起相互的信任关系,有了任何关系,业务才能稳固发展。

胜者无名

通常最厉害的人,你是感受不到他的厉害的。因为他不但会收起自己的锋芒,还会把各种问题提前解决,让你感觉不到困难。

书中举了扁鹊的故事。

一天,魏文王问扁鹊:“你们家兄弟三人,都精于医术,到底哪一位最好呢 ?”

扁鹊答:“我的大哥医术最好,二哥次之,我最差。”

文王再问:“那么为什么你最出名呢 ?”

扁鹊答道:我大哥治病,是治病于病情发作之前的时候,由于一般人不知道他能事先铲除病因,反而觉得他的治疗没什么明显的效果,所以他的名气无法传出去,只有我们家的人才知道。

我二哥治病,是治病于病情初起的时候,看上去以为他只能治轻微的小病,所以他的名气只能在我们乡里流传。

而我扁鹊治病,是治病于病情已经严重的时候。一般人看到我在经脉上穿针放血,在皮肤上敷药,用麻药让人昏迷,做的都是些不可思议的大手术,自然以为我的医术高明,因此名气响遍全国,远远大于我的两位哥哥。

其实技术团队的工作也是这样,做得好,大家感受不到你的辛苦,业务每周按时上线。做得不好,团队总是在救火。

类似的工作还包括安全工作,做得最好的安全工作,就是不出安全事故,让安全部门没有存在感。

自知之明

孙子讲「知己知彼,百战不殆」,其实强调的是知己。做到了知己,至少赢面就是 50%,很多人都是不知己,才输了战争。

做企业也是这样,很多时候,盯着竞争对手看,还不如盯着自己的用户看。解决好用户需求,自然竞争力就上去了。盯着竞争对手看,还不如盯着市场环境看,寻找新的商业机会,比和竞争对手抢小蛋糕更有价值。

如何知己呢?华彬在书中介绍了 19 世纪瑞士军事家若米尼的五个层次:

  1. 政策。我是谁,我代表谁的利益,我的行事方针。
  2. 战略。我在哪儿,我要去哪儿,怎么去。
  3. 大战术。核心竞争方法论。《孙子兵法》里的以正合,以奇胜,就是它的核心战术。
  4. 战争勤务。后勤保障。
  5. 工程艺术。对筑垒要点的攻守艺术。
  6. 基础战术。具体细节工作怎么做。

讲概率而不讲孤立事件

好的方法论不是总是对,而是在概率上极大可能对。最终打仗其实就是拼实力,实力强的不一定赢,但是胜率高。

书中的例子是这个:

有些时候炒股、风投、决策也是这样,不能害怕做出错误决策。我们追求的不是决策 100% 正确,而是追求决策整体收益最大化。如果多做一些决策,有一些失败,但是整体是收益最大的,其实也是好的策略。

]]>
引言

最近读完了《华杉讲透孙子兵法》,作者华杉是华与华营销咨询公司的董事长。

我们其实身边有不少华与华的营销作品,比如西北莜面村的「I love 莜」的设计,以及前阵子引起话题争议的「得到 App」的新 Logo 设计,都是出自华与华公司。

先胜后战

对于我个人来说,这本书的价值在于纠正了我多年以来对于《孙子兵法》的误解,我想这个误解也在很多人心中,即:认为孙子兵法是讲打仗时的各种奇技淫巧的计谋。

确实,在《孙子兵法》中有不少打仗时的战术。但是,华杉告诉我们,其实比起那些计谋来说,《孙子兵法》更强调的是战争之前的准备,所谓 “先胜而后战”,确定这个仗能打赢,才打。打不赢的仗,坚决不打。

什么样的仗打不赢呢?最常见的就是以少对多的仗。所以别看历史上有不少以少胜多的典故,那些故事能够成为典故,本身就说明是例外的情况,大部分的情况都是以多胜少。所以,侥幸心理不能有。

看完华杉的解说,我发现其实《孙子兵法》其实是一本介绍如何管理军队的管理学著作。其中的打仗部分,只是管理军队这个团队的一个业务。军队能够打好仗,很多细节都是在战斗环节之外的。

比如后勤管理,在古代战争中就特别关键。因为送两车粮食到前线,成本是八车粮食。为什么这么多?因为运粮食的车一路运一路需要吃粮食,运到前线,已经吃掉四车了,还需要留四车,这样他们才能一路回去。你看,十几万大军远征,如果管理不好后勤,可能都不需要战争军队就因为没有粮食而哗变了。

类似的这种基本的军队管理工作,《孙子兵法》里面分为「五事七计」,五事,是道、天、地、将、法。七计,是:主孰有道?将孰有能?天地孰得?法令孰行?兵众孰强?士卒孰练?赏罚孰明?

有了以上这些计算,判断战争有得打,打得赢,才会兴帅动众,去战场上见分晓。

如何评价一个创业公司

其实拿《孙子兵法》的「五事七计」,我们也可以用来评价一个创业公司的能力:

  • 主孰有道?就是判断这家公司的 CEO 及联合创始人是否靠谱。
  • 将孰有能?核心管理团队是否靠谱。
  • 天地孰得?创业的环境怎么样。竞争对手如何,融资环境怎么样。
  • 法令孰行?团队的内部规章制度、人事架构是否规范。
  • 兵众孰强?员工的能力如何。
  • 士卒孰练?员工对于工作是否熟练。
  • 赏罚孰明?团队的激励是否做得恰当。

这就和我们现在做的 SWOT 分析一样,在商业竞争上做详细的分析判断,然后看成功的机率有多大。

学会等待

如果在战争中用 SWOT 分析,发现胜算不大怎么办?那最好的办法就是等。作为防守的一方,可以坚守城门而不出,作为进攻的一方,可以围城而不攻。双方消耗资源,慢慢地形势就会发生变化,等确定能打赢了,再开战。

历史当中,没有耐心等待的人,都会导致失败。安史之乱,哥舒翰守潼关,他坚守半年不战,唐玄宗却要逼他出战。结果哥舒翰被迫出战,全军覆没,长安失守,唐玄宗南逃四川。

我们创业也是这样,时机很重要。有些时候就得耐心地等待好的机会。

炒股也是这样,一支股票能够放几年不卖的,如果不是被套牢了,都是高手。

领导要自己负决策责任

古代的有些皇帝,决策错误了,就怪谋臣,把谋臣杀了。但是其实很多事情,决策权其实都是在皇帝手中,他心中的想法,还是自己决定的。

曾国藩总结说:「大抵失败而归咎于谋主者,庸人之恒情也。」

在公司当负责人也一样,如果一件事情你是负责人,那么决策错了就是错了。如果是因为你不知情,那你应该反醒看看是不是没有建立好沟通机制,或者是否授权过多给了能力不够强的人。如果你知情,那你应该反思为什么当时没有做好判断,不管别人怎么说,毕竟负责人是有最终决策权的。

差的负责人总是把责任推给别人,自己没有任何错误。

好的负责人应该把所有错误都揽在自己身上,从自己身上找原因。即使是别人犯的错,也是你招聘、用人的问题。

皇上可以不懂军事,将军不能不懂政治

CEO 可以不懂技术,CTO 不能不懂得沟通。

对于 CEO 来说,不懂技术就最好不要插手技术。最著名的例子,就是长平之战,赵王不懂军事,但是却把廉颇换成了只会纸上谈兵的赵括。结果断送了赵国四十万人的性命。

对于 CTO 来说,应该需要学会沟通。人们都知道一山不容二虎,岳飞却有「迎还二帝」的雄心,这让高宗怎么能接受?回来两个皇帝,难道让高宗退位吗?岳飞虽然留得一世美名,但是他还是死了,宋朝还是亡了,从结果来看没有意义。

我身边有一些朋友创业,遇到 CEO 不能理解 CTO,于是两年换一个 CTO。CTO 一换,技术核心班子跟着换,于是技术的积累完全做不起来。技术债务越来越深,最终谁也搞不定了。

很多 CTO 也有问题,不懂得向上管理,明明做得很好,但是因为 CEO 很难理解技术细节,只能以线上事故数量、团队加不加班、技术团队的薪资成本来考量 CTO 的水平。最终即使什么事故都没出,也会被认为没有感受到什么价值但是花很多钱而被换掉。技术部门通常拿的薪资确实偏高,如果 CTO 不能很好地和 CEO 相处,那么整个业务的风险都很大。

所以,各位 CTO 还是应该想办法,和 CEO 建立起相互的信任关系,有了任何关系,业务才能稳固发展。

胜者无名

通常最厉害的人,你是感受不到他的厉害的。因为他不但会收起自己的锋芒,还会把各种问题提前解决,让你感觉不到困难。

书中举了扁鹊的故事。

一天,魏文王问扁鹊:“你们家兄弟三人,都精于医术,到底哪一位最好呢 ?”

扁鹊答:“我的大哥医术最好,二哥次之,我最差。”

文王再问:“那么为什么你最出名呢 ?”

扁鹊答道:我大哥治病,是治病于病情发作之前的时候,由于一般人不知道他能事先铲除病因,反而觉得他的治疗没什么明显的效果,所以他的名气无法传出去,只有我们家的人才知道。

我二哥治病,是治病于病情初起的时候,看上去以为他只能治轻微的小病,所以他的名气只能在我们乡里流传。

而我扁鹊治病,是治病于病情已经严重的时候。一般人看到我在经脉上穿针放血,在皮肤上敷药,用麻药让人昏迷,做的都是些不可思议的大手术,自然以为我的医术高明,因此名气响遍全国,远远大于我的两位哥哥。

其实技术团队的工作也是这样,做得好,大家感受不到你的辛苦,业务每周按时上线。做得不好,团队总是在救火。

类似的工作还包括安全工作,做得最好的安全工作,就是不出安全事故,让安全部门没有存在感。

自知之明

孙子讲「知己知彼,百战不殆」,其实强调的是知己。做到了知己,至少赢面就是 50%,很多人都是不知己,才输了战争。

做企业也是这样,很多时候,盯着竞争对手看,还不如盯着自己的用户看。解决好用户需求,自然竞争力就上去了。盯着竞争对手看,还不如盯着市场环境看,寻找新的商业机会,比和竞争对手抢小蛋糕更有价值。

如何知己呢?华彬在书中介绍了 19 世纪瑞士军事家若米尼的五个层次:

  1. 政策。我是谁,我代表谁的利益,我的行事方针。
  2. 战略。我在哪儿,我要去哪儿,怎么去。
  3. 大战术。核心竞争方法论。《孙子兵法》里的以正合,以奇胜,就是它的核心战术。
  4. 战争勤务。后勤保障。
  5. 工程艺术。对筑垒要点的攻守艺术。
  6. 基础战术。具体细节工作怎么做。

讲概率而不讲孤立事件

好的方法论不是总是对,而是在概率上极大可能对。最终打仗其实就是拼实力,实力强的不一定赢,但是胜率高。

书中的例子是这个:

有些时候炒股、风投、决策也是这样,不能害怕做出错误决策。我们追求的不是决策 100% 正确,而是追求决策整体收益最大化。如果多做一些决策,有一些失败,但是整体是收益最大的,其实也是好的策略。

]]>
0
<![CDATA[详解JVM如何处理异常]]> http://www.udpwork.com/item/17159.html http://www.udpwork.com/item/17159.html#reviews Sun, 21 Oct 2018 21:50:00 +0800 技术小黑屋 http://www.udpwork.com/item/17159.html 无论你是使用何种编程语言,在日常的开发过程中,都会不可避免的要处理异常。今天本文将尝试讲解一些JVM如何处理异常问题,希望能够讲清楚这个内部的机制,如果对大家有所启发和帮助,则甚好。

当异常不仅仅是异常

我们在标题中提到了异常,然而这里指的异常并不是单纯的Exception,而是更为宽泛的Throwable。只是我们工作中习以为常的将它们(错误地)这样称谓。

关于Exception和Throwable的关系简单描述一下

  • Exception属于Throwable的子类,Throwable的另一个重要的子类是Error
  • throw可以抛出的都是Throwable和其子类,catch可捕获的也是Throwable和其子类。

除此之外,但是Exception也有一些需要我们再次强调的

  • Exception分为两种类型,一种为Checked Exception,另一种为unchecked Exception
  • Checked Exception,比如最常见的IOException,这种异常需要调用处显式处理,要么使用try catch捕获,要么再次抛出去。
  • Unchecked Exception指的是所有继承自Error(包含自身)或者是RuntimeException(包含自身)的类。这些异常不强制在调用处进行处理。但是也可以try catch处理。

注:本文暂不做Checked Exception设计的好坏的分析。

Exception Table 异常表

提到JVM处理异常的机制,就需要提及Exception Table,以下称为异常表。我们暂且不急于介绍异常表,先看一个简单的Java处理异常的小例子。

1
2
3
4
5
6
7
public static void simpleTryCatch() {
   try {
       testNPE();
   } catch (Exception e) {
       e.printStackTrace();
   }
}

上面的代码是一个很简单的例子,用来捕获处理一个潜在的空指针异常。

当然如果只是看简简单单的代码,我们很难看出什么高深之处,更没有了今天文章要谈论的内容。

所以这里我们需要借助一把神兵利器,它就是javap,一个用来拆解class文件的工具,和javac一样由JDK提供。

然后我们使用javap来分析这段代码(需要先使用javac编译)

1
2
3
4
5
6
7
8
9
10
11
12
//javap -c Main
 public static void simpleTryCatch();
    Code:
       0: invokestatic  #3                  // Method testNPE:()V
       3: goto          11
       6: astore_0
       7: aload_0
       8: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      11: return
    Exception table:
       from    to  target type
           0     3     6   Class java/lang/Exception

看到上面的代码,应该会有会心一笑,因为终于看到了Exception table,也就是我们要研究的异常表。

异常表中包含了一个或多个异常处理者(Exception Handler)的信息,这些信息包含如下

  • from 可能发生异常的起始点
  • to 可能发生异常的结束点
  • target 上述from和to之前发生异常后的异常处理者的位置
  • type 异常处理者处理的异常的类信息

那么异常表用在什么时候呢

答案是异常发生的时候,当一个异常发生时

1.JVM会在当前出现异常的方法中,查找异常表,是否有合适的处理者来处理

2.如果当前方法异常表不为空,并且异常符合处理者的from和to节点,并且type也匹配,则JVM调用位于target的调用者来处理。

3.如果上一条未找到合理的处理者,则继续查找异常表中的剩余条目

4.如果当前方法的异常表无法处理,则向上查找(弹栈处理)刚刚调用该方法的调用处,并重复上面的操作。

5.如果所有的栈帧被弹出,仍然没有处理,则抛给当前的Thread,Thread则会终止。

6.如果当前Thread为最后一个非守护线程,且未处理异常,则会导致JVM终止运行。

以上就是JVM处理异常的一些机制。

try catch -finally

除了简单的try-catch外,我们还常常和finally做结合使用。比如这样的代码

1
2
3
4
5
6
7
8
9
public static void simpleTryCatchFinally() {
   try {
       testNPE();
   } catch (Exception e) {
       e.printStackTrace();
   } finally {
       System.out.println("Finally");
   }
}

同样我们使用javap分析一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void simpleTryCatchFinally();
    Code:
       0: invokestatic  #3                  // Method testNPE:()V
       3: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       6: ldc           #7                  // String Finally
       8: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      11: goto          41
      14: astore_0
      15: aload_0
      16: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      19: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      22: ldc           #7                  // String Finally
      24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: goto          41
      30: astore_1
      31: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: ldc           #7                  // String Finally
      36: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      39: aload_1
      40: athrow
      41: return
    Exception table:
       from    to  target type
           0     3    14   Class java/lang/Exception
           0     3    30   any
          14    19    30   any

和之前有所不同,这次

  • 异常表中,有三条数据,而我们仅仅捕获了一个Exception
  • 异常表的后两个item的type为any

上面的三条异常表item的意思为

  • 如果0到3之间,发生了Exception类型的异常,调用14位置的异常处理者。
  • 如果0到3之间,无论发生什么异常,都调用30位置的处理者
  • 如果14到19之间(即catch部分),不论发生什么异常,都调用30位置的处理者。

再次分析上面的Java代码,finally里面的部分已经被提取到了try部分和catch部分。我们再次调一下代码来看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  public static void simpleTryCatchFinally();
    Code:
      //try 部分提取finally代码,如果没有异常发生,则执行输出finally操作,直至goto到41位置,执行返回操作。  

       0: invokestatic  #3                  // Method testNPE:()V
       3: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       6: ldc           #7                  // String Finally
       8: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      11: goto          41

      //catch部分提取finally代码,如果没有异常发生,则执行输出finally操作,直至执行got到41位置,执行返回操作。
      14: astore_0
      15: aload_0
      16: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      19: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      22: ldc           #7                  // String Finally
      24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: goto          41
      //finally部分的代码如果被调用,有可能是try部分,也有可能是catch部分发生异常。
      30: astore_1
      31: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: ldc           #7                  // String Finally
      36: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      39: aload_1
      40: athrow     //如果异常没有被catch捕获,而是到了这里,执行完finally的语句后,仍然要把这个异常抛出去,传递给调用处。
      41: return

Catch先后顺序的问题

我们在代码中的catch的顺序决定了异常处理者在异常表的位置,所以,越是具体的异常要先处理,否则就会出现下面的问题

1
2
3
4
5
6
7
8
9
private static void misuseCatchException() {
   try {
       testNPE();
   } catch (Throwable t) {
       t.printStackTrace();
   } catch (Exception e) { //error occurs during compilings with tips Exception Java.lang.Exception has already benn caught.
       e.printStackTrace();
   }
}

这段代码会导致编译失败,因为先捕获Throwable后捕获Exception,会导致后面的catch永远无法被执行。

Return 和finally的问题

这算是我们扩展的一个相对比较极端的问题,就是类似这样的代码,既有return,又有finally,那么finally导致会不会执行

1
2
3
4
5
6
7
8
9
10
public static String tryCatchReturn() {
   try {
       testNPE();
       return  "OK";
   } catch (Exception e) {
       return "ERROR";
   } finally {
       System.out.println("tryCatchReturn");
   }
}

答案是finally会执行,那么还是使用上面的方法,我们来看一下为什么finally会执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static java.lang.String tryCatchReturn();
    Code:
       0: invokestatic  #3                  // Method testNPE:()V
       3: ldc           #6                  // String OK
       5: astore_0
       6: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       9: ldc           #8                  // String tryCatchReturn
      11: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      14: aload_0
      15: areturn       返回OK字符串,areturn意思为return a reference from a method
      16: astore_0
      17: ldc           #10                 // String ERROR
      19: astore_1
      20: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      23: ldc           #8                  // String tryCatchReturn
      25: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      28: aload_1
      29: areturn  //返回ERROR字符串
      30: astore_2
      31: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: ldc           #8                  // String tryCatchReturn
      36: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      39: aload_2
      40: athrow  如果catch有未处理的异常,抛出去。

行文仓促,加之本人水平有限,有错误的地方,请指出。

参考文章:

贴一个广告:Flipboard 中国招募安卓开发工程师感兴趣的可以关注一下。

]]>
无论你是使用何种编程语言,在日常的开发过程中,都会不可避免的要处理异常。今天本文将尝试讲解一些JVM如何处理异常问题,希望能够讲清楚这个内部的机制,如果对大家有所启发和帮助,则甚好。

当异常不仅仅是异常

我们在标题中提到了异常,然而这里指的异常并不是单纯的Exception,而是更为宽泛的Throwable。只是我们工作中习以为常的将它们(错误地)这样称谓。

关于Exception和Throwable的关系简单描述一下

  • Exception属于Throwable的子类,Throwable的另一个重要的子类是Error
  • throw可以抛出的都是Throwable和其子类,catch可捕获的也是Throwable和其子类。

除此之外,但是Exception也有一些需要我们再次强调的

  • Exception分为两种类型,一种为Checked Exception,另一种为unchecked Exception
  • Checked Exception,比如最常见的IOException,这种异常需要调用处显式处理,要么使用try catch捕获,要么再次抛出去。
  • Unchecked Exception指的是所有继承自Error(包含自身)或者是RuntimeException(包含自身)的类。这些异常不强制在调用处进行处理。但是也可以try catch处理。

注:本文暂不做Checked Exception设计的好坏的分析。

Exception Table 异常表

提到JVM处理异常的机制,就需要提及Exception Table,以下称为异常表。我们暂且不急于介绍异常表,先看一个简单的Java处理异常的小例子。

1
2
3
4
5
6
7
public static void simpleTryCatch() {
   try {
       testNPE();
   } catch (Exception e) {
       e.printStackTrace();
   }
}

上面的代码是一个很简单的例子,用来捕获处理一个潜在的空指针异常。

当然如果只是看简简单单的代码,我们很难看出什么高深之处,更没有了今天文章要谈论的内容。

所以这里我们需要借助一把神兵利器,它就是javap,一个用来拆解class文件的工具,和javac一样由JDK提供。

然后我们使用javap来分析这段代码(需要先使用javac编译)

1
2
3
4
5
6
7
8
9
10
11
12
//javap -c Main
 public static void simpleTryCatch();
    Code:
       0: invokestatic  #3                  // Method testNPE:()V
       3: goto          11
       6: astore_0
       7: aload_0
       8: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      11: return
    Exception table:
       from    to  target type
           0     3     6   Class java/lang/Exception

看到上面的代码,应该会有会心一笑,因为终于看到了Exception table,也就是我们要研究的异常表。

异常表中包含了一个或多个异常处理者(Exception Handler)的信息,这些信息包含如下

  • from 可能发生异常的起始点
  • to 可能发生异常的结束点
  • target 上述from和to之前发生异常后的异常处理者的位置
  • type 异常处理者处理的异常的类信息

那么异常表用在什么时候呢

答案是异常发生的时候,当一个异常发生时

1.JVM会在当前出现异常的方法中,查找异常表,是否有合适的处理者来处理

2.如果当前方法异常表不为空,并且异常符合处理者的from和to节点,并且type也匹配,则JVM调用位于target的调用者来处理。

3.如果上一条未找到合理的处理者,则继续查找异常表中的剩余条目

4.如果当前方法的异常表无法处理,则向上查找(弹栈处理)刚刚调用该方法的调用处,并重复上面的操作。

5.如果所有的栈帧被弹出,仍然没有处理,则抛给当前的Thread,Thread则会终止。

6.如果当前Thread为最后一个非守护线程,且未处理异常,则会导致JVM终止运行。

以上就是JVM处理异常的一些机制。

try catch -finally

除了简单的try-catch外,我们还常常和finally做结合使用。比如这样的代码

1
2
3
4
5
6
7
8
9
public static void simpleTryCatchFinally() {
   try {
       testNPE();
   } catch (Exception e) {
       e.printStackTrace();
   } finally {
       System.out.println("Finally");
   }
}

同样我们使用javap分析一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void simpleTryCatchFinally();
    Code:
       0: invokestatic  #3                  // Method testNPE:()V
       3: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       6: ldc           #7                  // String Finally
       8: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      11: goto          41
      14: astore_0
      15: aload_0
      16: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      19: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      22: ldc           #7                  // String Finally
      24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: goto          41
      30: astore_1
      31: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: ldc           #7                  // String Finally
      36: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      39: aload_1
      40: athrow
      41: return
    Exception table:
       from    to  target type
           0     3    14   Class java/lang/Exception
           0     3    30   any
          14    19    30   any

和之前有所不同,这次

  • 异常表中,有三条数据,而我们仅仅捕获了一个Exception
  • 异常表的后两个item的type为any

上面的三条异常表item的意思为

  • 如果0到3之间,发生了Exception类型的异常,调用14位置的异常处理者。
  • 如果0到3之间,无论发生什么异常,都调用30位置的处理者
  • 如果14到19之间(即catch部分),不论发生什么异常,都调用30位置的处理者。

再次分析上面的Java代码,finally里面的部分已经被提取到了try部分和catch部分。我们再次调一下代码来看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  public static void simpleTryCatchFinally();
    Code:
      //try 部分提取finally代码,如果没有异常发生,则执行输出finally操作,直至goto到41位置,执行返回操作。  

       0: invokestatic  #3                  // Method testNPE:()V
       3: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       6: ldc           #7                  // String Finally
       8: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      11: goto          41

      //catch部分提取finally代码,如果没有异常发生,则执行输出finally操作,直至执行got到41位置,执行返回操作。
      14: astore_0
      15: aload_0
      16: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      19: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      22: ldc           #7                  // String Finally
      24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: goto          41
      //finally部分的代码如果被调用,有可能是try部分,也有可能是catch部分发生异常。
      30: astore_1
      31: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: ldc           #7                  // String Finally
      36: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      39: aload_1
      40: athrow     //如果异常没有被catch捕获,而是到了这里,执行完finally的语句后,仍然要把这个异常抛出去,传递给调用处。
      41: return

Catch先后顺序的问题

我们在代码中的catch的顺序决定了异常处理者在异常表的位置,所以,越是具体的异常要先处理,否则就会出现下面的问题

1
2
3
4
5
6
7
8
9
private static void misuseCatchException() {
   try {
       testNPE();
   } catch (Throwable t) {
       t.printStackTrace();
   } catch (Exception e) { //error occurs during compilings with tips Exception Java.lang.Exception has already benn caught.
       e.printStackTrace();
   }
}

这段代码会导致编译失败,因为先捕获Throwable后捕获Exception,会导致后面的catch永远无法被执行。

Return 和finally的问题

这算是我们扩展的一个相对比较极端的问题,就是类似这样的代码,既有return,又有finally,那么finally导致会不会执行

1
2
3
4
5
6
7
8
9
10
public static String tryCatchReturn() {
   try {
       testNPE();
       return  "OK";
   } catch (Exception e) {
       return "ERROR";
   } finally {
       System.out.println("tryCatchReturn");
   }
}

答案是finally会执行,那么还是使用上面的方法,我们来看一下为什么finally会执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static java.lang.String tryCatchReturn();
    Code:
       0: invokestatic  #3                  // Method testNPE:()V
       3: ldc           #6                  // String OK
       5: astore_0
       6: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       9: ldc           #8                  // String tryCatchReturn
      11: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      14: aload_0
      15: areturn       返回OK字符串,areturn意思为return a reference from a method
      16: astore_0
      17: ldc           #10                 // String ERROR
      19: astore_1
      20: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      23: ldc           #8                  // String tryCatchReturn
      25: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      28: aload_1
      29: areturn  //返回ERROR字符串
      30: astore_2
      31: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: ldc           #8                  // String tryCatchReturn
      36: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      39: aload_2
      40: athrow  如果catch有未处理的异常,抛出去。

行文仓促,加之本人水平有限,有错误的地方,请指出。

参考文章:

贴一个广告:Flipboard 中国招募安卓开发工程师感兴趣的可以关注一下。

]]>
0
<![CDATA[Lua 的多线程支持]]> http://www.udpwork.com/item/17158.html http://www.udpwork.com/item/17158.html#reviews Sun, 21 Oct 2018 15:38:36 +0800 云风 http://www.udpwork.com/item/17158.html 单个 Lua 虚拟机只能工作在一个线程下,如果你需要在同一个进程中让 Lua 并行处理一些事务,必须为每个线程部署独立的 Lua 虚拟机。

ps. 在少量多线程应用环境,加锁也是可行的。你可以在编译时自定义lua_lock(L)和lua_unlock(L)去调用操作系统的锁。

比较成熟的 lua 多线程库有LanesEffil。它们都试图隐藏多虚拟机的细节,让用户使用起来好像多线程在使用同一个虚拟机一样。比如 Effil 就用了 effil.table 去模拟 table 并让多个虚拟机可以共享数据;Lanes 则有 deep userdata 可以在不同线程间共享。

这些个方案都允许用户在不同的虚拟机间相互调用函数,大约是利用在虚拟机间同步函数的字节码和 upvalue 实现的。基于这些线程库的程序,用起来和别的支持多线程的语言(比如 Golang)那样没有太大区别。

但我不喜欢这种多线程解决方案。我认为多线程本身是复杂的,隐藏线程并行运行的细节,让用户基于共享状态去写程序并没有什么好处。如果阅读代码的人一眼看上去并不能立刻分辨出一个函数到底会在哪个线程运行,只会增加多线程程序的维护成本。

所以,skynet 采用的是让框架去支持多线程,让用户明确知道有独立的虚拟机的概念,以及它们跑在不同的线程下。只用消息队列来协同工作。还有很多类似的项目也是这样做的,比如cqueues

我最近在开发客户端引擎时也遇到了多线程问题。例如 bgfx 的 log 回调就可能发生在不同线程中,所以无法简单的封装成 lua 函数进行回调;我们的引擎需要通过网络加载资源/代码,这部分网络 IO 处理最好能和逻辑线程分离,等等。

一开始,我采用的是 Lanes 。但是做了一段时间后,我觉得滥用多线程很容易滋生 bug 。

最近,我希望去掉 Lanes 这个库,改用自己实现的一套最简的线程库。由于客户端线程数量比较固定,无非是渲染线程,逻辑线程,IO 线程,Lua 调试器线程,物理线程;线程之间有固定的消息管道交换数据就够了。

所以,我想我可以从最基本的线程 api 设计起,一点点按需完善这个线程库。一开始,只需要有线程的创建;通讯管道可以支持收发 lua 基础数据类型就可用了。这部分在 skynet 中已经非常稳定,只需要把代码搬过来。

由于图形客户端有天然的运行周期(按帧渲染),我甚至不需要给通讯管道加读取超时参数。只用周期性查询是否有新数据即可。通讯管道也是有限的,所以并不需要像 golang 那样把 channel 做成 first class 的类型,并支持自由创建和销毁。我可以支持有限数量的具名管道,不同线程间约定好名字就可以通讯。

这些基础设施实现出来比我想象的要简单。搬运一些老代码,添加好 lua 的封装,一个周末就完成了。下周可以基于它们来重构我们已经做好(但还有 bug )的引擎代码。

]]>
单个 Lua 虚拟机只能工作在一个线程下,如果你需要在同一个进程中让 Lua 并行处理一些事务,必须为每个线程部署独立的 Lua 虚拟机。

ps. 在少量多线程应用环境,加锁也是可行的。你可以在编译时自定义lua_lock(L)和lua_unlock(L)去调用操作系统的锁。

比较成熟的 lua 多线程库有LanesEffil。它们都试图隐藏多虚拟机的细节,让用户使用起来好像多线程在使用同一个虚拟机一样。比如 Effil 就用了 effil.table 去模拟 table 并让多个虚拟机可以共享数据;Lanes 则有 deep userdata 可以在不同线程间共享。

这些个方案都允许用户在不同的虚拟机间相互调用函数,大约是利用在虚拟机间同步函数的字节码和 upvalue 实现的。基于这些线程库的程序,用起来和别的支持多线程的语言(比如 Golang)那样没有太大区别。

但我不喜欢这种多线程解决方案。我认为多线程本身是复杂的,隐藏线程并行运行的细节,让用户基于共享状态去写程序并没有什么好处。如果阅读代码的人一眼看上去并不能立刻分辨出一个函数到底会在哪个线程运行,只会增加多线程程序的维护成本。

所以,skynet 采用的是让框架去支持多线程,让用户明确知道有独立的虚拟机的概念,以及它们跑在不同的线程下。只用消息队列来协同工作。还有很多类似的项目也是这样做的,比如cqueues

我最近在开发客户端引擎时也遇到了多线程问题。例如 bgfx 的 log 回调就可能发生在不同线程中,所以无法简单的封装成 lua 函数进行回调;我们的引擎需要通过网络加载资源/代码,这部分网络 IO 处理最好能和逻辑线程分离,等等。

一开始,我采用的是 Lanes 。但是做了一段时间后,我觉得滥用多线程很容易滋生 bug 。

最近,我希望去掉 Lanes 这个库,改用自己实现的一套最简的线程库。由于客户端线程数量比较固定,无非是渲染线程,逻辑线程,IO 线程,Lua 调试器线程,物理线程;线程之间有固定的消息管道交换数据就够了。

所以,我想我可以从最基本的线程 api 设计起,一点点按需完善这个线程库。一开始,只需要有线程的创建;通讯管道可以支持收发 lua 基础数据类型就可用了。这部分在 skynet 中已经非常稳定,只需要把代码搬过来。

由于图形客户端有天然的运行周期(按帧渲染),我甚至不需要给通讯管道加读取超时参数。只用周期性查询是否有新数据即可。通讯管道也是有限的,所以并不需要像 golang 那样把 channel 做成 first class 的类型,并支持自由创建和销毁。我可以支持有限数量的具名管道,不同线程间约定好名字就可以通讯。

这些基础设施实现出来比我想象的要简单。搬运一些老代码,添加好 lua 的封装,一个周末就完成了。下周可以基于它们来重构我们已经做好(但还有 bug )的引擎代码。

]]>
0
<![CDATA[Move semantics in C++11]]> http://www.udpwork.com/item/17157.html http://www.udpwork.com/item/17157.html#reviews Fri, 19 Oct 2018 15:18:51 +0800 Robin Dong http://www.udpwork.com/item/17157.html After studyingan examplefor Move Semantics of C++11, I write a more complete code snippet:

#include <iostream>

using namespace std;

class Intvec {
  public:
    explicit Intvec(size_t num = 0)
      : m_size(num), m_data(new int[m_size]) {
      log("constructor");
    }

    ~Intvec() {
      log("destructor");
      if (m_data) {
        delete[] m_data;
        m_data = 0;
      }
    }

    Intvec(const Intvec& other) : m_size(other.m_size), m_data(new int[m_size]) {
      log("copy constructor");
      for (size_t i = 0; i < m_size; ++i)
        m_data[i] = other.m_data[i];
    }

    Intvec(Intvec&& other) {
      log("move constructor");
      std::swap(m_size, other.m_size);
      std::swap(m_data, other.m_data);
      other.m_size = 0;
      other.m_data = nullptr;
    }

    Intvec& operator=(const Intvec& other) {
      log("copy assignment operator");
      Intvec tmp(other);
      std::swap(m_size, tmp.m_size);
      std::swap(m_data, tmp.m_data);
      return *this;
    }

    Intvec& operator=(Intvec&& other) {
      log("move assignment operator");
      std::swap(m_size, other.m_size);
      std::swap(m_data, other.m_data);
      return *this;
    }

  private:
    void log(const char* msg) {
      cout << "[" << this << "] " << msg << "\n";
    }

    size_t m_size;
    int* m_data;
};

int main(void) {
  Intvec v1(20);
  Intvec v2;

  cout << "assigning lvalue...\n";
  v2 = v1;
  cout << "ended assigning lvalue...\n";

  //Intvec v4 = std::move(Intvec(30));
  Intvec v3;
  cout << "move assigning...\n";
  v3 = Intvec(30);
  cout << "ended move assigning...\n";

  cout << "move constructor\n";
  Intvec v4 = std::move(v3);
  cout << "ended move constructor\n";

  return 0;
}

Pay attention to last two lines in ‘move constructor’:

...
      other.m_size = 0;
      other.m_data = nullptr;
...

Since ‘move constructor’ will not set initial value for m_size and m_data of ‘v4’, the m_size and m_data of ‘v3’ will be uninitial after swaps. Adding the two lines of code means to initialize m_size and m_data of ‘v3’.

]]>
After studyingan examplefor Move Semantics of C++11, I write a more complete code snippet:

#include <iostream>

using namespace std;

class Intvec {
  public:
    explicit Intvec(size_t num = 0)
      : m_size(num), m_data(new int[m_size]) {
      log("constructor");
    }

    ~Intvec() {
      log("destructor");
      if (m_data) {
        delete[] m_data;
        m_data = 0;
      }
    }

    Intvec(const Intvec& other) : m_size(other.m_size), m_data(new int[m_size]) {
      log("copy constructor");
      for (size_t i = 0; i < m_size; ++i)
        m_data[i] = other.m_data[i];
    }

    Intvec(Intvec&& other) {
      log("move constructor");
      std::swap(m_size, other.m_size);
      std::swap(m_data, other.m_data);
      other.m_size = 0;
      other.m_data = nullptr;
    }

    Intvec& operator=(const Intvec& other) {
      log("copy assignment operator");
      Intvec tmp(other);
      std::swap(m_size, tmp.m_size);
      std::swap(m_data, tmp.m_data);
      return *this;
    }

    Intvec& operator=(Intvec&& other) {
      log("move assignment operator");
      std::swap(m_size, other.m_size);
      std::swap(m_data, other.m_data);
      return *this;
    }

  private:
    void log(const char* msg) {
      cout << "[" << this << "] " << msg << "\n";
    }

    size_t m_size;
    int* m_data;
};

int main(void) {
  Intvec v1(20);
  Intvec v2;

  cout << "assigning lvalue...\n";
  v2 = v1;
  cout << "ended assigning lvalue...\n";

  //Intvec v4 = std::move(Intvec(30));
  Intvec v3;
  cout << "move assigning...\n";
  v3 = Intvec(30);
  cout << "ended move assigning...\n";

  cout << "move constructor\n";
  Intvec v4 = std::move(v3);
  cout << "ended move constructor\n";

  return 0;
}

Pay attention to last two lines in ‘move constructor’:

...
      other.m_size = 0;
      other.m_data = nullptr;
...

Since ‘move constructor’ will not set initial value for m_size and m_data of ‘v4’, the m_size and m_data of ‘v3’ will be uninitial after swaps. Adding the two lines of code means to initialize m_size and m_data of ‘v3’.

]]>
0
<![CDATA[每周分享第 27 期]]> http://www.udpwork.com/item/17156.html http://www.udpwork.com/item/17156.html#reviews Fri, 19 Oct 2018 05:57:47 +0800 阮一峰 http://www.udpwork.com/item/17156.html 这里记录过去一周,我看到的值得分享的东西,每周五发布。

欢迎大家去 GitHub 的ruanyf/weekly提交issue,进行投稿。

2005年,斯坦福大学邀请乔布斯在毕业典礼演讲。这个演讲后来成为经典,《乔布斯传》说"或许有些演讲对后世影响更大,但是你找不到(比这篇)更好的演讲。"

演讲中,乔布斯说了一段有名的话。

"你们的时间有限,所以不要把它浪费在过其他人的生活。最重要的是,你要有勇气跟随你的内心和直觉。某种程度上,它们已经知道你真正想要成为什么样子。其他所有事情都是次要的。"

这段话后来被称为"热情假设",很多人都引用它鼓励年轻人:寻找职业方向的时候,要跟随内心的热情(passion),去做那些你有强烈意愿从事的工作。

但是,美国最近出版了一本新书《优秀到无法忽略》(《So Good They Can't Ignore You》),声称乔布斯的这个建议是完全错误的,误导年轻人。别的不说,乔布斯本人也不遵守"热情假设"。年轻时,他对禅宗思想最感兴趣,去印度学习佛教。如果他真的追随自己的内心,他就应该去当一个禅宗老师,而不是跑回美国创办苹果公司。

这本书认为,以下几个原因导致"热情假设"不是一个好的建议。

第一,热情真的很罕见。大部分人都对自己的工作没兴趣,而是对某种爱好(比如打球、钓鱼)有兴趣。如果大部分人都找不到自己的职业热情,你怎么能叫他们去追随热情呢?

第二,热情需要时间来建立。许多人刚开始工作的时候,对自己的职业并没有兴趣,随着时间积累,他们的经验越来越多,能够掌控的东西越来越多,这才慢慢开始热爱自己的工作。找工作阶段,你可能根本不会意识到这个职业就是你的热情所在。

第三,过度强调热情,容易对现状产生不满。2010年的一项调查发现,只有45%的美国人对自己的工作满意。由于很多人相信,无法产生热情的工作不是好工作,导致对职业生涯抱有不切实际的期望,对现有的工作不满意,不断跳槽。

这本书提出,热情不是凭空产生的,它跟自主权有关。如果你在某个职位上的自主权越大,能够掌控的东西越多,就越容易对当前的职业产生热情。与其强调跟随内心的热情,不如强调如何在某种职业里面获得自主权。你必须使自己变得优秀,让别人无法忽视你,同意让你掌控更多的资源,这就是书名的含义。

新闻

1、南极粒子

科学家最近确认,南极洲正在向外太空喷射高能粒子,充满能量的粒子从冰层底下发射,直接飞向太空。科学家目前不知道原因。

下一步,科学家将拟定计划,探索冰层当中或者冰层之下到底是什么东西在喷射粒子。上图为美国在南极点的阿蒙森-斯科特科考站设立的中微子探测器。

2、定制搜索页

谷歌推出验证服务。只要你验证了身份,就可以定制搜索结果。以后别人在谷歌搜索你,就可以看到你留下的内容。

这个功能对公司很有用,可以把本公司的最新动态放到搜索页头条。

3、美国机场的面部识别实验

美国国土安全部去年在九个机场测试脸部识别,结果匹配率只有85%,远低于97%的最低标准。

每个外国人入境时,美国政府会拍照。当外国人离境时,再与照片进行比对,看看是否为同一个人。结果,由于"网络慢、缺乏专职人员等原因",匹配效果不理想。尤其是29岁以下和70岁以上的外国人,经常匹配出错。

4、谷歌搜索的彩蛋

国外用户发现了一个谷歌搜索的彩蛋,搜索 text adventure 后,打开开发者工具,会看到提示,问你要不要玩游戏。回答 yes,就可以开始玩了。

这是一个文字游戏,探索加利福尼亚州山景城的谷歌总部。玩一次游戏,走完整个过程,大约需要30分钟到一个小时。

5、个人信息换咖啡

既然别人可以出售我的个人信息牟利,为什么我自己不能出售呢?

美国罗德岛有一个咖啡馆,在读的大学生可以免费获取咖啡,条件是提供自己的个人信息,包括姓名、电话号码,电子邮件地址、大学专业、出生日期和兴趣,同意接收赞助商的广告信息。

6、取消方向盘

美国国家公路交通安全管理局(NHTSA)表示,在符合条件的情况下,将允许自动驾驶汽车取消方向盘、脚踏板和后视镜。

汽车没有了方向盘,就意味着你只能依靠自动驾驶,最多在触摸板上进行操作。也就是说,操作汽车将来会变得跟操作电梯差不多。

7、独一无二的默认密码

很多互联网连接设备(比如路由器)都有出厂的默认密码,而且默认密码都是一样的,比如"0000"或者"1234"。用户使用时,应该改掉默认密码,但是实际上,许多人并没有修改,包括一些重要机构也是如此,导致巨大的安全隐患,很多 DDOS 攻击都是通过控制网络上的路由器而发起的。

加州最近通过法律,2020年起,任何在加利福尼亚州制造的互联网连接设备,必须每个设备都有不一样的密码,以提高整体的互联网安全。

8、软件接电话

10月9日发布的谷歌 Pixel 3 手机,里面的"谷歌助手"有自动接电话功能,官方明确说能对付推销电话。

软件自动接了电话以后,对方说什么会转成文字,显示在屏幕上,还会回应几句。你确定是骚扰电话,就按一下按钮,软件自动挂断电话,把对方加入黑名单。这个功能真酷,但目前只支持英文。

9、F-16 事故

10月11日,比利时空军发生了一件匪夷所思的事故。一名技术人员正在飞机库房,研究F-16 战斗机,不小心激活了战斗机加农炮。大炮加载,击中另一架F-16 战斗机,后者就击毁了。由于后者刚加过油,大火使得旁边的F-16也完蛋了。比利时空军有60架现役F-16飞机。

一架 F-16 起码3500万美元,这个一不小心导致的事故,真是代价昂贵。

10、AR 导航

高德推出车载 AR 导航,在真实的道路上可以叠加车道线、车辆识别、红绿灯和限速标志等各类对象,给驾驶员带来更直观的实景导航体验。

产品计划首批应用在智能后视镜上,做能力验证,后续将拓展至仪表盘、车机中控屏以及 HUD 平视系统等更多使用场景。(本条为读者@Anderson-Liu投稿)

11、一句话新闻

  • 《华盛顿邮报》称,根据统计,2019年美国一半的电话通话,将是推销电话和诈骗电话。

  • 芬兰的一项医学统计发现,一般的阑尾炎可能不需要手术,只靠抗生素治疗即可。调查中,三分之二病人不再需要手术,剩下的三分之一最后还是需要手术,但是前期的抗生素治疗并没有延误病情。

  • Wi-Fi 联盟发布 Wi-Fi 6 标准,用于支持下一代的 802.11ax 协议的无线连接。前五代协议分别是 802.11a / b / g / n / ac。

  • 日本提出一个法律草案,允许对人类胚胎进行基因编辑。

教程

1、如何写一个 Chrome 浏览器的扩展(英文)

想写 Chrome 浏览器的扩展吗?可以看这篇教程,很容易懂。原始代码有一些小问题,我重新做了一个Demo

2、树莓派如何搭建 NAS(英文)

想要搭建家用储存系统的朋友,可以看这篇教程,使用树莓派和移动硬盘,搭建一个 NAS,操作简单,可玩性高,费用便宜。

3、Kubernetes:令人惊讶的个人项目平台(英文)

流行的观点认为,Kubernetes 是一种过于复杂的技术,只适用于非常大的机器群。我认为这可能是错的,Kubernetes 适用于小型项目。

4、Kubernetes 用于个人项目?不用了,谢谢!(英文)

本文是对上一篇文章的反驳。

5、网页性能的准确测量(英文)

本文讨论如何使用window.requestAnimationFrame()测量脚本操作的准确耗时。

6、区块链技术概述[PDF](英文)

美国国家标准技术研究所(NIST)编写的介绍区块链的小册子,针对一般读者,内容比较全面完整。

7、我为什么起诉 PinScreen?(英文)

一个硅谷工程师起诉了他的华人老板,还制作了一个极其详细的网页。我觉得这招很好,值得借鉴。不管官司能否赢,我要让你曝曝光。

8、为什么国际航线的航路如此混乱?(英文)

《南华早报》的长篇可视化报道,解释为什么国际航线弯弯曲曲,不采用最近的直线距离飞行。本文图文并茂的呈现方式,值得借鉴。

9、CSV 和 JSON(英文)

CSV 和 JSON 两种数据格式的介绍和比较。很多人忽略的一个事实是,最近几年 CSV 的使用量一直在上升。

资源

1、精通 JavaScript 模块

开源电子书,介绍如何编写模块化的 JavaScript 代码。

2、机器学习解释

开源电子书,尝试用通俗的语言解释机器学习。

3、开源火星车

美国航空航天局开源了火星漫游车的设计。完全使用市场上可以买到的材料,爱好者就能做出自己的漫游车。它使用树莓派作为车载控制中心,使用安卓手机或 xbox 手柄遥控。

工具

1、remi

Python 的图形界面库,最大特点就是采用了 HTML 界面。脚本加入这个库以后,可以生成网页接口,使用浏览器访问。

2、dbxfs

dbxfs 允许用户将 Dropbox 目录挂载到本地文件系统。

3、ferret

Go 语言写的 web scraping 工具,主要特点是操作过程是声明式的,非常易写。

4、Sans Forgetica

一种字体,据称有助于增强记忆。

5、nvtop

Linux 服务器的 top 命令可以查看 CPU 的状态。nvtop 命令则是用来查看 NVidia GPU 的状态。

6、Mixnode

Mixnode 是一个收集了全世界网页的数据库,允许使用类似 SQL 的语法查询网页。

7、retejs

可视化编程的 JavaScript 框架。

8、命令行 2048 游戏

C++ 编写的2048游戏。

9、OpenCC

中文繁体和简体互相转换的开源工具,C 语言开发的。

文摘

1、摩托罗拉 Razr V3 手机

2003年7月,摩托罗拉公司发布了 Razr V3 手机。这部手机只是产品小组的一个普通设计,并不是公司主力产品。一开始,摩托罗拉预计销售量是30万部,上市前调高到80万部,上市价格是500美元。

这部手机有一个100万像素(640x480)的摄像头。采用金属机身,键盘也是金属的,而不是通常的橡胶或塑料键盘,这是为了把手机做得很薄,另一个特点是,它的按键比标准尺寸大得多。总之,它具有非常独特的视觉外观。

除了主显示屏,它还有一个小的外部显示屏。手机底部的大下巴,是为了容纳天线。由于这部手机几乎是全金属,会屏蔽信号,所以把天线放在底部。翻盖设计也使得这部手机,有一种翻开和关闭的玩耍的乐趣。如果今天发布这部手机,它的设计仍然会像当时一样酷。

最终,这部手机全球一共卖出了1.3亿部,是有史以来最畅销的手机型号之一。 从这部手机开始,大家意识到,手机不仅仅是通信工具,也是时尚用品。

2、苹果手表的潜力

9月份发布的 Apple Watch 4 在医疗保健上有重大突破。

它有两个很好的功能,一是跌倒检测,如果发现用户跌倒后一分钟内没有站起来,它就会自动拨打报警电话,并且附上用户的 GPS 位置,这对老人非常有用,可以挽救生命。二是,它带有美国食品药物管理局认可的心电图功能,一旦发现心动不正常,就会向用户发警报。

其实,苹果公司还有许多专利。下面这些功能,它应该考虑加入手表。

  • 睡眠呼吸检测。那些睡眠过程中的呼吸停止,通常很难发现,但是如果用户戴着手表过夜就可以。
  • 脉搏血氧仪。Apple Watch 可以根据吸收的红外线光量,来确定血液中含有多少氧气,判断用户的血液是否缺氧。
  • 呼吸率。每人每分钟呼吸的次数。
  • 血压。苹果于2017年申请了两项专利,通过将手表放在胸前来测量血压。
  • 紫外线探测器。苹果已经获得了一种新型传感器的专利,可以让你知道皮肤的紫外线暴露的风险。
  • 帕金森病。苹果在 ResearchKit 框架中添加了一个新的"运动障碍API",支持运动和震颤检测。 它如果发现用户一直在颤抖,就可以提醒用户可能患有帕金森病。
  • 糖尿病。苹果正在申请一项不用抽血的血液葡萄糖传感器。

如果这些功能都能做到,对于很多用户来说,苹果手表可能远比手机重要。

3、马尾巴有什么用?

从大象到长颈鹿到斑马,大型哺乳动物都有尾巴,它们的目的是什么?

这些尾巴看起来很相似 - 它们都是长而薄,上面有黑色的毛发,它们不停地来回摆动。生物学家普遍认为摇摆可以阻止苍蝇,但尾巴到底是怎么做到的?

炎热的夏天,到处都是叮咬的昆虫。短短一天内,一匹马就会因昆虫(如蚊子)叮咬而失去一杯血。蚊子不仅会带走血,还会带来疾病,比如疟疾、寨卡病毒,登革热。远离蚊子,可能会对马的健康产生重大影响。大型哺乳动物,从斑马到长颈鹿,再到大象,都会吸引大量的昆虫。动物越大,尾巴越大。

我们买了一条用真马尾巴制成的鞭子,用它来打蚊子,结果发现根本打不中。原因很简单,蚊子太轻,一只蚊子重2毫克,相当于四分之一的鸡毛,只要鞭子一靠近,带来的风就会把蚊子吹走。

我们发现,当尾巴静止时,蚊子很容易飞过来,停在天花板上。但当尾巴快速摆动时,蚊子只要飞向尾巴就会掉头,因为尾巴摆动产生的风足以驱逐一半的蚊子。

所以,为什么动物如此迅速地摆动它们的尾巴呢?这是因为它们必须产生与蚊子飞行速度相当的风,速度大约是每秒一米或每小时两英里。马尾不仅仅是一种装饰品,这是他们抵御昆虫叮咬的主要防线。

本周图片

1、不是橙色的水果

如果你在搜索引擎里面,搜索"不是橙色的水果",结果会恰恰相反,返回的都是橙色的水果。这证明,搜索引擎目前都是基于关键词,而不是语义搜索。

2、火星基地

SpaceX 公司的老板马斯克,在推特上贴出一张图片,表示这是 SpaceX 公司火星基地的设计图片。

新奇

1、穷人的降噪耳机

开放式办公环境,为了避免打扰,通常我们需要一副降噪耳机。但是,好的降噪耳机很贵,有时做工也不令人满意,很容易坏。国外就有开发者想出 DIY 降噪耳机。

只要找一副隔音效果较好的有线耳机,然后外接一个蓝牙接收器即可,总成本在300元人民币以下,效果完全不输那些名牌产品。

本周金句

1、

如果一个人不想做某件事,通常不是由于客观条件不允许,而是他有下面四种心态之一:恐惧(Fear)、排斥(Rejection)、自卑(Low self-esteem)、怠惰(Laziness)。

--《不要对自己撒谎

2、

神经疾病(比如帕金森病、癫痫、阿尔茨海默病)都涉及神经系统(大脑,脊髓和神经)的故障或损伤,精神疾病的标志则是行为不安和情绪状态。

--《神经疾病和精神疾病有什么区别?》

3、

最早,我们做的是一个地理位置应用,人们到了一个地点,可以签到和发照片。我们发现,人们对位置不太在乎,只是希望将照片放在那里。

后来,由于发展得不好,我们决定简化功能,只保留照片、评论,以及给照片标识位置,应用的名字改成了 Instagram。

--《Instagram 的故事》

欢迎订阅

这个专栏每周五发布,同步更新在我的个人网站微信公众号语雀

微信搜索"阮一峰的网络日志 "或者扫描二维码,即可订阅。

(完)

文档信息

]]>
这里记录过去一周,我看到的值得分享的东西,每周五发布。

欢迎大家去 GitHub 的ruanyf/weekly提交issue,进行投稿。

2005年,斯坦福大学邀请乔布斯在毕业典礼演讲。这个演讲后来成为经典,《乔布斯传》说"或许有些演讲对后世影响更大,但是你找不到(比这篇)更好的演讲。"

演讲中,乔布斯说了一段有名的话。

"你们的时间有限,所以不要把它浪费在过其他人的生活。最重要的是,你要有勇气跟随你的内心和直觉。某种程度上,它们已经知道你真正想要成为什么样子。其他所有事情都是次要的。"

这段话后来被称为"热情假设",很多人都引用它鼓励年轻人:寻找职业方向的时候,要跟随内心的热情(passion),去做那些你有强烈意愿从事的工作。

但是,美国最近出版了一本新书《优秀到无法忽略》(《So Good They Can't Ignore You》),声称乔布斯的这个建议是完全错误的,误导年轻人。别的不说,乔布斯本人也不遵守"热情假设"。年轻时,他对禅宗思想最感兴趣,去印度学习佛教。如果他真的追随自己的内心,他就应该去当一个禅宗老师,而不是跑回美国创办苹果公司。

这本书认为,以下几个原因导致"热情假设"不是一个好的建议。

第一,热情真的很罕见。大部分人都对自己的工作没兴趣,而是对某种爱好(比如打球、钓鱼)有兴趣。如果大部分人都找不到自己的职业热情,你怎么能叫他们去追随热情呢?

第二,热情需要时间来建立。许多人刚开始工作的时候,对自己的职业并没有兴趣,随着时间积累,他们的经验越来越多,能够掌控的东西越来越多,这才慢慢开始热爱自己的工作。找工作阶段,你可能根本不会意识到这个职业就是你的热情所在。

第三,过度强调热情,容易对现状产生不满。2010年的一项调查发现,只有45%的美国人对自己的工作满意。由于很多人相信,无法产生热情的工作不是好工作,导致对职业生涯抱有不切实际的期望,对现有的工作不满意,不断跳槽。

这本书提出,热情不是凭空产生的,它跟自主权有关。如果你在某个职位上的自主权越大,能够掌控的东西越多,就越容易对当前的职业产生热情。与其强调跟随内心的热情,不如强调如何在某种职业里面获得自主权。你必须使自己变得优秀,让别人无法忽视你,同意让你掌控更多的资源,这就是书名的含义。

新闻

1、南极粒子

科学家最近确认,南极洲正在向外太空喷射高能粒子,充满能量的粒子从冰层底下发射,直接飞向太空。科学家目前不知道原因。

下一步,科学家将拟定计划,探索冰层当中或者冰层之下到底是什么东西在喷射粒子。上图为美国在南极点的阿蒙森-斯科特科考站设立的中微子探测器。

2、定制搜索页

谷歌推出验证服务。只要你验证了身份,就可以定制搜索结果。以后别人在谷歌搜索你,就可以看到你留下的内容。

这个功能对公司很有用,可以把本公司的最新动态放到搜索页头条。

3、美国机场的面部识别实验

美国国土安全部去年在九个机场测试脸部识别,结果匹配率只有85%,远低于97%的最低标准。

每个外国人入境时,美国政府会拍照。当外国人离境时,再与照片进行比对,看看是否为同一个人。结果,由于"网络慢、缺乏专职人员等原因",匹配效果不理想。尤其是29岁以下和70岁以上的外国人,经常匹配出错。

4、谷歌搜索的彩蛋

国外用户发现了一个谷歌搜索的彩蛋,搜索 text adventure 后,打开开发者工具,会看到提示,问你要不要玩游戏。回答 yes,就可以开始玩了。

这是一个文字游戏,探索加利福尼亚州山景城的谷歌总部。玩一次游戏,走完整个过程,大约需要30分钟到一个小时。

5、个人信息换咖啡

既然别人可以出售我的个人信息牟利,为什么我自己不能出售呢?

美国罗德岛有一个咖啡馆,在读的大学生可以免费获取咖啡,条件是提供自己的个人信息,包括姓名、电话号码,电子邮件地址、大学专业、出生日期和兴趣,同意接收赞助商的广告信息。

6、取消方向盘

美国国家公路交通安全管理局(NHTSA)表示,在符合条件的情况下,将允许自动驾驶汽车取消方向盘、脚踏板和后视镜。

汽车没有了方向盘,就意味着你只能依靠自动驾驶,最多在触摸板上进行操作。也就是说,操作汽车将来会变得跟操作电梯差不多。

7、独一无二的默认密码

很多互联网连接设备(比如路由器)都有出厂的默认密码,而且默认密码都是一样的,比如"0000"或者"1234"。用户使用时,应该改掉默认密码,但是实际上,许多人并没有修改,包括一些重要机构也是如此,导致巨大的安全隐患,很多 DDOS 攻击都是通过控制网络上的路由器而发起的。

加州最近通过法律,2020年起,任何在加利福尼亚州制造的互联网连接设备,必须每个设备都有不一样的密码,以提高整体的互联网安全。

8、软件接电话

10月9日发布的谷歌 Pixel 3 手机,里面的"谷歌助手"有自动接电话功能,官方明确说能对付推销电话。

软件自动接了电话以后,对方说什么会转成文字,显示在屏幕上,还会回应几句。你确定是骚扰电话,就按一下按钮,软件自动挂断电话,把对方加入黑名单。这个功能真酷,但目前只支持英文。

9、F-16 事故

10月11日,比利时空军发生了一件匪夷所思的事故。一名技术人员正在飞机库房,研究F-16 战斗机,不小心激活了战斗机加农炮。大炮加载,击中另一架F-16 战斗机,后者就击毁了。由于后者刚加过油,大火使得旁边的F-16也完蛋了。比利时空军有60架现役F-16飞机。

一架 F-16 起码3500万美元,这个一不小心导致的事故,真是代价昂贵。

10、AR 导航

高德推出车载 AR 导航,在真实的道路上可以叠加车道线、车辆识别、红绿灯和限速标志等各类对象,给驾驶员带来更直观的实景导航体验。

产品计划首批应用在智能后视镜上,做能力验证,后续将拓展至仪表盘、车机中控屏以及 HUD 平视系统等更多使用场景。(本条为读者@Anderson-Liu投稿)

11、一句话新闻

  • 《华盛顿邮报》称,根据统计,2019年美国一半的电话通话,将是推销电话和诈骗电话。

  • 芬兰的一项医学统计发现,一般的阑尾炎可能不需要手术,只靠抗生素治疗即可。调查中,三分之二病人不再需要手术,剩下的三分之一最后还是需要手术,但是前期的抗生素治疗并没有延误病情。

  • Wi-Fi 联盟发布 Wi-Fi 6 标准,用于支持下一代的 802.11ax 协议的无线连接。前五代协议分别是 802.11a / b / g / n / ac。

  • 日本提出一个法律草案,允许对人类胚胎进行基因编辑。

教程

1、如何写一个 Chrome 浏览器的扩展(英文)

想写 Chrome 浏览器的扩展吗?可以看这篇教程,很容易懂。原始代码有一些小问题,我重新做了一个Demo

2、树莓派如何搭建 NAS(英文)

想要搭建家用储存系统的朋友,可以看这篇教程,使用树莓派和移动硬盘,搭建一个 NAS,操作简单,可玩性高,费用便宜。

3、Kubernetes:令人惊讶的个人项目平台(英文)

流行的观点认为,Kubernetes 是一种过于复杂的技术,只适用于非常大的机器群。我认为这可能是错的,Kubernetes 适用于小型项目。

4、Kubernetes 用于个人项目?不用了,谢谢!(英文)

本文是对上一篇文章的反驳。

5、网页性能的准确测量(英文)

本文讨论如何使用window.requestAnimationFrame()测量脚本操作的准确耗时。

6、区块链技术概述[PDF](英文)

美国国家标准技术研究所(NIST)编写的介绍区块链的小册子,针对一般读者,内容比较全面完整。

7、我为什么起诉 PinScreen?(英文)

一个硅谷工程师起诉了他的华人老板,还制作了一个极其详细的网页。我觉得这招很好,值得借鉴。不管官司能否赢,我要让你曝曝光。

8、为什么国际航线的航路如此混乱?(英文)

《南华早报》的长篇可视化报道,解释为什么国际航线弯弯曲曲,不采用最近的直线距离飞行。本文图文并茂的呈现方式,值得借鉴。

9、CSV 和 JSON(英文)

CSV 和 JSON 两种数据格式的介绍和比较。很多人忽略的一个事实是,最近几年 CSV 的使用量一直在上升。

资源

1、精通 JavaScript 模块

开源电子书,介绍如何编写模块化的 JavaScript 代码。

2、机器学习解释

开源电子书,尝试用通俗的语言解释机器学习。

3、开源火星车

美国航空航天局开源了火星漫游车的设计。完全使用市场上可以买到的材料,爱好者就能做出自己的漫游车。它使用树莓派作为车载控制中心,使用安卓手机或 xbox 手柄遥控。

工具

1、remi

Python 的图形界面库,最大特点就是采用了 HTML 界面。脚本加入这个库以后,可以生成网页接口,使用浏览器访问。

2、dbxfs

dbxfs 允许用户将 Dropbox 目录挂载到本地文件系统。

3、ferret

Go 语言写的 web scraping 工具,主要特点是操作过程是声明式的,非常易写。

4、Sans Forgetica

一种字体,据称有助于增强记忆。

5、nvtop

Linux 服务器的 top 命令可以查看 CPU 的状态。nvtop 命令则是用来查看 NVidia GPU 的状态。

6、Mixnode

Mixnode 是一个收集了全世界网页的数据库,允许使用类似 SQL 的语法查询网页。

7、retejs

可视化编程的 JavaScript 框架。

8、命令行 2048 游戏

C++ 编写的2048游戏。

9、OpenCC

中文繁体和简体互相转换的开源工具,C 语言开发的。

文摘

1、摩托罗拉 Razr V3 手机

2003年7月,摩托罗拉公司发布了 Razr V3 手机。这部手机只是产品小组的一个普通设计,并不是公司主力产品。一开始,摩托罗拉预计销售量是30万部,上市前调高到80万部,上市价格是500美元。

这部手机有一个100万像素(640x480)的摄像头。采用金属机身,键盘也是金属的,而不是通常的橡胶或塑料键盘,这是为了把手机做得很薄,另一个特点是,它的按键比标准尺寸大得多。总之,它具有非常独特的视觉外观。

除了主显示屏,它还有一个小的外部显示屏。手机底部的大下巴,是为了容纳天线。由于这部手机几乎是全金属,会屏蔽信号,所以把天线放在底部。翻盖设计也使得这部手机,有一种翻开和关闭的玩耍的乐趣。如果今天发布这部手机,它的设计仍然会像当时一样酷。

最终,这部手机全球一共卖出了1.3亿部,是有史以来最畅销的手机型号之一。 从这部手机开始,大家意识到,手机不仅仅是通信工具,也是时尚用品。

2、苹果手表的潜力

9月份发布的 Apple Watch 4 在医疗保健上有重大突破。

它有两个很好的功能,一是跌倒检测,如果发现用户跌倒后一分钟内没有站起来,它就会自动拨打报警电话,并且附上用户的 GPS 位置,这对老人非常有用,可以挽救生命。二是,它带有美国食品药物管理局认可的心电图功能,一旦发现心动不正常,就会向用户发警报。

其实,苹果公司还有许多专利。下面这些功能,它应该考虑加入手表。

  • 睡眠呼吸检测。那些睡眠过程中的呼吸停止,通常很难发现,但是如果用户戴着手表过夜就可以。
  • 脉搏血氧仪。Apple Watch 可以根据吸收的红外线光量,来确定血液中含有多少氧气,判断用户的血液是否缺氧。
  • 呼吸率。每人每分钟呼吸的次数。
  • 血压。苹果于2017年申请了两项专利,通过将手表放在胸前来测量血压。
  • 紫外线探测器。苹果已经获得了一种新型传感器的专利,可以让你知道皮肤的紫外线暴露的风险。
  • 帕金森病。苹果在 ResearchKit 框架中添加了一个新的"运动障碍API",支持运动和震颤检测。 它如果发现用户一直在颤抖,就可以提醒用户可能患有帕金森病。
  • 糖尿病。苹果正在申请一项不用抽血的血液葡萄糖传感器。

如果这些功能都能做到,对于很多用户来说,苹果手表可能远比手机重要。

3、马尾巴有什么用?

从大象到长颈鹿到斑马,大型哺乳动物都有尾巴,它们的目的是什么?

这些尾巴看起来很相似 - 它们都是长而薄,上面有黑色的毛发,它们不停地来回摆动。生物学家普遍认为摇摆可以阻止苍蝇,但尾巴到底是怎么做到的?

炎热的夏天,到处都是叮咬的昆虫。短短一天内,一匹马就会因昆虫(如蚊子)叮咬而失去一杯血。蚊子不仅会带走血,还会带来疾病,比如疟疾、寨卡病毒,登革热。远离蚊子,可能会对马的健康产生重大影响。大型哺乳动物,从斑马到长颈鹿,再到大象,都会吸引大量的昆虫。动物越大,尾巴越大。

我们买了一条用真马尾巴制成的鞭子,用它来打蚊子,结果发现根本打不中。原因很简单,蚊子太轻,一只蚊子重2毫克,相当于四分之一的鸡毛,只要鞭子一靠近,带来的风就会把蚊子吹走。

我们发现,当尾巴静止时,蚊子很容易飞过来,停在天花板上。但当尾巴快速摆动时,蚊子只要飞向尾巴就会掉头,因为尾巴摆动产生的风足以驱逐一半的蚊子。

所以,为什么动物如此迅速地摆动它们的尾巴呢?这是因为它们必须产生与蚊子飞行速度相当的风,速度大约是每秒一米或每小时两英里。马尾不仅仅是一种装饰品,这是他们抵御昆虫叮咬的主要防线。

本周图片

1、不是橙色的水果

如果你在搜索引擎里面,搜索"不是橙色的水果",结果会恰恰相反,返回的都是橙色的水果。这证明,搜索引擎目前都是基于关键词,而不是语义搜索。

2、火星基地

SpaceX 公司的老板马斯克,在推特上贴出一张图片,表示这是 SpaceX 公司火星基地的设计图片。

新奇

1、穷人的降噪耳机

开放式办公环境,为了避免打扰,通常我们需要一副降噪耳机。但是,好的降噪耳机很贵,有时做工也不令人满意,很容易坏。国外就有开发者想出 DIY 降噪耳机。

只要找一副隔音效果较好的有线耳机,然后外接一个蓝牙接收器即可,总成本在300元人民币以下,效果完全不输那些名牌产品。

本周金句

1、

如果一个人不想做某件事,通常不是由于客观条件不允许,而是他有下面四种心态之一:恐惧(Fear)、排斥(Rejection)、自卑(Low self-esteem)、怠惰(Laziness)。

--《不要对自己撒谎

2、

神经疾病(比如帕金森病、癫痫、阿尔茨海默病)都涉及神经系统(大脑,脊髓和神经)的故障或损伤,精神疾病的标志则是行为不安和情绪状态。

--《神经疾病和精神疾病有什么区别?》

3、

最早,我们做的是一个地理位置应用,人们到了一个地点,可以签到和发照片。我们发现,人们对位置不太在乎,只是希望将照片放在那里。

后来,由于发展得不好,我们决定简化功能,只保留照片、评论,以及给照片标识位置,应用的名字改成了 Instagram。

--《Instagram 的故事》

欢迎订阅

这个专栏每周五发布,同步更新在我的个人网站微信公众号语雀

微信搜索"阮一峰的网络日志 "或者扫描二维码,即可订阅。

(完)

文档信息

]]>
0
<![CDATA[emojify.php, 让 Typecho 用上 Emoji 表情]]> http://www.udpwork.com/item/17155.html http://www.udpwork.com/item/17155.html#reviews Thu, 18 Oct 2018 15:35:00 +0800 明城 http://www.udpwork.com/item/17155.html Emoji能让单调的沟通多些色彩,团队这边近期也开始使用 Emoji 作为 git 提交的提示符使用

其中,有很多相关的命令行工具可以使用。但翻遍了一圈,竟然没有 PHP 相关的类库,于是就自己写了一个( 很久没写 PHP 了,感觉生疏了好多 😑 )。

表情符号的数据源自好几个项目,所以从数据量上面来说应该是目前 emojify 类工具的超集了,超过了两千多条。例如,可以完全放心使用emoji-cheat-sheet下的定义。

具体的对应数据可以在这里查看(文件很大),顺便还可以看看自己系统对于 Emoji 字符的支持情况。

因为是 PHP 相关,所以在完成这个简单的类库以后顺便做成了Typecho插件。这个插件安装很简单,只要在 Typecho 的 Plugin 目录下无脑 git clone :

git clone git@github.com:mingcheng/emojify.php.git  Emojify

然后使用管理员登录,开启插件就可以使用了 😘。Github 的地址在https://github.com/mingcheng/emojify.php这里,欢迎测试以及提交 Issues(当然,你也可以留言测试下 Emoji 😄 )。

- eof -

]]>
Emoji能让单调的沟通多些色彩,团队这边近期也开始使用 Emoji 作为 git 提交的提示符使用

其中,有很多相关的命令行工具可以使用。但翻遍了一圈,竟然没有 PHP 相关的类库,于是就自己写了一个( 很久没写 PHP 了,感觉生疏了好多 😑 )。

表情符号的数据源自好几个项目,所以从数据量上面来说应该是目前 emojify 类工具的超集了,超过了两千多条。例如,可以完全放心使用emoji-cheat-sheet下的定义。

具体的对应数据可以在这里查看(文件很大),顺便还可以看看自己系统对于 Emoji 字符的支持情况。

因为是 PHP 相关,所以在完成这个简单的类库以后顺便做成了Typecho插件。这个插件安装很简单,只要在 Typecho 的 Plugin 目录下无脑 git clone :

git clone git@github.com:mingcheng/emojify.php.git  Emojify

然后使用管理员登录,开启插件就可以使用了 😘。Github 的地址在https://github.com/mingcheng/emojify.php这里,欢迎测试以及提交 Issues(当然,你也可以留言测试下 Emoji 😄 )。

- eof -

]]>
0
<![CDATA[Flexbox 布局的最简单表单]]> http://www.udpwork.com/item/17154.html http://www.udpwork.com/item/17154.html#reviews Thu, 18 Oct 2018 08:17:18 +0800 阮一峰 http://www.udpwork.com/item/17154.html 弹性布局(Flexbox)逐渐流行,越来越多人使用,因为它写 CSS 布局真是太方便了。

三年前,我写过 Flexbox 的介绍(),但是有些地方写得不清楚。今天,我看到一篇教程,才意识到一个最简单的表单,就可以解释 Flexbox,而且内容还很实用。

下面,你只需要10分钟,就可以学会简单的表单布局。

一、<form> 元素

表单使用<form>元素。

<form></form>

上面是一个空表单。根据 HTML 标准,它是一个块级元素,默认将占据全部宽度,但是高度为0,因为没有任何内容。

二、表单控件

现在,加入两个最常用的表单控件。

<form>
  <input type="email" name="email">
  <button type="submit">Send</button>
</form>

上面代码中,表单包含一个输入框(<input>)和一个按钮(<button>)。

根据标准,这两个控件都是行内块级元素(inline-block),也就是说,它们默认并排在一行上。

上图是浏览器对这个表单的默认渲染(颜色除外),可以看到,这两个控件之间有3像素~4像素的间隔,这是浏览器的内置样式指定的。

三、指定 Flexbox 布局

接着,指定表单使用 Flexbox 布局。

form  {
  display: flex;
}

可以看到,两个控件之间的间隔消失了,因为弹性布局的项目(item)默认没有间隔。

四、flex-grow 属性

两个地方值得注意。

(1)两个控件元素的宽度没有发生变化,因为弹性布局默认不改变项目的宽度。

(2)弹性布局默认左对齐,所以两个控件会从行首开始排列。

如果我们希望,输入框占据当前行的所有剩余宽度,只需要指定输入框的flex-grow属性为1。

input  {
  flex-grow: 1;
}

上图中,按钮的宽度没变,但是输入框变宽了,等于当前行的宽度减去按钮的宽度。

flex-grow属性默认等于0,即使用本来的宽度,不拉伸。等于1时,就表示该项目宽度拉伸,占据当前行的所有剩余宽度。

五、align-self 属性和 align-items 属性

我们做一点改变,在按钮里面插入一张图片。

<form action="#">
  <input type="email" placeholder="Enter your email">
  <button type="button"><svg>  <!-- a smiley icon -->  </svg></button>
</form>

按钮插入图片后,它的高度变了,变得更高了。这时,就发生了一件很奇妙的事情。

上图中,按钮变高了,输入框也自动变得一样高了!

前面说过,弹性布局默认不改变项目的宽度,但是它默认改变项目的高度。如果项目没有显式指定高度,就将占据容器的所有高度。 本例中,按钮变高了,导致表单元素也变高了,使得输入框的高度自动拉伸了。

align-self属性可以改变这种行为。

input {
  flex-grow: 1;
  align-self: center;
}

align-self属性可以取四个值。

  • flex-start:顶边对齐,高度不拉伸
  • flex-end:底边对齐,高度不拉伸
  • center:居中,高度不拉伸
  • stretch:默认值,高度自动拉伸

如果项目很多,一个个地设置align-self属性就很麻烦。这时,可以在容器元素(本例为表单)设置align-items属性,它的值被所有子项目的align-self属性继承。

form {
  display: flex;
  align-items: center;
}

上面代码中,<form>元素设置了align-items以后,就不用在控件上设置align-self,除非希望两者的值不一样。

(完)

文档信息

]]>
弹性布局(Flexbox)逐渐流行,越来越多人使用,因为它写 CSS 布局真是太方便了。

三年前,我写过 Flexbox 的介绍(),但是有些地方写得不清楚。今天,我看到一篇教程,才意识到一个最简单的表单,就可以解释 Flexbox,而且内容还很实用。

下面,你只需要10分钟,就可以学会简单的表单布局。

一、<form> 元素

表单使用<form>元素。

<form></form>

上面是一个空表单。根据 HTML 标准,它是一个块级元素,默认将占据全部宽度,但是高度为0,因为没有任何内容。

二、表单控件

现在,加入两个最常用的表单控件。

<form>
  <input type="email" name="email">
  <button type="submit">Send</button>
</form>

上面代码中,表单包含一个输入框(<input>)和一个按钮(<button>)。

根据标准,这两个控件都是行内块级元素(inline-block),也就是说,它们默认并排在一行上。

上图是浏览器对这个表单的默认渲染(颜色除外),可以看到,这两个控件之间有3像素~4像素的间隔,这是浏览器的内置样式指定的。

三、指定 Flexbox 布局

接着,指定表单使用 Flexbox 布局。

form  {
  display: flex;
}

可以看到,两个控件之间的间隔消失了,因为弹性布局的项目(item)默认没有间隔。

四、flex-grow 属性

两个地方值得注意。

(1)两个控件元素的宽度没有发生变化,因为弹性布局默认不改变项目的宽度。

(2)弹性布局默认左对齐,所以两个控件会从行首开始排列。

如果我们希望,输入框占据当前行的所有剩余宽度,只需要指定输入框的flex-grow属性为1。

input  {
  flex-grow: 1;
}

上图中,按钮的宽度没变,但是输入框变宽了,等于当前行的宽度减去按钮的宽度。

flex-grow属性默认等于0,即使用本来的宽度,不拉伸。等于1时,就表示该项目宽度拉伸,占据当前行的所有剩余宽度。

五、align-self 属性和 align-items 属性

我们做一点改变,在按钮里面插入一张图片。

<form action="#">
  <input type="email" placeholder="Enter your email">
  <button type="button"><svg>  <!-- a smiley icon -->  </svg></button>
</form>

按钮插入图片后,它的高度变了,变得更高了。这时,就发生了一件很奇妙的事情。

上图中,按钮变高了,输入框也自动变得一样高了!

前面说过,弹性布局默认不改变项目的宽度,但是它默认改变项目的高度。如果项目没有显式指定高度,就将占据容器的所有高度。 本例中,按钮变高了,导致表单元素也变高了,使得输入框的高度自动拉伸了。

align-self属性可以改变这种行为。

input {
  flex-grow: 1;
  align-self: center;
}

align-self属性可以取四个值。

  • flex-start:顶边对齐,高度不拉伸
  • flex-end:底边对齐,高度不拉伸
  • center:居中,高度不拉伸
  • stretch:默认值,高度自动拉伸

如果项目很多,一个个地设置align-self属性就很麻烦。这时,可以在容器元素(本例为表单)设置align-items属性,它的值被所有子项目的align-self属性继承。

form {
  display: flex;
  align-items: center;
}

上面代码中,<form>元素设置了align-items以后,就不用在控件上设置align-self,除非希望两者的值不一样。

(完)

文档信息

]]>
0
<![CDATA[exFAT 文件系统指南]]> http://www.udpwork.com/item/17152.html http://www.udpwork.com/item/17152.html#reviews Tue, 16 Oct 2018 17:35:50 +0800 阮一峰 http://www.udpwork.com/item/17152.html 国庆假期,我拍了一些手机视频,打算存到新买的移动硬盘。

然后,就傻眼了。我的 Mac 电脑无法写入移动硬盘,因为移动硬盘的默认文件系统是 NTFS,Mac 不支持写入 NTFS。

虽然可以买一个软件解决这个问题,但是我不想为这种功能付钱。经过一番研究,我发现把移动硬盘的文件系统改成 exFAT,就可以解决问题,Mac 原生支持读写 exFAT。

由于这个问题很普遍,下面我就来写一写跟 exFAT 相关的知识。

一、文件系统

所谓文件系统,就是文件的储存方式。简单说,它就是一个门牌系统,为储存设备划分门牌号,每个文件分配一个门牌,然后就能按照门牌找到文件。

没有文件系统的硬盘,就是一块荒地。如果有人住在那里,你只能说那里有人住,精确位置你说不出来。只有划分了路牌,你才能说出,这个人住在"人民路15号",这样才能精确定位。文件系统就是路牌的划分方法。

储存设备都需要指定文件系统,计算机才能读写。所谓"格式化",就是为硬盘安装文件系统。不同的操作系统有不同的文件系统,Linux 使用 ext4,OSX使用 HFS +,Windows 使用 NTFS,Solaris 和 Unix 使用ZFS。如果计算机不认识某个文件系统,就会显示这块盘无法读写。

现在的问题就是,NTFS 文件系统是 Windows 的专有系统,Mac 可以读,但是默认不能写入。

二、Windows 的文件系统

Windows 系统主要有三种文件系统。

  • FAT32
  • NTFS
  • exFAT

格式化硬盘的时候,Windows 系统会提供这三种文件系统让你选。这时应该选哪一种呢?

FAT32 是最老的文件系统,所有操作系统都支持,兼容性最好。但是,它是为32位计算机设计的,文件不能超过 232- 1 个字节,也就是不能超过 4GB,分区不能超过 8TB。目前来看,这个文件系统有点过时了,只适合小文件,如果有大的视频文件,就不能使用它。

NTFS 是 Windows 的默认文件系统,用来替换 FAT32。Windows 的系统盘只能使用这个系统,移动硬盘买来装的也是它。

exFAT 可以看作是 FAT32 的64位升级版,ex就是 extended 的缩写(表示"扩展的 FAT32"),功能不如 NTFS,但是解决了文件和分区的大小问题,两者最大都可以到 128PB。由于 Mac 和 Linux 电脑可以读写这种系统,所以移动硬盘的文件系统可以改成它。

三、解决方案

移动硬盘买来后,你把它格式化成 exFAT 文件系统,问题就解决了。

Windows 在资源管理器或我的电脑里面,都可以进行格式化。

Mac 在磁盘工具进行格式化。

格式化完成后,就 OK 了。如果你使用 Linux 系统,可能需要装一下 exFAT 支持,Ubuntu 和 Debian 执行下面的命令。

$ sudo apt-get install exfat-utils exfat-fuse

一般读者读到这里,就可以了。如果你像我一样,想用 Linux 进行 exFAT 格式化,请接着往下读。

四、Linux 的 exFAT 格式化

Linux 进行硬盘格式化,需要先找到设备路径。

$ sudo fdisk -l

上面命令会列出本机的所有储存设备,移动硬盘一般是/dev/sdX1的形式,比如/dev/sdc1。这里需要了解sdX1的含义,sd表示可移动设备和SATA 设备,X表示设备的序号,依次为 a、b、c 等,最后的1表示这是该设备的第一个分区。

然后,使用下面的命令进行格式化。

$ sudo mkfs.exfat /dev/sdX1

注意,如果你的储存设备只显示为/dev/sdX,没有最后的数字,表明这个设备没有分区。exFAT 只能用来格式化硬盘的一个分区,所以必须先分区,再格式化,下面介绍如何分区。

五、分区表

所谓硬盘分区,就是指一块硬盘上面,同时存在多个文件系统。每个文件系统管理的区域,就称为一个分区(partition)。比如,一块 100 GB 的硬盘,可以一半是 NTFS 分区,另一半是 exFAT 分区。

硬盘必须先分区,才能指定每个区的文件系统。分区大小、起始位置、结束位置、文件系统等信息,都储存在分区表里面。

分区表也分成两种格式:MBR 和 GPT。前者是传统格式,兼容性好;后者更现代,功能更强大。一般来说,都推荐使用 GPT。gdisk命令用于分区操作。

$ sudo gdisk /dev/sdX
GPT fdisk (gdisk) version 0.8.8

Partition table scan:
  MBR: not present
  BSD: not present
  APM: not present
  GPT: not present

Creating new GPT entries.

Command (? for help):

上面命令表示对/dev/sdX进行分区。输出结果表明,这个设备还没有分区表。

第一步,o命令表示创建 GPT 分区表。

Command (? for help): o
This option deletes all partitions and creates a new protective MBR.
Proceed? (Y/N): Y

第二步,n命令表示新建一个分区。

Command (? for help): n
Partition number (1-128, default 1):
First sector (34-16326462, default = 2048) or {+-}size{KMGTP}:
Last sector (2048-16326462, default = 16326462) or {+-}size{KMGTP}:
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): 0700
Changed type of partition to 'Microsoft basic data'

上面代码中,分区号(Partition number,默认为1)、起始扇区、结束扇区,都可以接受默认值,直接按回车。这时整个硬盘只建一个分区,占据所有空间。文件系统的类型要设成0700,代表 exFAT。

第三步,w命令表示写入所有变更。

Command (? for help): w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): Y
OK; writing new GUID partition table (GPT) to /dev/sdX.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot.
The operation has completed successfully.

到了这一步,分区表应该已经建立了。然后,使用上一节的命令,建立 exFAT 文件系统。

$ sudo mkfs.exfat /dev/sdX1
mkexfatfs 1.0.1
Creating... done.
Flushing... done.
File system created successfully.

六、参考链接

(完)

文档信息

]]>
国庆假期,我拍了一些手机视频,打算存到新买的移动硬盘。

然后,就傻眼了。我的 Mac 电脑无法写入移动硬盘,因为移动硬盘的默认文件系统是 NTFS,Mac 不支持写入 NTFS。

虽然可以买一个软件解决这个问题,但是我不想为这种功能付钱。经过一番研究,我发现把移动硬盘的文件系统改成 exFAT,就可以解决问题,Mac 原生支持读写 exFAT。

由于这个问题很普遍,下面我就来写一写跟 exFAT 相关的知识。

一、文件系统

所谓文件系统,就是文件的储存方式。简单说,它就是一个门牌系统,为储存设备划分门牌号,每个文件分配一个门牌,然后就能按照门牌找到文件。

没有文件系统的硬盘,就是一块荒地。如果有人住在那里,你只能说那里有人住,精确位置你说不出来。只有划分了路牌,你才能说出,这个人住在"人民路15号",这样才能精确定位。文件系统就是路牌的划分方法。

储存设备都需要指定文件系统,计算机才能读写。所谓"格式化",就是为硬盘安装文件系统。不同的操作系统有不同的文件系统,Linux 使用 ext4,OSX使用 HFS +,Windows 使用 NTFS,Solaris 和 Unix 使用ZFS。如果计算机不认识某个文件系统,就会显示这块盘无法读写。

现在的问题就是,NTFS 文件系统是 Windows 的专有系统,Mac 可以读,但是默认不能写入。

二、Windows 的文件系统

Windows 系统主要有三种文件系统。

  • FAT32
  • NTFS
  • exFAT

格式化硬盘的时候,Windows 系统会提供这三种文件系统让你选。这时应该选哪一种呢?

FAT32 是最老的文件系统,所有操作系统都支持,兼容性最好。但是,它是为32位计算机设计的,文件不能超过 232- 1 个字节,也就是不能超过 4GB,分区不能超过 8TB。目前来看,这个文件系统有点过时了,只适合小文件,如果有大的视频文件,就不能使用它。

NTFS 是 Windows 的默认文件系统,用来替换 FAT32。Windows 的系统盘只能使用这个系统,移动硬盘买来装的也是它。

exFAT 可以看作是 FAT32 的64位升级版,ex就是 extended 的缩写(表示"扩展的 FAT32"),功能不如 NTFS,但是解决了文件和分区的大小问题,两者最大都可以到 128PB。由于 Mac 和 Linux 电脑可以读写这种系统,所以移动硬盘的文件系统可以改成它。

三、解决方案

移动硬盘买来后,你把它格式化成 exFAT 文件系统,问题就解决了。

Windows 在资源管理器或我的电脑里面,都可以进行格式化。

Mac 在磁盘工具进行格式化。

格式化完成后,就 OK 了。如果你使用 Linux 系统,可能需要装一下 exFAT 支持,Ubuntu 和 Debian 执行下面的命令。

$ sudo apt-get install exfat-utils exfat-fuse

一般读者读到这里,就可以了。如果你像我一样,想用 Linux 进行 exFAT 格式化,请接着往下读。

四、Linux 的 exFAT 格式化

Linux 进行硬盘格式化,需要先找到设备路径。

$ sudo fdisk -l

上面命令会列出本机的所有储存设备,移动硬盘一般是/dev/sdX1的形式,比如/dev/sdc1。这里需要了解sdX1的含义,sd表示可移动设备和SATA 设备,X表示设备的序号,依次为 a、b、c 等,最后的1表示这是该设备的第一个分区。

然后,使用下面的命令进行格式化。

$ sudo mkfs.exfat /dev/sdX1

注意,如果你的储存设备只显示为/dev/sdX,没有最后的数字,表明这个设备没有分区。exFAT 只能用来格式化硬盘的一个分区,所以必须先分区,再格式化,下面介绍如何分区。

五、分区表

所谓硬盘分区,就是指一块硬盘上面,同时存在多个文件系统。每个文件系统管理的区域,就称为一个分区(partition)。比如,一块 100 GB 的硬盘,可以一半是 NTFS 分区,另一半是 exFAT 分区。

硬盘必须先分区,才能指定每个区的文件系统。分区大小、起始位置、结束位置、文件系统等信息,都储存在分区表里面。

分区表也分成两种格式:MBR 和 GPT。前者是传统格式,兼容性好;后者更现代,功能更强大。一般来说,都推荐使用 GPT。gdisk命令用于分区操作。

$ sudo gdisk /dev/sdX
GPT fdisk (gdisk) version 0.8.8

Partition table scan:
  MBR: not present
  BSD: not present
  APM: not present
  GPT: not present

Creating new GPT entries.

Command (? for help):

上面命令表示对/dev/sdX进行分区。输出结果表明,这个设备还没有分区表。

第一步,o命令表示创建 GPT 分区表。

Command (? for help): o
This option deletes all partitions and creates a new protective MBR.
Proceed? (Y/N): Y

第二步,n命令表示新建一个分区。

Command (? for help): n
Partition number (1-128, default 1):
First sector (34-16326462, default = 2048) or {+-}size{KMGTP}:
Last sector (2048-16326462, default = 16326462) or {+-}size{KMGTP}:
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): 0700
Changed type of partition to 'Microsoft basic data'

上面代码中,分区号(Partition number,默认为1)、起始扇区、结束扇区,都可以接受默认值,直接按回车。这时整个硬盘只建一个分区,占据所有空间。文件系统的类型要设成0700,代表 exFAT。

第三步,w命令表示写入所有变更。

Command (? for help): w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): Y
OK; writing new GUID partition table (GPT) to /dev/sdX.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot.
The operation has completed successfully.

到了这一步,分区表应该已经建立了。然后,使用上一节的命令,建立 exFAT 文件系统。

$ sudo mkfs.exfat /dev/sdX1
mkexfatfs 1.0.1
Creating... done.
Flushing... done.
File system created successfully.

六、参考链接

(完)

文档信息

]]>
0
<![CDATA[小记]]> http://www.udpwork.com/item/17132.html http://www.udpwork.com/item/17132.html#reviews Mon, 15 Oct 2018 06:20:34 +0800 qyjohn http://www.udpwork.com/item/17132.html 昨日在悉尼的文昌中学校友聚会,校友们带来了许多自家制作的海南菜和海南点心,吃了个饱。

聚会后,和几位到哥涛的新家去小坐。我在文昌中学读书时,哥涛的父亲韩海光任教导。闲聊中哥涛提起他的爷爷韩超南(锦山人)早年从广东省立第六师范学校毕业,大约于1936年前后在燕京大学读物理专业。五十年代起在文昌中学教书,高十五届校友林树楫说是1965年曾经教过他。文革期间下放到农村,1978年起又回到侨中教书。《琼山县志》第二十七篇《人物》中关于“王尊荣”的条目下,有短短的一句提到韩超南,如下:“抗战结束后……韩汉英、陈济棠主政琼崖……韩超南任琼崖公路局长”。

哥涛在读书的年龄,正好遇上文革,所以没读多少书。但是因为祖孙三代的关系,哥涛在文中住的时间很长。哥涛问大家:“你说你站文中早,我问你,文中的第一架车是什么车,咪时候买的?”大家都回答不出来。哥涛嘿嘿一笑,说:“1978年,一辆面包车。开车的是一个伯爹,现在已经过世了。”

1978年,文中有一个政策,凡是文中的教职工子女,可以无限期地读复习班。用校长邹福如的话来说,就是一直复习到文中大门崩。

然而哥涛没有去复读。他做汽车生意去了。

这些事情,都是闲聊间的速记,不一定准确。等有空的时候,我再约哥涛慢慢聊。

 

]]>
昨日在悉尼的文昌中学校友聚会,校友们带来了许多自家制作的海南菜和海南点心,吃了个饱。

聚会后,和几位到哥涛的新家去小坐。我在文昌中学读书时,哥涛的父亲韩海光任教导。闲聊中哥涛提起他的爷爷韩超南(锦山人)早年从广东省立第六师范学校毕业,大约于1936年前后在燕京大学读物理专业。五十年代起在文昌中学教书,高十五届校友林树楫说是1965年曾经教过他。文革期间下放到农村,1978年起又回到侨中教书。《琼山县志》第二十七篇《人物》中关于“王尊荣”的条目下,有短短的一句提到韩超南,如下:“抗战结束后……韩汉英、陈济棠主政琼崖……韩超南任琼崖公路局长”。

哥涛在读书的年龄,正好遇上文革,所以没读多少书。但是因为祖孙三代的关系,哥涛在文中住的时间很长。哥涛问大家:“你说你站文中早,我问你,文中的第一架车是什么车,咪时候买的?”大家都回答不出来。哥涛嘿嘿一笑,说:“1978年,一辆面包车。开车的是一个伯爹,现在已经过世了。”

1978年,文中有一个政策,凡是文中的教职工子女,可以无限期地读复习班。用校长邹福如的话来说,就是一直复习到文中大门崩。

然而哥涛没有去复读。他做汽车生意去了。

这些事情,都是闲聊间的速记,不一定准确。等有空的时候,我再约哥涛慢慢聊。

 

]]>
0
<![CDATA[有点意思的Kotlin的默认参数与JVMOverloads]]> http://www.udpwork.com/item/17131.html http://www.udpwork.com/item/17131.html#reviews Sun, 14 Oct 2018 19:18:00 +0800 技术小黑屋 http://www.udpwork.com/item/17131.html 在Java中,当我们定义一个类的时候,总会出现一些变量是必须要填写的,而另一些是可选的。比如像下面这样,我们定一个Person类,其中name是必须填写的,而性别sex和isChinese可选,如果不填写就直接使用默认值。

1
2
3
4
5
6
public class Person {
   public Person(String name) {}
   public Person(String name, int sex) {}
   public Person(String name, boolean isChinese){}
   public Person(String name, int sex, boolean isChinese) {}
}

当仅仅只有这两个可选参数时,上述的情况还好很多,可是当新增了其他的属性的时候,我们需要实现更多的构造方法重载。这在Java中更加容易出现telescoping constructor的问题,进而影响我们的开发效率和代码可读性。

在Kotlin中,这种问题得到了很好的解决。这便是要提到的方法的默认参数,其实这个很简单,在其他的语言也是支持的。

便于大家理解,我们先看一看默认参数是什么,下面是一个Book的类和它的构造方法(Kotlin代码)

1
2
3
class Book(var name: String, val isChineseBook: Boolean = true,
          val hasMultipleAuthor: Boolean = false, val isPopular: Boolean = false,
           val isForChildren: Boolean = false) {

我们在调用的时候可以按照如下的Kotlin代码

1
2
3
4
5
6
1 Book("Book0")
2 Book("Book1", isForChildren = false)
3 Book("Book2", true)
4 Book("Book3", true, true)
5 Book("Book4", true, true, true)
6 Book("Book5", true, true, true, true)

我们可以根据自己的需要填写必要的参数值,当然也可以像第1行Book("Book1", isForChildren = false)不按照顺序填写参数也是可以的,这是一个很赞的特性,能很大程度上增强代码的可读性。

但是Kotlin的这一特性,只应用于Kotlin代码调用的场景,如果是在Java代码中,我们还是必须要填写完整的参数。这一点着实令人沮丧。不过还在有一个解决办法,那就是使用@JvmOverloads注解,示例如下

1
class People @JvmOverloads constructor(val name: String, val sex: Int = 1, val isChinese: Boolean = true)

在Java中调用示例效果

1
2
3
4
//call constructor with JVMOverloads
People people = new People("");
People people1 = new People("", 0);
People people2 = new People("", 1, true);

那么JvmOverloads是如何工作的呢?

其实@JvmOverloads的作用就是告诉编译器,自动生成多个该方法的重载。因为我们通过反编译分析即可验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@JvmOverloads
public People(@NotNull String name, int sex, boolean isChinese) {
  Intrinsics.checkParameterIsNotNull(name, "name");
  super();
  this.name = name;
  this.sex = sex;
  this.isChinese = isChinese;
}

// $FF: synthetic method
@JvmOverloads
public People(String var1, int var2, boolean var3, int var4, DefaultConstructorMarker var5) {
  if((var4 & 2) != 0) {
     var2 = 1;
  }

  if((var4 & 4) != 0) {
     var3 = true;
  }

  this(var1, var2, var3);
}

@JvmOverloads
public People(@NotNull String name, int sex) {
  this(name, sex, false, 4, (DefaultConstructorMarker)null);
}

@JvmOverloads
public People(@NotNull String name) {
  this(name, 0, false, 6, (DefaultConstructorMarker)null);
}

注意,上面的重载方法并没有按照组合来生成,比如public People(@NotNull String name, int sex, boolean isChinese),因为这样也是出于可读性来考虑和避免潜在方法签名冲突问题。

最后,我们来研究一下Kotlin中默认参数的实现原理。因为这里面存在着一些程序设计的巧妙之处。

这里我们还是使用刚刚提到的Book这个类

1
2
3
4
class Book(var name: String, val isChineseBook: Boolean = true,
          val hasMultipleAuthor: Boolean = false, val isPopular: Boolean = false,
           val isForChildren: Boolean = false) {
}

通过反编译,我们得到了一些类似这样的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public Book(@NotNull String name, boolean isChineseBook, boolean hasMultipleAuthor, boolean isPopular, boolean isForChildren) {
  Intrinsics.checkParameterIsNotNull(name, "name");
  super();
  this.name = name;
  this.isChineseBook = isChineseBook;
  this.hasMultipleAuthor = hasMultipleAuthor;
  this.isPopular = isPopular;
  this.isForChildren = isForChildren;
}

// $FF: synthetic method
public Book(String var1, boolean var2, boolean var3, boolean var4, boolean var5, int var6, DefaultConstructorMarker var7) {
  if((var6 & 2) != 0) {
     var2 = true;
  }

  if((var6 & 4) != 0) {
     var3 = false;
  }

  if((var6 & 8) != 0) {
     var4 = false;
  }

  if((var6 & 16) != 0) {
     var5 = false;
  }

  this(var1, var2, var3, var4, var5);
}

是不是有点不一样,它只生成了两个构造方法,而不是所谓的多个参数组合的构造方法。更有意思的是,当我们这样调用时

1
2
3
4
5
Book("Book0")
Book("Book2", true)
Book("Book3", true, true)
Book("Book4", true, true, true)
Book("Book5", true, true, true, true)

其对应的字节码反编译成java是

1
2
3
4
5
new Book("Book0", false, false, false, false, 30, (DefaultConstructorMarker)null);
new Book("Book2", true, false, false, false, 28, (DefaultConstructorMarker)null);
new Book("Book3", true, true, false, false, 24, (DefaultConstructorMarker)null);
new Book("Book4", true, true, true, false, 16, (DefaultConstructorMarker)null);
new Book("Book5", true, true, true, true);

我们会注意到上面有很多数字,比如30,14,28,24,16等。那么这些数字是怎么生成的呢?

对于构造方法的每个参数,

  • 都有一个位置,即方法声明时所在的位置,我们这里使用i代替表示。注意该从0开始,
  • 每个参数有一个mask值,该值为2的i次方,比如第0个位置的参数的mask值为1,第1个位置的mask值为2,以此类推。
  • 如果在调用时,编译器检测到某些参数没有调用,就将这些参数的mask值,求和,便生成了我们上面提到的数字。

具体示例如下

https://asset.droidyue.com/image/kotlin_jvm_overloads.png

比如Book(“Book0”)我们传递了第一个参数,所以最后的30 就是由 2 + 4 + 8 + 16 这些缺失的位置的mask值计算得出来的。

知道了,mask值的生成规则,就便于我们理解编译器生成的构造方法了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// $FF: synthetic method
public Book(String var1, boolean var2, boolean var3, boolean var4, boolean var5, int var6, DefaultConstructorMarker var7) {
  if((var6 & 2) != 0) {
     var2 = true;
  }

  if((var6 & 4) != 0) {
     var3 = false;
  }

  if((var6 & 8) != 0) {
     var4 = false;
  }

  if((var6 & 16) != 0) {
     var5 = false;
  }

  this(var1, var2, var3, var4, var5);
}

其实这个构造方法就是根据根据mask判断,某个位置的参数是否在调用时进行了赋值,如果没有赋值则进行设置默认值操作。

这种使用mask或者flag的方法其实很巧,减少了一些不必要的重载方法的生成。对于我们以后处理类似的问题,提供了一些不过的思路和参考价值。

]]>
在Java中,当我们定义一个类的时候,总会出现一些变量是必须要填写的,而另一些是可选的。比如像下面这样,我们定一个Person类,其中name是必须填写的,而性别sex和isChinese可选,如果不填写就直接使用默认值。

1
2
3
4
5
6
public class Person {
   public Person(String name) {}
   public Person(String name, int sex) {}
   public Person(String name, boolean isChinese){}
   public Person(String name, int sex, boolean isChinese) {}
}

当仅仅只有这两个可选参数时,上述的情况还好很多,可是当新增了其他的属性的时候,我们需要实现更多的构造方法重载。这在Java中更加容易出现telescoping constructor的问题,进而影响我们的开发效率和代码可读性。

在Kotlin中,这种问题得到了很好的解决。这便是要提到的方法的默认参数,其实这个很简单,在其他的语言也是支持的。

便于大家理解,我们先看一看默认参数是什么,下面是一个Book的类和它的构造方法(Kotlin代码)

1
2
3
class Book(var name: String, val isChineseBook: Boolean = true,
          val hasMultipleAuthor: Boolean = false, val isPopular: Boolean = false,
           val isForChildren: Boolean = false) {

我们在调用的时候可以按照如下的Kotlin代码

1
2
3
4
5
6
1 Book("Book0")
2 Book("Book1", isForChildren = false)
3 Book("Book2", true)
4 Book("Book3", true, true)
5 Book("Book4", true, true, true)
6 Book("Book5", true, true, true, true)

我们可以根据自己的需要填写必要的参数值,当然也可以像第1行Book("Book1", isForChildren = false)不按照顺序填写参数也是可以的,这是一个很赞的特性,能很大程度上增强代码的可读性。

但是Kotlin的这一特性,只应用于Kotlin代码调用的场景,如果是在Java代码中,我们还是必须要填写完整的参数。这一点着实令人沮丧。不过还在有一个解决办法,那就是使用@JvmOverloads注解,示例如下

1
class People @JvmOverloads constructor(val name: String, val sex: Int = 1, val isChinese: Boolean = true)

在Java中调用示例效果

1
2
3
4
//call constructor with JVMOverloads
People people = new People("");
People people1 = new People("", 0);
People people2 = new People("", 1, true);

那么JvmOverloads是如何工作的呢?

其实@JvmOverloads的作用就是告诉编译器,自动生成多个该方法的重载。因为我们通过反编译分析即可验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@JvmOverloads
public People(@NotNull String name, int sex, boolean isChinese) {
  Intrinsics.checkParameterIsNotNull(name, "name");
  super();
  this.name = name;
  this.sex = sex;
  this.isChinese = isChinese;
}

// $FF: synthetic method
@JvmOverloads
public People(String var1, int var2, boolean var3, int var4, DefaultConstructorMarker var5) {
  if((var4 & 2) != 0) {
     var2 = 1;
  }

  if((var4 & 4) != 0) {
     var3 = true;
  }

  this(var1, var2, var3);
}

@JvmOverloads
public People(@NotNull String name, int sex) {
  this(name, sex, false, 4, (DefaultConstructorMarker)null);
}

@JvmOverloads
public People(@NotNull String name) {
  this(name, 0, false, 6, (DefaultConstructorMarker)null);
}

注意,上面的重载方法并没有按照组合来生成,比如public People(@NotNull String name, int sex, boolean isChinese),因为这样也是出于可读性来考虑和避免潜在方法签名冲突问题。

最后,我们来研究一下Kotlin中默认参数的实现原理。因为这里面存在着一些程序设计的巧妙之处。

这里我们还是使用刚刚提到的Book这个类

1
2
3
4
class Book(var name: String, val isChineseBook: Boolean = true,
          val hasMultipleAuthor: Boolean = false, val isPopular: Boolean = false,
           val isForChildren: Boolean = false) {
}

通过反编译,我们得到了一些类似这样的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public Book(@NotNull String name, boolean isChineseBook, boolean hasMultipleAuthor, boolean isPopular, boolean isForChildren) {
  Intrinsics.checkParameterIsNotNull(name, "name");
  super();
  this.name = name;
  this.isChineseBook = isChineseBook;
  this.hasMultipleAuthor = hasMultipleAuthor;
  this.isPopular = isPopular;
  this.isForChildren = isForChildren;
}

// $FF: synthetic method
public Book(String var1, boolean var2, boolean var3, boolean var4, boolean var5, int var6, DefaultConstructorMarker var7) {
  if((var6 & 2) != 0) {
     var2 = true;
  }

  if((var6 & 4) != 0) {
     var3 = false;
  }

  if((var6 & 8) != 0) {
     var4 = false;
  }

  if((var6 & 16) != 0) {
     var5 = false;
  }

  this(var1, var2, var3, var4, var5);
}

是不是有点不一样,它只生成了两个构造方法,而不是所谓的多个参数组合的构造方法。更有意思的是,当我们这样调用时

1
2
3
4
5
Book("Book0")
Book("Book2", true)
Book("Book3", true, true)
Book("Book4", true, true, true)
Book("Book5", true, true, true, true)

其对应的字节码反编译成java是

1
2
3
4
5
new Book("Book0", false, false, false, false, 30, (DefaultConstructorMarker)null);
new Book("Book2", true, false, false, false, 28, (DefaultConstructorMarker)null);
new Book("Book3", true, true, false, false, 24, (DefaultConstructorMarker)null);
new Book("Book4", true, true, true, false, 16, (DefaultConstructorMarker)null);
new Book("Book5", true, true, true, true);

我们会注意到上面有很多数字,比如30,14,28,24,16等。那么这些数字是怎么生成的呢?

对于构造方法的每个参数,

  • 都有一个位置,即方法声明时所在的位置,我们这里使用i代替表示。注意该从0开始,
  • 每个参数有一个mask值,该值为2的i次方,比如第0个位置的参数的mask值为1,第1个位置的mask值为2,以此类推。
  • 如果在调用时,编译器检测到某些参数没有调用,就将这些参数的mask值,求和,便生成了我们上面提到的数字。

具体示例如下

https://asset.droidyue.com/image/kotlin_jvm_overloads.png

比如Book(“Book0”)我们传递了第一个参数,所以最后的30 就是由 2 + 4 + 8 + 16 这些缺失的位置的mask值计算得出来的。

知道了,mask值的生成规则,就便于我们理解编译器生成的构造方法了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// $FF: synthetic method
public Book(String var1, boolean var2, boolean var3, boolean var4, boolean var5, int var6, DefaultConstructorMarker var7) {
  if((var6 & 2) != 0) {
     var2 = true;
  }

  if((var6 & 4) != 0) {
     var3 = false;
  }

  if((var6 & 8) != 0) {
     var4 = false;
  }

  if((var6 & 16) != 0) {
     var5 = false;
  }

  this(var1, var2, var3, var4, var5);
}

其实这个构造方法就是根据根据mask判断,某个位置的参数是否在调用时进行了赋值,如果没有赋值则进行设置默认值操作。

这种使用mask或者flag的方法其实很巧,减少了一些不必要的重载方法的生成。对于我们以后处理类似的问题,提供了一些不过的思路和参考价值。

]]>
0
<![CDATA[英剧Press第一季完结的终评:虎头蛇尾之作]]> http://www.udpwork.com/item/17143.html http://www.udpwork.com/item/17143.html#reviews Sun, 14 Oct 2018 13:40:44 +0800 魏武挥 http://www.udpwork.com/item/17143.html

我个人期待最大的剧属于英剧,英国人做电视剧,有一种说不出的黑幽默。

Press第一集是让我惊艳的,但到了本季最后一集,我怀疑我对第二季会兴趣缺缺。

如果打个分的话(满分100):

第一集,80分

第二集,70-75分

第三集:70-75分

第四集:90分——是,第四集相当好看

第五集:60分——我昨儿给了个差评,但好歹还算及格

第六集:无所谓了,反正不及格。

我稍许啰嗦一下第六集的剧本走向,以说明我为什么对这个剧有虎头蛇尾的评价。

至于什么慷慨陈词或者坚持理想,而让你热泪盈眶激动不已,抱歉,我是厮混江湖多年的大学老师,不是毫无社会阅历的大学生。

Press最大的看点其实是现实感。

媒体行业不是一朵白莲花,也不怎么存在什么就是为了理想啥都不顾的媒体人。可能刚出道的人还有,越资深越不会这样。

但在谐振这个新闻故事上——也是本季贯穿整季的故事——Press处理的非常差。

第一集展示出来的爆料人的犹豫纠结,是非常现实的。

但第六集他忽然就决定竹筒倒豆子了,跳跃性也未免太大。这里没有任何铺垫。

就算这个人的故事不重要,我们直接进入他竹筒倒豆子,先驱报的做法也不符合现实的媒体操作。

请注意本剧对先驱报的设定:非常谨慎的大报。他们为一个印度孩子控诉工厂欺压的故事,还要考究诸如记者是通过翻译才了解情况这样的细节(这很现实),在谐振的处理上,完全就丧失了其专业性做派。

这个重大的缺陷就是:信源单一。他们只有一个爆料人。

一个爆料人就敢做这样的文章,这是邮报的作风,不是先驱报的作风。

其实Press不是不可以处理这样的问题,它完全可以给诸多爆料人出示一些电脑里证据的镜头,强化除了人证还有物证,就可以把这个瑕疵给补上。但很遗憾,本剧没有。

这种偷工减料,再加上爆料人忽然没来由地要竹筒倒豆子,让我怀疑编剧大概急着要下班去接孩子。

再来看首相马修、大亨埃莫森以及邓肯是怎么去阻止先驱报报道的。

本集提供了一些反转式的噱头——是,我就是称之为噱头。

首相和大亨的目的是阻止报道,为了体现邓肯尚有那么一丝一毫的良知,他并没有阻止,他只是在先驱报报道后对霍莉展开了人身攻击。目的不能说完全达到,这里面的区别就算大亨不知道,首相不知道的么?但应首相所托去阻止报道的大亨,依然以“事成”的标准给了相当厚的封赏。

很难说服我这个观众。

伦敦的确有一个有“记者教堂”之称的圣布莱德教堂,因为地处舰队街(英国很多媒体在这里),所以和媒体人士颇有渊源,大亨埃莫森所隐射的默多克,第四次婚礼就在这个教堂。但这个教堂真正的称号是新娘教堂:St. Brides Church。

霍莉和邓肯在这个教堂的频繁见面,我不知道编剧想暗示什么。媒体行业的前辈圣人唤醒了邓肯的部分良知?这种胡诌的东西,不是我这种人能buy的。随便找个咖啡馆不就完了的事,非要翻出某个前辈的言辞——其实邓肯对这个人也不是很在乎——真的有些做作。

早在大亨韦斯特性侵案的故事中,本剧其实已经给到了一个非常简单非常有效的报道阻止方法。

但第六集非要去绕个大圈子,纯粹就是为了编故事。

让人看得非常捉急。

大亨韦斯特性侵的报道,被法官下了临时禁令拦下,导致先驱报当日破天荒的无报可发,冲突被推到非常刺激的点上。这个故事Press处理得蛮过瘾。虽然最后用和未成年人发生性关系来草草收场,但我可以理解电视剧这么干也有它的道理。

毕竟,me too的争议在现实中依然没有解决。

谐振能不能用类似的方法拦下呢?

其实按照本剧的设定,没啥不可以的。

首先就是首相这一方的权势,显然能调动的资源远超先驱报。

其次,按照本剧设定,对这样的报道,也是有人反对的。反对者不是别人,恰恰是被称为先驱报最有原则的调查记者詹姆斯。谐振本来就是他操作的,但是他认为报道不妥而退出了执笔。所以,拦下这样的报道,至少在本剧设定中,不是没有“legitimacy”。

最后,法院干预所谓媒体言论自由,这种事就算在现实中也是有的,比如,美国法院就给纽约时报下过禁令。这样的历史,后来在《华盛顿邮报》这个电影中有所反映。顺便说一句,这部电影很好看,它揭示了新闻业最核心的秘密,有心人是看得出来的。

本剧的男一号无疑是邓肯。

这个角色在整整一季中也被处理得比较草率。

邓肯是一个比较邪门的设定,喜欢剑走偏锋,为达目的不择手段。这都是编剧给他的人设。但为了让这个角色丰富起来,编剧另外给了他一个人设:他非常爱他的儿子,以至于他最终没拦下先驱报的报道,可能也是因为儿子。霍莉忽悠他说要给儿子留下一个好榜样。

这其实是一个很肤浅的所谓人设丰富化的操作技巧:善恶根本不在一条轨道上。用这种方式避免角色脸谱化,当然是可以的,但属于很低级的方法,常见于国内各种剧。

这样的主角,ta的善恶,应该混杂在ta职业行为上,这样对于我这样的观众,就会喜欢和讨厌兼而有之。

但第六集的处理方法,让你不得不选上一边:原来邓肯为了一己之私,那么邪恶啊。

这就很没劲了。

逼着观众站队,这是国内剧的套路。有失英剧水准。

本剧的男二号应该是黑人小哥艾德。

他的黑化(第五集)和白化(第六集),相当跳跃。

尤其是白化。

又逼着观众站队了:原来他是个好人啊。

嚓,没劲透了。

女一号是霍莉。

这个角色的设定是:具体操作手法上她偏邓肯,比如她认为花钱买料也没啥不可以的。底层理念她偏阿米娜,有理想主义气质。

但这位女一号在第六集对邓肯的企图报复(写黑稿)以及最终放弃报复(邓肯还是没报警),就是三个字:不专业——邓肯花钱买女人这是人的私事,不晓得涉及什么公共利益值得她去浓墨重彩做采访写报道的。而放弃报道无非就是邓肯卖了个人情没阻止他们报道谐振罢了。这是典型的私人恩怨,与专业无关。

这违背了霍莉最基础的设定:她是一个专业记者。

霍莉是故意被打造出有点高冷的,大多数时候甚至面无表情。她的专业范应该得到尊重和加强,结果被处理成一个小菜场大妈,然后还飘扬着理想主义式的白莲花气味。

这种分裂,实在是看不下去。

女二号?

这个剧没有女二号。无论是阿米娜还是蕾欧娜,最终都没完全展开。

本剧的片头开场是这样的:

纸笔、镜头、婚礼、杯子、键盘。键盘属于过渡性物品,前面几个都象征着某种传统。

接下来,纸笔烧了,婚礼的人偶倒了,杯子摔了,这都象征着传统的毁灭,当然,键盘也碎了。

片头最终呈现出两摞被烧毁的报纸,你可以读出其中的暗示。

但这个片头在六集中很遗憾的不是重点:数字新媒体是如何在摧毁传统媒体,以及传统媒体该如何自救。

第五集开始给了些许迹象:先驱报决定放弃报纸收费,并尝试建立收费墙。

流于表面之至。

我不是希望这个剧能给出媒体转型的教科书式的方法,我知道这是电视剧,不是课程讲解。

但Press这个剧,太过聚焦于报社内部,整个社会和大众,是怎样倒向数字,媒体或顺应或坚守的选择,这个剧刻画得非常不够。

照道理,这是能编出《黑镜》的英国人的强项啊。

BTW,这个剧对于商业公司如何和媒体打交道的勾勒也相当不够,除了詹姆斯报道的某工厂欺压童工我们看到了一个ceo的少许表现,商业公司PR的戏份几乎就没有了。这不是现实。

Press,真的开了个好头。

但我说不准我还会不会看第二季——如果英国人还打算继续拍的话。

但剧评,除非第二季有着惊人的故事走向

我想,到此为止了吧。

—— 首发 扯氮集 ——

本号不接受商业合作,实在死乞白赖想合作,五十万一篇好不?

作者执教于上海交通大学媒体与传播学院,天奇创投基金管理合伙人

英剧Press第一季完结的终评:虎头蛇尾之作最先出现在扯氮集

]]>

我个人期待最大的剧属于英剧,英国人做电视剧,有一种说不出的黑幽默。

Press第一集是让我惊艳的,但到了本季最后一集,我怀疑我对第二季会兴趣缺缺。

如果打个分的话(满分100):

第一集,80分

第二集,70-75分

第三集:70-75分

第四集:90分——是,第四集相当好看

第五集:60分——我昨儿给了个差评,但好歹还算及格

第六集:无所谓了,反正不及格。

我稍许啰嗦一下第六集的剧本走向,以说明我为什么对这个剧有虎头蛇尾的评价。

至于什么慷慨陈词或者坚持理想,而让你热泪盈眶激动不已,抱歉,我是厮混江湖多年的大学老师,不是毫无社会阅历的大学生。

Press最大的看点其实是现实感。

媒体行业不是一朵白莲花,也不怎么存在什么就是为了理想啥都不顾的媒体人。可能刚出道的人还有,越资深越不会这样。

但在谐振这个新闻故事上——也是本季贯穿整季的故事——Press处理的非常差。

第一集展示出来的爆料人的犹豫纠结,是非常现实的。

但第六集他忽然就决定竹筒倒豆子了,跳跃性也未免太大。这里没有任何铺垫。

就算这个人的故事不重要,我们直接进入他竹筒倒豆子,先驱报的做法也不符合现实的媒体操作。

请注意本剧对先驱报的设定:非常谨慎的大报。他们为一个印度孩子控诉工厂欺压的故事,还要考究诸如记者是通过翻译才了解情况这样的细节(这很现实),在谐振的处理上,完全就丧失了其专业性做派。

这个重大的缺陷就是:信源单一。他们只有一个爆料人。

一个爆料人就敢做这样的文章,这是邮报的作风,不是先驱报的作风。

其实Press不是不可以处理这样的问题,它完全可以给诸多爆料人出示一些电脑里证据的镜头,强化除了人证还有物证,就可以把这个瑕疵给补上。但很遗憾,本剧没有。

这种偷工减料,再加上爆料人忽然没来由地要竹筒倒豆子,让我怀疑编剧大概急着要下班去接孩子。

再来看首相马修、大亨埃莫森以及邓肯是怎么去阻止先驱报报道的。

本集提供了一些反转式的噱头——是,我就是称之为噱头。

首相和大亨的目的是阻止报道,为了体现邓肯尚有那么一丝一毫的良知,他并没有阻止,他只是在先驱报报道后对霍莉展开了人身攻击。目的不能说完全达到,这里面的区别就算大亨不知道,首相不知道的么?但应首相所托去阻止报道的大亨,依然以“事成”的标准给了相当厚的封赏。

很难说服我这个观众。

伦敦的确有一个有“记者教堂”之称的圣布莱德教堂,因为地处舰队街(英国很多媒体在这里),所以和媒体人士颇有渊源,大亨埃莫森所隐射的默多克,第四次婚礼就在这个教堂。但这个教堂真正的称号是新娘教堂:St. Brides Church。

霍莉和邓肯在这个教堂的频繁见面,我不知道编剧想暗示什么。媒体行业的前辈圣人唤醒了邓肯的部分良知?这种胡诌的东西,不是我这种人能buy的。随便找个咖啡馆不就完了的事,非要翻出某个前辈的言辞——其实邓肯对这个人也不是很在乎——真的有些做作。

早在大亨韦斯特性侵案的故事中,本剧其实已经给到了一个非常简单非常有效的报道阻止方法。

但第六集非要去绕个大圈子,纯粹就是为了编故事。

让人看得非常捉急。

大亨韦斯特性侵的报道,被法官下了临时禁令拦下,导致先驱报当日破天荒的无报可发,冲突被推到非常刺激的点上。这个故事Press处理得蛮过瘾。虽然最后用和未成年人发生性关系来草草收场,但我可以理解电视剧这么干也有它的道理。

毕竟,me too的争议在现实中依然没有解决。

谐振能不能用类似的方法拦下呢?

其实按照本剧的设定,没啥不可以的。

首先就是首相这一方的权势,显然能调动的资源远超先驱报。

其次,按照本剧设定,对这样的报道,也是有人反对的。反对者不是别人,恰恰是被称为先驱报最有原则的调查记者詹姆斯。谐振本来就是他操作的,但是他认为报道不妥而退出了执笔。所以,拦下这样的报道,至少在本剧设定中,不是没有“legitimacy”。

最后,法院干预所谓媒体言论自由,这种事就算在现实中也是有的,比如,美国法院就给纽约时报下过禁令。这样的历史,后来在《华盛顿邮报》这个电影中有所反映。顺便说一句,这部电影很好看,它揭示了新闻业最核心的秘密,有心人是看得出来的。

本剧的男一号无疑是邓肯。

这个角色在整整一季中也被处理得比较草率。

邓肯是一个比较邪门的设定,喜欢剑走偏锋,为达目的不择手段。这都是编剧给他的人设。但为了让这个角色丰富起来,编剧另外给了他一个人设:他非常爱他的儿子,以至于他最终没拦下先驱报的报道,可能也是因为儿子。霍莉忽悠他说要给儿子留下一个好榜样。

这其实是一个很肤浅的所谓人设丰富化的操作技巧:善恶根本不在一条轨道上。用这种方式避免角色脸谱化,当然是可以的,但属于很低级的方法,常见于国内各种剧。

这样的主角,ta的善恶,应该混杂在ta职业行为上,这样对于我这样的观众,就会喜欢和讨厌兼而有之。

但第六集的处理方法,让你不得不选上一边:原来邓肯为了一己之私,那么邪恶啊。

这就很没劲了。

逼着观众站队,这是国内剧的套路。有失英剧水准。

本剧的男二号应该是黑人小哥艾德。

他的黑化(第五集)和白化(第六集),相当跳跃。

尤其是白化。

又逼着观众站队了:原来他是个好人啊。

嚓,没劲透了。

女一号是霍莉。

这个角色的设定是:具体操作手法上她偏邓肯,比如她认为花钱买料也没啥不可以的。底层理念她偏阿米娜,有理想主义气质。

但这位女一号在第六集对邓肯的企图报复(写黑稿)以及最终放弃报复(邓肯还是没报警),就是三个字:不专业——邓肯花钱买女人这是人的私事,不晓得涉及什么公共利益值得她去浓墨重彩做采访写报道的。而放弃报道无非就是邓肯卖了个人情没阻止他们报道谐振罢了。这是典型的私人恩怨,与专业无关。

这违背了霍莉最基础的设定:她是一个专业记者。

霍莉是故意被打造出有点高冷的,大多数时候甚至面无表情。她的专业范应该得到尊重和加强,结果被处理成一个小菜场大妈,然后还飘扬着理想主义式的白莲花气味。

这种分裂,实在是看不下去。

女二号?

这个剧没有女二号。无论是阿米娜还是蕾欧娜,最终都没完全展开。

本剧的片头开场是这样的:

纸笔、镜头、婚礼、杯子、键盘。键盘属于过渡性物品,前面几个都象征着某种传统。

接下来,纸笔烧了,婚礼的人偶倒了,杯子摔了,这都象征着传统的毁灭,当然,键盘也碎了。

片头最终呈现出两摞被烧毁的报纸,你可以读出其中的暗示。

但这个片头在六集中很遗憾的不是重点:数字新媒体是如何在摧毁传统媒体,以及传统媒体该如何自救。

第五集开始给了些许迹象:先驱报决定放弃报纸收费,并尝试建立收费墙。

流于表面之至。

我不是希望这个剧能给出媒体转型的教科书式的方法,我知道这是电视剧,不是课程讲解。

但Press这个剧,太过聚焦于报社内部,整个社会和大众,是怎样倒向数字,媒体或顺应或坚守的选择,这个剧刻画得非常不够。

照道理,这是能编出《黑镜》的英国人的强项啊。

BTW,这个剧对于商业公司如何和媒体打交道的勾勒也相当不够,除了詹姆斯报道的某工厂欺压童工我们看到了一个ceo的少许表现,商业公司PR的戏份几乎就没有了。这不是现实。

Press,真的开了个好头。

但我说不准我还会不会看第二季——如果英国人还打算继续拍的话。

但剧评,除非第二季有着惊人的故事走向

我想,到此为止了吧。

—— 首发 扯氮集 ——

本号不接受商业合作,实在死乞白赖想合作,五十万一篇好不?

作者执教于上海交通大学媒体与传播学院,天奇创投基金管理合伙人

英剧Press第一季完结的终评:虎头蛇尾之作最先出现在扯氮集

]]>
0
<![CDATA[奥美之父 -《一个广告人的自白》]]> http://www.udpwork.com/item/17130.html http://www.udpwork.com/item/17130.html#reviews Sat, 13 Oct 2018 21:35:56 +0800 唐巧 http://www.udpwork.com/item/17130.html

最近读完了《一个广告人的自白》,作者是现代广告之父:大卫·奥格威,他创办了知名的广告营销公司:奥美。

这是一本有些年代的书,中文版出版于 1991 年,但是这些书常年热销,全球售卖了超过 100 万本。我手里这本中文版,是 2015 年的第 3 版,短短几年已经重印了 28 次。

作者在书中介绍了他对于经营广告公司、争取客户、创作高水平广告、写强有力文案的经验。最后他还站在一个功成名就者的角度,给年经人提供了一些功成名就的要诀。

下面说一些我的读书感受。

如何经营广告公司

奥格威在「如何经营广告公司」一章中,其实主要介绍的是如何招人,以及推崇什么样的工作文化。

如何招人

在招人上,奥格威喜欢招:

  • 聪明的人
  • 努力工作的人
  • 热爱当前工作的人
  • 专业技能扎实的人

这种招人规则和我们公司猿辅导如出一辙。那么如何找到热爱当前工作的人呢?我会看他花多少非工作时间在专业上。如果一个人喜欢技术,那么他在工作之余,也会去看开源代码,读技术书籍,甚至做技术分享,这就是他的热情的体现。

如何培养人

奥格威对奥美工作文化的建设,我感受到的是一种培养文化,培养文化主要体现在:

  • 奥格威欣赏那种愿意招聘比自己厉害、将来可以接替自己的优秀下属的人。
  • 奥格威欣赏愿意培养下属的人。因为这样公司才能形成内部提拔的机会。

这两点都很难做到,因为这两点都稍微有一些反人性,会让领导产生一定的「危机感」。招聘比自己厉害的人,培养自己的下属,都会使得自己的「不可替代性」变弱。如果一个人希望守住自己的职位,那么他可能潜意识里面就不会做这样的事情。

但是这一点对于领导的成长也至关重要,一个人能够领导好一个团队,并且培养出一个优秀的接班人,那么这个人才可以做更大的挑战。如果你希望不断跳出自己的舒适圈,不断成长进步,就应该做这样的事情,让自己有一天可以脱离现有的团队,做新的事情。否则团队离不开你,你也被绑定在团队中了。

猿辅导公司也一直按这样的企业文化做事情,不过我们的叫法不一样。我们的规则是:只招聘不低于团队平均水平的人。我们在招聘的时候会给候选人打分,如果是 3 分,就表示达到团队的平均水平。我们希望用团队的平均水平来让大家建立起招聘的高标准。因为只有新进来的人高于或等于团队平均水平,我们的技术团队才不至于越来越水。

猿辅导也一直有着强烈的 Mentor 制度,一个人从职场新人获得提升的第一步,就是让他尝试指导新人。如果一个老员工,无法指导新员工,那么他是很难获得更多的晋升机会了。因为即便他再厉害,他也只能单打独斗,不能将他的能力传播给别人。

如何沟通

奥格威还介绍了奥美的沟通文化,我简单总结起来,就是鼓励简单直接地表达和沟通。这和《原则》中的说的极度透明的工作原则类似。

互联网公司基本上也都具备这样的文化。例如大部分互联网公司员工的工位都完全一样,管理层的员工并不会拥有独立办公室。大家在沟通上都倾向直呼其名,也是希望减少职级对沟通的影响。腾讯公司的员工都有一个英文名,大家沟通就直接叫英文名,我听说这也是为了防止大家叫某某总,从而打破上下级隔阂

基于用户调研和 MVP 的工作方式

奥格威介绍了如何制作出优秀广告的经验,我从中看到的,都是互联网公司做产品的经验。比如:重视用户的调研和分析。广告的内容是基于对事实的理解和分析做出来的,而不是广告人在办公室靠灵光一现拍脑袋想出来的。

奥格威在书中介绍了帮美国旅游局做的旅游广告。他在做这个广告前,针对英国,法国人做了大量调研,发现大家不去美国是因为担心花费太高,进一步研究发现,其实英国的国民收入水平与美国的差距非常大。当时调研发现美国有半数家庭收入超过 5000 美元,而英国只有 3% 的家庭收入达到这个数。

于是广告方案就是:强调去美国旅游花费并不高。奥格威的广告词非常直接:强调每周只需 35 英磅即可在美国旅游。虽然广告被很多人批评,但是事实是,广告带来了 20% 以上的旅游业务增长。

除了调研外,奥格威也特别注重 MVP,因为广告投放出去花的都是真金白银,所以,效果得优化到极致。于是互联网公司里面常见的「A/B Test」,灰度投放在奥美是基本常识。所有最终使用的广告,都是在小渠道中测试通过,打败了别的各种方案的获胜者。

长远规划

我从奥格威的经营理念中,还看到不少长远规划。这些长远规则会牺牲奥美公司的短期收益,但是长期收益会很高。比如说:奥美会非常严格地挑选广告主:

  • 不认可的产品。不接。
  • 预计做不出比上一家更好的广告的广告主。不接。
  • 产品本身或者管理本身有问题的广告主。不接。

奥美会把好的客户经理放到现有客户上,而不是让他去扩展新客户。如果没有好的合格的客户经理,或者业务超过了奥美的承受能力,奥美就不接。

奥美还花了大量的钱做市场调研和分析,这使得他们的利润率低于平均水平。

这些决策都会影响奥美的收入和利润,但是长远来看,广告公司本身的品牌被树立。奥美之后的很多新增业务,都是由于之前的口碑获得的。

恶意收购

看完这本书后,我又查了一下奥美的历史,发现奥美在 1989 年被 WPP 公司强行恶意收购。我突然对恶意收购很感兴趣,想知道是如何操作的。于是在知乎上查到一些资料。

感兴趣的可以自行查看知乎的原贴:《有哪些经典的敌意收购 (Hostile takeover) 案例和手法?》

一些奥美的参考资料

]]>

最近读完了《一个广告人的自白》,作者是现代广告之父:大卫·奥格威,他创办了知名的广告营销公司:奥美。

这是一本有些年代的书,中文版出版于 1991 年,但是这些书常年热销,全球售卖了超过 100 万本。我手里这本中文版,是 2015 年的第 3 版,短短几年已经重印了 28 次。

作者在书中介绍了他对于经营广告公司、争取客户、创作高水平广告、写强有力文案的经验。最后他还站在一个功成名就者的角度,给年经人提供了一些功成名就的要诀。

下面说一些我的读书感受。

如何经营广告公司

奥格威在「如何经营广告公司」一章中,其实主要介绍的是如何招人,以及推崇什么样的工作文化。

如何招人

在招人上,奥格威喜欢招:

  • 聪明的人
  • 努力工作的人
  • 热爱当前工作的人
  • 专业技能扎实的人

这种招人规则和我们公司猿辅导如出一辙。那么如何找到热爱当前工作的人呢?我会看他花多少非工作时间在专业上。如果一个人喜欢技术,那么他在工作之余,也会去看开源代码,读技术书籍,甚至做技术分享,这就是他的热情的体现。

如何培养人

奥格威对奥美工作文化的建设,我感受到的是一种培养文化,培养文化主要体现在:

  • 奥格威欣赏那种愿意招聘比自己厉害、将来可以接替自己的优秀下属的人。
  • 奥格威欣赏愿意培养下属的人。因为这样公司才能形成内部提拔的机会。

这两点都很难做到,因为这两点都稍微有一些反人性,会让领导产生一定的「危机感」。招聘比自己厉害的人,培养自己的下属,都会使得自己的「不可替代性」变弱。如果一个人希望守住自己的职位,那么他可能潜意识里面就不会做这样的事情。

但是这一点对于领导的成长也至关重要,一个人能够领导好一个团队,并且培养出一个优秀的接班人,那么这个人才可以做更大的挑战。如果你希望不断跳出自己的舒适圈,不断成长进步,就应该做这样的事情,让自己有一天可以脱离现有的团队,做新的事情。否则团队离不开你,你也被绑定在团队中了。

猿辅导公司也一直按这样的企业文化做事情,不过我们的叫法不一样。我们的规则是:只招聘不低于团队平均水平的人。我们在招聘的时候会给候选人打分,如果是 3 分,就表示达到团队的平均水平。我们希望用团队的平均水平来让大家建立起招聘的高标准。因为只有新进来的人高于或等于团队平均水平,我们的技术团队才不至于越来越水。

猿辅导也一直有着强烈的 Mentor 制度,一个人从职场新人获得提升的第一步,就是让他尝试指导新人。如果一个老员工,无法指导新员工,那么他是很难获得更多的晋升机会了。因为即便他再厉害,他也只能单打独斗,不能将他的能力传播给别人。

如何沟通

奥格威还介绍了奥美的沟通文化,我简单总结起来,就是鼓励简单直接地表达和沟通。这和《原则》中的说的极度透明的工作原则类似。

互联网公司基本上也都具备这样的文化。例如大部分互联网公司员工的工位都完全一样,管理层的员工并不会拥有独立办公室。大家在沟通上都倾向直呼其名,也是希望减少职级对沟通的影响。腾讯公司的员工都有一个英文名,大家沟通就直接叫英文名,我听说这也是为了防止大家叫某某总,从而打破上下级隔阂

基于用户调研和 MVP 的工作方式

奥格威介绍了如何制作出优秀广告的经验,我从中看到的,都是互联网公司做产品的经验。比如:重视用户的调研和分析。广告的内容是基于对事实的理解和分析做出来的,而不是广告人在办公室靠灵光一现拍脑袋想出来的。

奥格威在书中介绍了帮美国旅游局做的旅游广告。他在做这个广告前,针对英国,法国人做了大量调研,发现大家不去美国是因为担心花费太高,进一步研究发现,其实英国的国民收入水平与美国的差距非常大。当时调研发现美国有半数家庭收入超过 5000 美元,而英国只有 3% 的家庭收入达到这个数。

于是广告方案就是:强调去美国旅游花费并不高。奥格威的广告词非常直接:强调每周只需 35 英磅即可在美国旅游。虽然广告被很多人批评,但是事实是,广告带来了 20% 以上的旅游业务增长。

除了调研外,奥格威也特别注重 MVP,因为广告投放出去花的都是真金白银,所以,效果得优化到极致。于是互联网公司里面常见的「A/B Test」,灰度投放在奥美是基本常识。所有最终使用的广告,都是在小渠道中测试通过,打败了别的各种方案的获胜者。

长远规划

我从奥格威的经营理念中,还看到不少长远规划。这些长远规则会牺牲奥美公司的短期收益,但是长期收益会很高。比如说:奥美会非常严格地挑选广告主:

  • 不认可的产品。不接。
  • 预计做不出比上一家更好的广告的广告主。不接。
  • 产品本身或者管理本身有问题的广告主。不接。

奥美会把好的客户经理放到现有客户上,而不是让他去扩展新客户。如果没有好的合格的客户经理,或者业务超过了奥美的承受能力,奥美就不接。

奥美还花了大量的钱做市场调研和分析,这使得他们的利润率低于平均水平。

这些决策都会影响奥美的收入和利润,但是长远来看,广告公司本身的品牌被树立。奥美之后的很多新增业务,都是由于之前的口碑获得的。

恶意收购

看完这本书后,我又查了一下奥美的历史,发现奥美在 1989 年被 WPP 公司强行恶意收购。我突然对恶意收购很感兴趣,想知道是如何操作的。于是在知乎上查到一些资料。

感兴趣的可以自行查看知乎的原贴:《有哪些经典的敌意收购 (Hostile takeover) 案例和手法?》

一些奥美的参考资料

]]>
0
<![CDATA[英剧PressS01E05: 两条奇怪的人际关系线]]> http://www.udpwork.com/item/17144.html http://www.udpwork.com/item/17144.html#reviews Sat, 13 Oct 2018 11:54:55 +0800 魏武挥 http://www.udpwork.com/item/17144.html

我必须承认的是,《新闻之争》的第五集我看得并不满意。我本来期望第六集也就是本季的最后一集能够有所挽回,所以一直拖到今天才开始我的第五集剧评,但第六集依然比较失望。

这个剧至少五和六都开始向某种正确迈进。

为了努力做到不太监,我有些意兴阑珊地写下这一集剧评。

第五集,主要是两条人际关系线:霍莉和邓肯,霍莉和艾德。

前者为主线,后者为副线。

霍莉最终离开了《邮报》,在我看来,有如下几个原因:

1、邓肯对叙利亚屠杀案——也是霍莉心心念念一路从前东家那里带来的新闻故事——看上去并不是太关心。如果说一开始还因为《邮报》有自己的危机要处理使得邓肯未能及时接见那位索要5000磅的小伙还可理解的话,最终邓肯更关心自己该不该给儿子打电话,大概是最后一根让霍莉极度失望的稻草。

2、邓肯接受了霍莉的建议,进行了一次“舆情翻转”来应对他们在一起校园欺凌报道所遭来的危机,但邓肯做得更为。。。残忍。。。一些?欺凌三个小孩的死者因为舆论的讨伐(这里的主力当然是《邮报》)选择了自杀,邓肯找来了他的母亲来参与反欺凌运动。

3、霍莉不能接受邓肯的一言堂,她认为所有人因为怕他而都顺着邓肯。而邓肯的霸道作风,很难说会让其实骨子里也蛮霸道的霍莉接受罢!

但说实话,这些原因在我看来,并不都那么站得住脚。

邓肯不是不关心叙利亚屠杀案,但从重要度来看,排在后面是很正常的。在危机出现之前,邓肯更为关心“谐振”究竟是什么,这是英国政府的一个项目,关心这个比关心什么叙利亚屠杀更多显然没什么错。

危机出现后,事关《邮报》自身,当然危机如何处理具备第一优先级。

而危机处理方法出现,叙利亚小伙其实已经离去,但这个时候邓肯关心自己是不是要电话给自己儿子,也一点不奇怪。霍莉不了解邓肯在自家私事的纠结可以理解,但由此而推导出邓肯不重视她的工作,显然并不理性。

校园欺凌案。

这最终导致那位女实习生对艾德的不屑:因为艾德在早先报道中称这位叫丹尼里昂斯的十七岁小男生为“monster”(恶魔)。

我倒是觉得这位女实习生太过白莲花。一个欺凌他人直接导致三个孩童住院的人,称其一声“monster”可能有点过,但不算太过。

严格说来,是这样一个逻辑:丹尼死于千夫所指,《邮报》只是其中一“夫”,虽然是一马当先的那个夫。

事实上,先驱报也报道了这个新闻,可能并没有使用“monster”这个词眼,但未见得twitter上没人会因为报纸不用“monster”而不叫骂。

当这个丹尼选择自杀后,邓肯打算死扛,把丹尼做成活该,霍莉并不认同。她提供了“舆情翻转”这个操作技巧。

这只是技巧的高下之分,邓肯的选择显然不是好选择。但霍莉也没见得认为《邮报》应该道歉。尽管一开始在丹尼还没自杀的时候霍莉觉得这个新闻不宜跟进,但报社危机已现,想不跟进都不可能了。

霍莉和邓肯的分歧只是要不要拉丹尼的母亲出面支持《邮报》的反欺凌运动。

是的,我承认让丹尼母亲参与到《邮报》的运动,是蛮虚伪的。但从公关技巧来看,邓肯是更为高明的。的确他的做法,更有说服力,也更容易完成霍莉根本性的目标:不要让《邮报》成为焦点。

虽然这一集让我不以为然的地方很多,但要说到能不能在实操上去借鉴电视剧的故事编造,这个欺凌案的危机处理,是有些很现实的东西可学的。

邓肯的霸道的确让手下阳奉阴违,这是这一集两幕有趣的对比。

但邓肯并不是霸道到手下都不敢提反对意见。事实上,在欺凌案该如何处理的问题上,《邮报》的两位高级职员都表示支持霍莉。虽然邓肯拒绝了他们的看法,但这至少说明,在一些大事上,《邮报》不是万马齐喑的。

邓肯提出了一个馊主意,但邓肯的坚持是因为虽然有人说no但并没有拿出具体方案来。等到霍莉告诉他有个更棒的方法,他立刻就接受并举一反三。

而邓肯计较霍莉到底应该不应该参加编务会这一段,我觉得非常匪夷所思。你可以视为邓肯给霍莉一个下马威让她知道不再是副新闻主编了,也可以视为邓肯知道霍莉是个刺头不想让编务会上出现什么不和谐的声音,但这都未免太小看了邓肯。他不是这样的人。

后面几次开会,霍莉站在那里,邓肯一句话都没说。

艾德进一步黑化。

我倒是觉得他吃醋了。

因为他一开始认为邓肯之所以要招募霍莉是想睡霍莉。

这事因为他和霍莉睡过引起了艾德非常复杂纠结的心情——其实他在泡那位女实习生。

艾德在这一集中表现出了邪恶的气质,很突兀,因为他这种醋意不应该有这么强。

艾德在下一集中的白化也很突兀。

这个黑人小哥在这两集中处理得相当差。

在邮报发生了什么花了四十分钟后,本集最后花了二十分钟的时间讲了先驱报发生了什么。

先驱报决定做免费报纸。

在这里我们倒是知道了先驱报一份的价格:2磅。

在下一集,我们会知道邮报多少钱:50便士。

先驱报发行量低于邮报是很正常的,何必计较这个。价格摆在那里。如果先驱报的发行量只是略低于邮报,反倒说明先驱报其实很成功。

先驱报放弃了报纸收费,转向网上做部分报道的收费墙。至少收费墙是今天现实中一些大报的选择。

霍莉要报复邓肯,写邓肯的黑稿。

非常不职业。

她向先驱报同仁透露邓肯有一个保险箱藏了很多黑料以供他敲诈勒索。

但事实上,邓肯什么时候和她吐露过一个字以示他想“勒索”?

他只是在意识到一个明星已死所以性爱录像就没有什么太大噱头的价值直接将物料扔进了垃圾桶。

仅此而已。

第五集,差评

—— 首发 扯氮集 ——

本号不接受商业合作,实在死乞白赖想合作,五十万一篇好不?

作者执教于上海交通大学媒体与传播学院,天奇创投基金管理合伙人

英剧PressS01E05: 两条奇怪的人际关系线最先出现在扯氮集

]]>

我必须承认的是,《新闻之争》的第五集我看得并不满意。我本来期望第六集也就是本季的最后一集能够有所挽回,所以一直拖到今天才开始我的第五集剧评,但第六集依然比较失望。

这个剧至少五和六都开始向某种正确迈进。

为了努力做到不太监,我有些意兴阑珊地写下这一集剧评。

第五集,主要是两条人际关系线:霍莉和邓肯,霍莉和艾德。

前者为主线,后者为副线。

霍莉最终离开了《邮报》,在我看来,有如下几个原因:

1、邓肯对叙利亚屠杀案——也是霍莉心心念念一路从前东家那里带来的新闻故事——看上去并不是太关心。如果说一开始还因为《邮报》有自己的危机要处理使得邓肯未能及时接见那位索要5000磅的小伙还可理解的话,最终邓肯更关心自己该不该给儿子打电话,大概是最后一根让霍莉极度失望的稻草。

2、邓肯接受了霍莉的建议,进行了一次“舆情翻转”来应对他们在一起校园欺凌报道所遭来的危机,但邓肯做得更为。。。残忍。。。一些?欺凌三个小孩的死者因为舆论的讨伐(这里的主力当然是《邮报》)选择了自杀,邓肯找来了他的母亲来参与反欺凌运动。

3、霍莉不能接受邓肯的一言堂,她认为所有人因为怕他而都顺着邓肯。而邓肯的霸道作风,很难说会让其实骨子里也蛮霸道的霍莉接受罢!

但说实话,这些原因在我看来,并不都那么站得住脚。

邓肯不是不关心叙利亚屠杀案,但从重要度来看,排在后面是很正常的。在危机出现之前,邓肯更为关心“谐振”究竟是什么,这是英国政府的一个项目,关心这个比关心什么叙利亚屠杀更多显然没什么错。

危机出现后,事关《邮报》自身,当然危机如何处理具备第一优先级。

而危机处理方法出现,叙利亚小伙其实已经离去,但这个时候邓肯关心自己是不是要电话给自己儿子,也一点不奇怪。霍莉不了解邓肯在自家私事的纠结可以理解,但由此而推导出邓肯不重视她的工作,显然并不理性。

校园欺凌案。

这最终导致那位女实习生对艾德的不屑:因为艾德在早先报道中称这位叫丹尼里昂斯的十七岁小男生为“monster”(恶魔)。

我倒是觉得这位女实习生太过白莲花。一个欺凌他人直接导致三个孩童住院的人,称其一声“monster”可能有点过,但不算太过。

严格说来,是这样一个逻辑:丹尼死于千夫所指,《邮报》只是其中一“夫”,虽然是一马当先的那个夫。

事实上,先驱报也报道了这个新闻,可能并没有使用“monster”这个词眼,但未见得twitter上没人会因为报纸不用“monster”而不叫骂。

当这个丹尼选择自杀后,邓肯打算死扛,把丹尼做成活该,霍莉并不认同。她提供了“舆情翻转”这个操作技巧。

这只是技巧的高下之分,邓肯的选择显然不是好选择。但霍莉也没见得认为《邮报》应该道歉。尽管一开始在丹尼还没自杀的时候霍莉觉得这个新闻不宜跟进,但报社危机已现,想不跟进都不可能了。

霍莉和邓肯的分歧只是要不要拉丹尼的母亲出面支持《邮报》的反欺凌运动。

是的,我承认让丹尼母亲参与到《邮报》的运动,是蛮虚伪的。但从公关技巧来看,邓肯是更为高明的。的确他的做法,更有说服力,也更容易完成霍莉根本性的目标:不要让《邮报》成为焦点。

虽然这一集让我不以为然的地方很多,但要说到能不能在实操上去借鉴电视剧的故事编造,这个欺凌案的危机处理,是有些很现实的东西可学的。

邓肯的霸道的确让手下阳奉阴违,这是这一集两幕有趣的对比。

但邓肯并不是霸道到手下都不敢提反对意见。事实上,在欺凌案该如何处理的问题上,《邮报》的两位高级职员都表示支持霍莉。虽然邓肯拒绝了他们的看法,但这至少说明,在一些大事上,《邮报》不是万马齐喑的。

邓肯提出了一个馊主意,但邓肯的坚持是因为虽然有人说no但并没有拿出具体方案来。等到霍莉告诉他有个更棒的方法,他立刻就接受并举一反三。

而邓肯计较霍莉到底应该不应该参加编务会这一段,我觉得非常匪夷所思。你可以视为邓肯给霍莉一个下马威让她知道不再是副新闻主编了,也可以视为邓肯知道霍莉是个刺头不想让编务会上出现什么不和谐的声音,但这都未免太小看了邓肯。他不是这样的人。

后面几次开会,霍莉站在那里,邓肯一句话都没说。

艾德进一步黑化。

我倒是觉得他吃醋了。

因为他一开始认为邓肯之所以要招募霍莉是想睡霍莉。

这事因为他和霍莉睡过引起了艾德非常复杂纠结的心情——其实他在泡那位女实习生。

艾德在这一集中表现出了邪恶的气质,很突兀,因为他这种醋意不应该有这么强。

艾德在下一集中的白化也很突兀。

这个黑人小哥在这两集中处理得相当差。

在邮报发生了什么花了四十分钟后,本集最后花了二十分钟的时间讲了先驱报发生了什么。

先驱报决定做免费报纸。

在这里我们倒是知道了先驱报一份的价格:2磅。

在下一集,我们会知道邮报多少钱:50便士。

先驱报发行量低于邮报是很正常的,何必计较这个。价格摆在那里。如果先驱报的发行量只是略低于邮报,反倒说明先驱报其实很成功。

先驱报放弃了报纸收费,转向网上做部分报道的收费墙。至少收费墙是今天现实中一些大报的选择。

霍莉要报复邓肯,写邓肯的黑稿。

非常不职业。

她向先驱报同仁透露邓肯有一个保险箱藏了很多黑料以供他敲诈勒索。

但事实上,邓肯什么时候和她吐露过一个字以示他想“勒索”?

他只是在意识到一个明星已死所以性爱录像就没有什么太大噱头的价值直接将物料扔进了垃圾桶。

仅此而已。

第五集,差评

—— 首发 扯氮集 ——

本号不接受商业合作,实在死乞白赖想合作,五十万一篇好不?

作者执教于上海交通大学媒体与传播学院,天奇创投基金管理合伙人

英剧PressS01E05: 两条奇怪的人际关系线最先出现在扯氮集

]]>
0
<![CDATA[每周分享第 26 期]]> http://www.udpwork.com/item/17129.html http://www.udpwork.com/item/17129.html#reviews Fri, 12 Oct 2018 12:23:24 +0800 阮一峰 http://www.udpwork.com/item/17129.html 这里记录过去一周,我看到的值得分享的东西,每周五发布。

Basecamp是 IT 行业很有名的一家公司,提供团队协作工具,同时也是 Rails on Ruby 框架的创造者。这家公司的特别之处在于,它不仅写软件,还写畅销书!

它的两位老板喜欢写书,已经出版了三本----《Rework》、《Getting Real》和《Remote》----每一本都卖得很好。最近,他们又出版了第四本《工作何必疯狂》(It Doesn't Have to Be Crazy at Work,见上图)。

这本新书的主要观点是,IT 行业的员工加班(或者说投入工作的程度),已经超过了合理标准,接近于疯狂,应该得到遏制。很多软件工程师每周的工作时间已经达到了60小时,甚至70、80小时。除了工作和睡眠,生活几乎没有其他内容。作者认为,这是错误的。

加班真的是 IT 行业的日常。国内有过一个《2016 年 IT 公司加班时间排行榜》,排名前三位的分别是华为、腾讯和阿里,平均每天的加班时间分别为3.96小时、3.92小时和3.89小时。

是不是一定要那么多加班?这本书说,也不是。加班多的一个因素是,公司没有好好珍惜员工的时间,打断工作的事情特别多,最典型的举措就是开放式办公室,使得人们互相干扰。员工因此不得不加班,因为在正常时间内无法完成工作。作者认为,公司应该创造条件,让员工全身心投入工作,不要拖到夜深人静时才能没有打扰。

许多公司似乎都很擅长浪费:浪费时间、注意力、金钱、精力。每周60、70、80的工作小时中,有多少是真正用于工作本身的?又有多少时间是在会议中浪费掉的,或被各种琐事打断的?大部分吧。

答案不是更多的时间投入,而是减少浪费,减少员工的注意力分散,减少那些引起焦虑和压力的事情。

同时,这本书也指出,IT 公司的工作任务也确实过多:无休止的需求、不断的营销活动、精确到小时的排期,都给工程师带来了焦虑和压力。普通的行业,用户一年增长一倍,绝对是值得庆祝的,但是 IT 行业不行。这个行业渴望的是一年增长十倍,甚至百倍,要的是从0变成独角兽的那种速度。

作者说,我们要一定要这样吗?IT 行业对高速增长有一种不健康的痴迷。正是这种痴迷,使得人们产生不切实际的期望,导致从业者精疲力尽。

这本书的主张是,软件行业也可以是一个平静的行业,而不一定像现在这样,是一个加速再加速的行业。平静才是长期可持续发展的关键。(注意,这里不是指公司,公司只要不断雇佣新人,就可以让工作永远加速运行。但是员工不行,人的生理和心理都不是可以长期透支的。)

过去的18年里,我们一直致力于让 Basecamp 成为一家平静的公司。我们不提出不可能的承诺,不追求高额的营业额,不设置最后期限,不人为制造繁忙的工作和系统性的焦虑。有些项目似乎永远不会结束,我们觉得也 OK。

新闻

1、氢气列车

德国开通全世界第一列氢气列车,运行在一条100公里的线路上。氢气列车通过氢气和氧气的化学反应产生能量,转化为电力,唯一的排放物就是水。单个氢气罐可以支持列车行驶1,000公里。

列车由法国的阿尔斯通公司制造。阿尔斯通表示,计划到2021年向德国再提供14辆氢气列车,而英国,荷兰,丹麦,挪威,意大利和加拿大等国也有兴趣。法国政府已经表示,要在2022年之前将第一批氢气列车投入使用。

氢气的最大问题,就是很难大量运输。这就是为什么氢气汽车没法推广,因为氢气加油站的网络建设不了。但是,火车就没有这个问题,每个城市的火车站都可以建氢气供应站。

2、日本的小行星探测器

9月21日,日本的隼鸟二号飞船(Hayabusa-2)到达了一个名叫 Ryugu 的小行星,释放了两辆探测车成功登陆。这是人类探测器第一次登陆小行星。

Ryugu 小行星其实是一块一公里长的太空岩石。隼鸟二号是2014年12月3日发射的,足足飞了近四年,才达到那里。由于小行星接近于没有重力,探测车无法行驶,而是跳跃式前进,向前动一下就会跳起来。探测车已经发回了第一批照片。

探测车的下一步的任务是,10月下旬引爆一些炸药,收集炸出来的岩石。(我的疑问:太空没有空气,为什么能产生爆炸?)这些岩石没有暴露在太空环境,所以是原始的。一年后的2019年12月,隼鸟二号飞船将返程,将小行星的岩石样本送回地球。

3、最古老的动物化石

科学家最近在俄罗斯偏远地区的一处悬崖,发现了一块古老的海洋生物化石。经过鉴定,该化石距今大约5.58亿年,是已知最古老的动物化石。下图是发现化石的现场。

4、IPv6 的普及状况

IPv6 诞生至今已经25年了,依然没有得过普及。世界前1000位的网站,只有304个启用了 IPv6。前100万位的网站,总支持率是18.6%。

一个网站启用 IPv6 有两个条件:一是网站本身支持 IPv6(域名有 AAAA 记录),二是网站的 DNS 主机支持IPv6。下面是排名最靠前的不支持 IPv6 的网站。

5、机器人皮肤

耶鲁大学的科学家发明了一种可卷曲的"机器人皮肤",可以包裹在任何物体的外层,使得该物体变成可以控制的机器人。上图是毛绒填充的小马玩具穿上"机器人皮肤"以后,变成可以走动了。

科学家已经实现了,给气球穿上"机器人皮肤",使得气球变成可以编程控制。

6、无人机查税

希腊的圣托里尼岛是世界著名的旅游景点,每年有大量的游客参加一日游项目。但是,很多导游公司为了逃税,隐瞒游客数量。

当地政府开始启用无人机,观察游客数量,以及出海的游船,用来评估当地业者应该交税的数量。据称,已经查到9艘没有开票的游船,涉及收入总计约25,000欧元,游船主人现在面临罚款。

7、南大西洋的海底电缆

最近,安哥拉到巴西的海底电缆铺设完成,这是历史上第一条南大西洋的海底电缆。南部非洲和南美洲以前从来没有直连的海底电缆。

这条电缆也是南美洲第一条不经过美国的国际互联网线路。2020年,巴西直达欧洲(葡萄牙)的海底电缆也将完成。

8、AI 艺术品

这个月,佳士得将首次拍卖人工智能艺术品。一幅算法生成的人物肖像,估价达到了1万美元。

事实上,已经有很多绘画机器人,通过大量画作的训练,会自动生成美术作品。下面六幅抽象画,有五幅是人的作品,还有一幅软件生成的,你能看出来是哪一幅吗?

9、一句话新闻

  • 美国科学家发现,人体在禁食期间会产生一种分子,防止血管系统的衰老。也就是说,24小时不吃东西,有助于延长寿命和防止衰老。
  • Java 11最近发布了。但是,你不要用 Oracle 的官方 JDK 开发,因为那个 JDK 升级和用于生产环境是要钱的。你应该使用 OpenJDK,Redhat公开承诺将持续维护 OpenJDK。
  • Facebook宣布,新建的新加坡数据中心,将百分百采用可再生能源,主要由安装在近900个屋顶上的太阳能电池板供电。该项目预计将在2020年完成。
  • Firefox推出 VR 设备专用的网络浏览器。

教程

1、异常处理是错误的设计(英文)

许多主流语言都采用抛出异常的方式处理错误,这篇文章认为,这种设计是错误的,传统的返回值是更好的方式。抛出异常的主要好处是,可以将业务代码与错误处理代码分开,缺点是它改变了控制流,有点像 goto 语言,可以随意跳转。

2、如何撰写技术文档(英文)

技术文档(documents)分成四种:教程(tutorial)、指导(guide)、解释(explanation)和参考(reference)。本文解释了每一种文档的特点,并给出了写作建议。

3、阿里巴巴的18位创始人(英文)

1999年,阿里巴巴集团成立,当时共有18位创始人,大部分是马云的同事、朋友和学生。这篇文章汇总了这18个人的公开资料。

4、使用 Qt 为 Python 脚本添加图形界面(英文)

Qt 是一个 C++ 写的跨平台控件库,PyQt 将其移植到 Python,可以很方便地为 Python 脚本添加图形界面。

5、微服务入门示例(英文)

本文解释了什么是微服务架构,并且给出了一个简单的示例,在 Docker 里面使用 Flask 框架和 ZeroMQ 搭建一个简单的微服务应用。

6、为什么使用 i3 窗口管理器?(英文)

Linux 发行版一般都自带桌面环境,如果你需要的只是一个好用的命令行,那么你可以放弃桌面环境,改用窗口管理器。i3 就是一个很好用的平铺式窗口管理器,本文给出了五个推荐理由。如果你有兴趣的话,这里还有一篇简单教程

7、Pi-hole 拦截广告(英文)

如果你还在用广告拦截器,Pi-hole 是更好的选择。它是树莓派上的 DNS 服务器,架设在局域网里面,内置黑名单,拦截广告域名的 DNS 请求。从此,局域网所有设备都看不到广告。

8、如何制作命令行动画?(英文)

本文介绍如何使用 ncurses 这个 C 语言标准库,写一个简单的命令行动画。

9、如何将 Web 应用做成 Docker?(英文)

本文通过一个很简单的单页应用,演示如何将 Web 应用放到 Docker 里面,然后通过 Docker 运行。

资源

1、Youtube 的教育视频

如果你不知道 Youtube 上可以学什么,就来看看这个帖子。当然,所有资源都是英语的。

2、计算机组织导论:以树莓派和 ARM 汇编语言为例

开源教材,介绍计算机的底层实现,写得比较好懂。

3、从零开始写一个操作系统

一个教程库,每一步都有指导,教你怎么写出一个简单的操作系统。

4、创业公司

一个网页游戏,玩家是创业公司的创始人,模拟经营这家公司,完成各种任务。

5、各种算法的 Python 实现

这个仓库收集 Python 语言实现的各种算法代码。

工具

1、ReactXP

微软基于 React Native 开发的框架,沿用了 React Native 的 API,可以用同一套代码编译出 Web、手机 和 Windows 应用。

2、Bing 的图像搜索

与谷歌一样,Bing 也提供图像搜索。但是,它还带有裁剪功能,可以只搜索图像的一部分。

3、富文本编辑器 Trix

Rails 框架的创造者 Basecamp 公司,开发了一个富文本编辑器 Trix,发布1.0版了。

4、Cephes

Cephes 是 JavaScript (准确说是 WebAssembly)的数学函数库,类似 Python 的 SciPy。

5、qutebrowser

一个 Qt 库制作的最简化浏览器,内核是 Chromium。最大特点就是它自带命令行,可以完全用键盘操作。

6、DNS 1.1.1.1

该网站提供指导,如何将各种操作系统或设备的 DNS 设为 1.1.1.1。

7、sqlfmt

SQL 语句格式化的在线工具。

8、Caddy

Caddy 是一个支持 HTTP/2,自带 HTTPS 功能的跨平台、易于使用的 Web 服务器。

9、camelot

从 PDF 文件(非扫描)里面提取表格的 Python 库。

文摘

1、Chrome 的恐龙游戏

Chrome 浏览器由于网络中断、无法上网时,会显示一个恐龙游戏。一头霸王龙困在沙漠之中,必须不停地奔跑,且必须跳跃避开像仙人掌和翼手龙这样的障碍物,随着游戏的进展,速度会越来越快。

该游戏于2014年9月首次发布。地址栏键入 chrome://dino 就可以进入该游戏。开始游戏的方法是:PC 用户按一下空格键,手机用户点击恐龙。

游戏的设计师塞巴斯蒂安·加布里尔(Sebastien Gabriel)说,断网就好比回到互联网之前的时代,因此我们设想出这个回归"史前时代"的游戏,那时的计算机用户好比恐龙,无网可上就像困在沙漠中。恐龙的形象采用像素艺术的风格,恐龙的动作故意设计得很僵化。

只要不触碰障碍,用户可以一直游戏下去。游戏内部设置的最长游戏时间是1700万年,这大约是霸王龙在地球上存活的时间。

2、湾区就是新的矿场

19世纪时,美国社会有一个流行的现象。许多年轻人离乡背井,为了得到高薪,去遥远的西部矿场挖矿。在那里工作几年,就可以赚取足够的钱,回家娶一个老婆。矿场的生活是非常艰苦的,条件很差,这也是矿主支付高薪的原因,否则没人愿意来。

20世纪时,这种现象变成了石油行业。石油钻井平台通常都在偏远地区,有的还是在沙漠和海洋里。石油工人可以得到高工资,代价是恶劣的生活条件,以及远离家庭的孤独。

现在,这种现象变成了 IT 行业。年轻人为了高薪,来到湾区当程序员,同样过着艰苦的生活。我听说,旧金山的一间卧室里面,居然住了好几个大学毕业生,因为旧金山双居室公寓的租金是5500美元/月。有些谷歌的雇员也是合住,而且地理位置也不好,离交通干线很远。

很多人把湾区看成新时代的矿场。IT 行业的工资比其他行业高,那些外来的程序员,计划是省钱,忍受一段时间的恶劣生活,然后在30多岁退休,再去低成本城市工作并在那里开始家庭生活。

本周图片

1、南极洲的冰川裂缝

上图是充满裂缝的冰川。由于气温上升,南极洲的冰川开始崩解,变成小块落入海中,然后融化。

2、海市蜃楼

下面是2018年8月,有人在加拿大拍到的海市蜃楼照片。船的形状一直在改变,整个过程持续了半个小时,然后就消失不见了。

3、珍道具

珍道具(Chindōgu)是一些富有创意,但并不实用的发明。它是今年71岁的 Kenji Kawakami 在上个世纪90年代发起的。当时,他负责编制一本针对日本家庭主妇的邮购商品目录。有时候,商品目录看起来很薄,他就会加入一些自己制作的产品,有点像恶作剧。

遮雨的鞋子。

唇膏状的黄油棒。

带有扫帚功能的拖鞋。

感冒患者专用的面纸架。

喷水的牙刷。

新奇

1、便携打字机

美国一家公司推出一台便携式设备,只能用于打字,这是为了防止使用者分心。它包括一个全尺寸键盘和一个电子墨水屏,可以打开多个文档,系统会自动保存并同步到Dropbox等服务。该设备的众筹价是279美元。

本周金句

1、

七年前,风险投资家 Marc Andreessen 写了一篇文章《软件正在吞噬世界》。他提出,软件公司将会赢得世界。

现在我们知道,他是对的。但是,他没有提到,这个社会的大部分人不拥有软件公司,而是生活在被软件吃掉的世界里。

--John Battelle《被软件吃掉的世界》

2、

我有时觉得,硅谷是一个游戏,每个玩家有10年时间。在这10年里面,如果你发财了,就赢得了游戏,否则你就出局,必须离开硅谷。

--Florent Crivello

欢迎订阅

这个专栏每周五发布,同步更新在我的个人网站微信公众号语雀

微信搜索"阮一峰的网络日志 "或者扫描二维码,即可订阅。

(完)

文档信息

]]>
这里记录过去一周,我看到的值得分享的东西,每周五发布。

Basecamp是 IT 行业很有名的一家公司,提供团队协作工具,同时也是 Rails on Ruby 框架的创造者。这家公司的特别之处在于,它不仅写软件,还写畅销书!

它的两位老板喜欢写书,已经出版了三本----《Rework》、《Getting Real》和《Remote》----每一本都卖得很好。最近,他们又出版了第四本《工作何必疯狂》(It Doesn't Have to Be Crazy at Work,见上图)。

这本新书的主要观点是,IT 行业的员工加班(或者说投入工作的程度),已经超过了合理标准,接近于疯狂,应该得到遏制。很多软件工程师每周的工作时间已经达到了60小时,甚至70、80小时。除了工作和睡眠,生活几乎没有其他内容。作者认为,这是错误的。

加班真的是 IT 行业的日常。国内有过一个《2016 年 IT 公司加班时间排行榜》,排名前三位的分别是华为、腾讯和阿里,平均每天的加班时间分别为3.96小时、3.92小时和3.89小时。

是不是一定要那么多加班?这本书说,也不是。加班多的一个因素是,公司没有好好珍惜员工的时间,打断工作的事情特别多,最典型的举措就是开放式办公室,使得人们互相干扰。员工因此不得不加班,因为在正常时间内无法完成工作。作者认为,公司应该创造条件,让员工全身心投入工作,不要拖到夜深人静时才能没有打扰。

许多公司似乎都很擅长浪费:浪费时间、注意力、金钱、精力。每周60、70、80的工作小时中,有多少是真正用于工作本身的?又有多少时间是在会议中浪费掉的,或被各种琐事打断的?大部分吧。

答案不是更多的时间投入,而是减少浪费,减少员工的注意力分散,减少那些引起焦虑和压力的事情。

同时,这本书也指出,IT 公司的工作任务也确实过多:无休止的需求、不断的营销活动、精确到小时的排期,都给工程师带来了焦虑和压力。普通的行业,用户一年增长一倍,绝对是值得庆祝的,但是 IT 行业不行。这个行业渴望的是一年增长十倍,甚至百倍,要的是从0变成独角兽的那种速度。

作者说,我们要一定要这样吗?IT 行业对高速增长有一种不健康的痴迷。正是这种痴迷,使得人们产生不切实际的期望,导致从业者精疲力尽。

这本书的主张是,软件行业也可以是一个平静的行业,而不一定像现在这样,是一个加速再加速的行业。平静才是长期可持续发展的关键。(注意,这里不是指公司,公司只要不断雇佣新人,就可以让工作永远加速运行。但是员工不行,人的生理和心理都不是可以长期透支的。)

过去的18年里,我们一直致力于让 Basecamp 成为一家平静的公司。我们不提出不可能的承诺,不追求高额的营业额,不设置最后期限,不人为制造繁忙的工作和系统性的焦虑。有些项目似乎永远不会结束,我们觉得也 OK。

新闻

1、氢气列车

德国开通全世界第一列氢气列车,运行在一条100公里的线路上。氢气列车通过氢气和氧气的化学反应产生能量,转化为电力,唯一的排放物就是水。单个氢气罐可以支持列车行驶1,000公里。

列车由法国的阿尔斯通公司制造。阿尔斯通表示,计划到2021年向德国再提供14辆氢气列车,而英国,荷兰,丹麦,挪威,意大利和加拿大等国也有兴趣。法国政府已经表示,要在2022年之前将第一批氢气列车投入使用。

氢气的最大问题,就是很难大量运输。这就是为什么氢气汽车没法推广,因为氢气加油站的网络建设不了。但是,火车就没有这个问题,每个城市的火车站都可以建氢气供应站。

2、日本的小行星探测器

9月21日,日本的隼鸟二号飞船(Hayabusa-2)到达了一个名叫 Ryugu 的小行星,释放了两辆探测车成功登陆。这是人类探测器第一次登陆小行星。

Ryugu 小行星其实是一块一公里长的太空岩石。隼鸟二号是2014年12月3日发射的,足足飞了近四年,才达到那里。由于小行星接近于没有重力,探测车无法行驶,而是跳跃式前进,向前动一下就会跳起来。探测车已经发回了第一批照片。

探测车的下一步的任务是,10月下旬引爆一些炸药,收集炸出来的岩石。(我的疑问:太空没有空气,为什么能产生爆炸?)这些岩石没有暴露在太空环境,所以是原始的。一年后的2019年12月,隼鸟二号飞船将返程,将小行星的岩石样本送回地球。

3、最古老的动物化石

科学家最近在俄罗斯偏远地区的一处悬崖,发现了一块古老的海洋生物化石。经过鉴定,该化石距今大约5.58亿年,是已知最古老的动物化石。下图是发现化石的现场。

4、IPv6 的普及状况

IPv6 诞生至今已经25年了,依然没有得过普及。世界前1000位的网站,只有304个启用了 IPv6。前100万位的网站,总支持率是18.6%。

一个网站启用 IPv6 有两个条件:一是网站本身支持 IPv6(域名有 AAAA 记录),二是网站的 DNS 主机支持IPv6。下面是排名最靠前的不支持 IPv6 的网站。

5、机器人皮肤

耶鲁大学的科学家发明了一种可卷曲的"机器人皮肤",可以包裹在任何物体的外层,使得该物体变成可以控制的机器人。上图是毛绒填充的小马玩具穿上"机器人皮肤"以后,变成可以走动了。

科学家已经实现了,给气球穿上"机器人皮肤",使得气球变成可以编程控制。

6、无人机查税

希腊的圣托里尼岛是世界著名的旅游景点,每年有大量的游客参加一日游项目。但是,很多导游公司为了逃税,隐瞒游客数量。

当地政府开始启用无人机,观察游客数量,以及出海的游船,用来评估当地业者应该交税的数量。据称,已经查到9艘没有开票的游船,涉及收入总计约25,000欧元,游船主人现在面临罚款。

7、南大西洋的海底电缆

最近,安哥拉到巴西的海底电缆铺设完成,这是历史上第一条南大西洋的海底电缆。南部非洲和南美洲以前从来没有直连的海底电缆。

这条电缆也是南美洲第一条不经过美国的国际互联网线路。2020年,巴西直达欧洲(葡萄牙)的海底电缆也将完成。

8、AI 艺术品

这个月,佳士得将首次拍卖人工智能艺术品。一幅算法生成的人物肖像,估价达到了1万美元。

事实上,已经有很多绘画机器人,通过大量画作的训练,会自动生成美术作品。下面六幅抽象画,有五幅是人的作品,还有一幅软件生成的,你能看出来是哪一幅吗?

9、一句话新闻

  • 美国科学家发现,人体在禁食期间会产生一种分子,防止血管系统的衰老。也就是说,24小时不吃东西,有助于延长寿命和防止衰老。
  • Java 11最近发布了。但是,你不要用 Oracle 的官方 JDK 开发,因为那个 JDK 升级和用于生产环境是要钱的。你应该使用 OpenJDK,Redhat公开承诺将持续维护 OpenJDK。
  • Facebook宣布,新建的新加坡数据中心,将百分百采用可再生能源,主要由安装在近900个屋顶上的太阳能电池板供电。该项目预计将在2020年完成。
  • Firefox推出 VR 设备专用的网络浏览器。

教程

1、异常处理是错误的设计(英文)

许多主流语言都采用抛出异常的方式处理错误,这篇文章认为,这种设计是错误的,传统的返回值是更好的方式。抛出异常的主要好处是,可以将业务代码与错误处理代码分开,缺点是它改变了控制流,有点像 goto 语言,可以随意跳转。

2、如何撰写技术文档(英文)

技术文档(documents)分成四种:教程(tutorial)、指导(guide)、解释(explanation)和参考(reference)。本文解释了每一种文档的特点,并给出了写作建议。

3、阿里巴巴的18位创始人(英文)

1999年,阿里巴巴集团成立,当时共有18位创始人,大部分是马云的同事、朋友和学生。这篇文章汇总了这18个人的公开资料。

4、使用 Qt 为 Python 脚本添加图形界面(英文)

Qt 是一个 C++ 写的跨平台控件库,PyQt 将其移植到 Python,可以很方便地为 Python 脚本添加图形界面。

5、微服务入门示例(英文)

本文解释了什么是微服务架构,并且给出了一个简单的示例,在 Docker 里面使用 Flask 框架和 ZeroMQ 搭建一个简单的微服务应用。

6、为什么使用 i3 窗口管理器?(英文)

Linux 发行版一般都自带桌面环境,如果你需要的只是一个好用的命令行,那么你可以放弃桌面环境,改用窗口管理器。i3 就是一个很好用的平铺式窗口管理器,本文给出了五个推荐理由。如果你有兴趣的话,这里还有一篇简单教程

7、Pi-hole 拦截广告(英文)

如果你还在用广告拦截器,Pi-hole 是更好的选择。它是树莓派上的 DNS 服务器,架设在局域网里面,内置黑名单,拦截广告域名的 DNS 请求。从此,局域网所有设备都看不到广告。

8、如何制作命令行动画?(英文)

本文介绍如何使用 ncurses 这个 C 语言标准库,写一个简单的命令行动画。

9、如何将 Web 应用做成 Docker?(英文)

本文通过一个很简单的单页应用,演示如何将 Web 应用放到 Docker 里面,然后通过 Docker 运行。

资源

1、Youtube 的教育视频

如果你不知道 Youtube 上可以学什么,就来看看这个帖子。当然,所有资源都是英语的。

2、计算机组织导论:以树莓派和 ARM 汇编语言为例

开源教材,介绍计算机的底层实现,写得比较好懂。

3、从零开始写一个操作系统

一个教程库,每一步都有指导,教你怎么写出一个简单的操作系统。

4、创业公司

一个网页游戏,玩家是创业公司的创始人,模拟经营这家公司,完成各种任务。

5、各种算法的 Python 实现

这个仓库收集 Python 语言实现的各种算法代码。

工具

1、ReactXP

微软基于 React Native 开发的框架,沿用了 React Native 的 API,可以用同一套代码编译出 Web、手机 和 Windows 应用。

2、Bing 的图像搜索

与谷歌一样,Bing 也提供图像搜索。但是,它还带有裁剪功能,可以只搜索图像的一部分。

3、富文本编辑器 Trix

Rails 框架的创造者 Basecamp 公司,开发了一个富文本编辑器 Trix,发布1.0版了。

4、Cephes

Cephes 是 JavaScript (准确说是 WebAssembly)的数学函数库,类似 Python 的 SciPy。

5、qutebrowser

一个 Qt 库制作的最简化浏览器,内核是 Chromium。最大特点就是它自带命令行,可以完全用键盘操作。

6、DNS 1.1.1.1

该网站提供指导,如何将各种操作系统或设备的 DNS 设为 1.1.1.1。

7、sqlfmt

SQL 语句格式化的在线工具。

8、Caddy

Caddy 是一个支持 HTTP/2,自带 HTTPS 功能的跨平台、易于使用的 Web 服务器。

9、camelot

从 PDF 文件(非扫描)里面提取表格的 Python 库。

文摘

1、Chrome 的恐龙游戏

Chrome 浏览器由于网络中断、无法上网时,会显示一个恐龙游戏。一头霸王龙困在沙漠之中,必须不停地奔跑,且必须跳跃避开像仙人掌和翼手龙这样的障碍物,随着游戏的进展,速度会越来越快。

该游戏于2014年9月首次发布。地址栏键入 chrome://dino 就可以进入该游戏。开始游戏的方法是:PC 用户按一下空格键,手机用户点击恐龙。

游戏的设计师塞巴斯蒂安·加布里尔(Sebastien Gabriel)说,断网就好比回到互联网之前的时代,因此我们设想出这个回归"史前时代"的游戏,那时的计算机用户好比恐龙,无网可上就像困在沙漠中。恐龙的形象采用像素艺术的风格,恐龙的动作故意设计得很僵化。

只要不触碰障碍,用户可以一直游戏下去。游戏内部设置的最长游戏时间是1700万年,这大约是霸王龙在地球上存活的时间。

2、湾区就是新的矿场

19世纪时,美国社会有一个流行的现象。许多年轻人离乡背井,为了得到高薪,去遥远的西部矿场挖矿。在那里工作几年,就可以赚取足够的钱,回家娶一个老婆。矿场的生活是非常艰苦的,条件很差,这也是矿主支付高薪的原因,否则没人愿意来。

20世纪时,这种现象变成了石油行业。石油钻井平台通常都在偏远地区,有的还是在沙漠和海洋里。石油工人可以得到高工资,代价是恶劣的生活条件,以及远离家庭的孤独。

现在,这种现象变成了 IT 行业。年轻人为了高薪,来到湾区当程序员,同样过着艰苦的生活。我听说,旧金山的一间卧室里面,居然住了好几个大学毕业生,因为旧金山双居室公寓的租金是5500美元/月。有些谷歌的雇员也是合住,而且地理位置也不好,离交通干线很远。

很多人把湾区看成新时代的矿场。IT 行业的工资比其他行业高,那些外来的程序员,计划是省钱,忍受一段时间的恶劣生活,然后在30多岁退休,再去低成本城市工作并在那里开始家庭生活。

本周图片

1、南极洲的冰川裂缝

上图是充满裂缝的冰川。由于气温上升,南极洲的冰川开始崩解,变成小块落入海中,然后融化。

2、海市蜃楼

下面是2018年8月,有人在加拿大拍到的海市蜃楼照片。船的形状一直在改变,整个过程持续了半个小时,然后就消失不见了。

3、珍道具

珍道具(Chindōgu)是一些富有创意,但并不实用的发明。它是今年71岁的 Kenji Kawakami 在上个世纪90年代发起的。当时,他负责编制一本针对日本家庭主妇的邮购商品目录。有时候,商品目录看起来很薄,他就会加入一些自己制作的产品,有点像恶作剧。

遮雨的鞋子。

唇膏状的黄油棒。

带有扫帚功能的拖鞋。

感冒患者专用的面纸架。

喷水的牙刷。

新奇

1、便携打字机

美国一家公司推出一台便携式设备,只能用于打字,这是为了防止使用者分心。它包括一个全尺寸键盘和一个电子墨水屏,可以打开多个文档,系统会自动保存并同步到Dropbox等服务。该设备的众筹价是279美元。

本周金句

1、

七年前,风险投资家 Marc Andreessen 写了一篇文章《软件正在吞噬世界》。他提出,软件公司将会赢得世界。

现在我们知道,他是对的。但是,他没有提到,这个社会的大部分人不拥有软件公司,而是生活在被软件吃掉的世界里。

--John Battelle《被软件吃掉的世界》

2、

我有时觉得,硅谷是一个游戏,每个玩家有10年时间。在这10年里面,如果你发财了,就赢得了游戏,否则你就出局,必须离开硅谷。

--Florent Crivello

欢迎订阅

这个专栏每周五发布,同步更新在我的个人网站微信公众号语雀

微信搜索"阮一峰的网络日志 "或者扫描二维码,即可订阅。

(完)

文档信息

]]>
0
<![CDATA[How Tensorflow set device for each Operation ?]]> http://www.udpwork.com/item/17128.html http://www.udpwork.com/item/17128.html#reviews Fri, 12 Oct 2018 11:41:12 +0800 Robin Dong http://www.udpwork.com/item/17128.html In Tensorflow, we only need to use snippet below to assign a device to a Operation:

with tf.device('/GPU:0'):
  ...
  result = tf.matmul(a, b)

How dose it implement? Let’s take a look.

There is a mechanism called‘context manager’in Python. For example, we can use it to add a wrapper for a few codes:

from contextlib import contextmanager

@contextmanager
def tag(name):
  print("[%s]" % name)
  yield
  print("[/%s]" % name)
  
with tag("robin"):
  print("what")
  print("is")
  print("nature's")

The result of running this script is:

[robin]
what
is
nature's
[/robin]

Function ‘tag()’ works like a decorator. It will do something before and after those codes laying under its ‘context’.

Tensorflow uses the same principle.

@tf_export("device")                                                       
def device(device_name_or_function):
...
  if context.executing_eagerly():                                          
    # TODO(agarwal): support device functions in EAGER mode.
    if callable(device_name_or_function):
      raise RuntimeError(
          "tf.device does not support functions when eager execution "
          "is enabled.")
    return context.device(device_name_or_function)
  else:
    return get_default_graph().device(device_name_or_function)

This will call class Graph’s function ‘device()’. Its implementation:

@tf_export("GraphKeys")
class GraphKeys(object):
...
  @tf_contextlib.contextmanager
  def device(self, device_name_or_function):
  ...
      self._add_device_to_stack(device_name_or_function, offset=2)
    try:
      yield
    finally:
      self._device_function_stack.pop_obj()

The key line is ‘self._add_device_to_stack()’. Context of ‘device’ will add device name into stack of python, and when developer create an Operation it will fetch device name from stack and set it to this Operation.
Let’s check the code routine of creating Operation:

@tf_export("GraphKeys")
class GraphKeys(object):
...
  def create_op(
      self,
      op_type,                                                             
      inputs,
      dtypes,  # pylint: disable=redefined-outer-name
      input_types=None,
      name=None,
      attrs=None,
      op_def=None,                                                         
      compute_shapes=True,                                                 
      compute_device=True):
  ...
    with self._mutation_lock():
      ret = Operation(
          node_def,
          self,
          inputs=inputs,
          output_types=dtypes,
          control_inputs=control_inputs,
          input_types=input_types,
          original_op=self._default_original_op,
          op_def=op_def)
      self._create_op_helper(ret, compute_device=compute_device)
    return ret

def _create_op_helper(self, op, compute_device=True):
  ...
    if compute_device:
      self._apply_device_functions(op)

def _apply_device_functions(self, op):
  ...
    for device_spec in self._device_function_stack.peek_objs():
      if device_spec.function is None:
        break
      op._set_device(device_spec.function(op))
    op._device_code_locations = self._snapshot_device_function_stack_metadata()

‘self._device_function_stack.peek_objs’ is where it peek the device name from stack.

]]>
In Tensorflow, we only need to use snippet below to assign a device to a Operation:

with tf.device('/GPU:0'):
  ...
  result = tf.matmul(a, b)

How dose it implement? Let’s take a look.

There is a mechanism called‘context manager’in Python. For example, we can use it to add a wrapper for a few codes:

from contextlib import contextmanager

@contextmanager
def tag(name):
  print("[%s]" % name)
  yield
  print("[/%s]" % name)
  
with tag("robin"):
  print("what")
  print("is")
  print("nature's")

The result of running this script is:

[robin]
what
is
nature's
[/robin]

Function ‘tag()’ works like a decorator. It will do something before and after those codes laying under its ‘context’.

Tensorflow uses the same principle.

@tf_export("device")                                                       
def device(device_name_or_function):
...
  if context.executing_eagerly():                                          
    # TODO(agarwal): support device functions in EAGER mode.
    if callable(device_name_or_function):
      raise RuntimeError(
          "tf.device does not support functions when eager execution "
          "is enabled.")
    return context.device(device_name_or_function)
  else:
    return get_default_graph().device(device_name_or_function)

This will call class Graph’s function ‘device()’. Its implementation:

@tf_export("GraphKeys")
class GraphKeys(object):
...
  @tf_contextlib.contextmanager
  def device(self, device_name_or_function):
  ...
      self._add_device_to_stack(device_name_or_function, offset=2)
    try:
      yield
    finally:
      self._device_function_stack.pop_obj()

The key line is ‘self._add_device_to_stack()’. Context of ‘device’ will add device name into stack of python, and when developer create an Operation it will fetch device name from stack and set it to this Operation.
Let’s check the code routine of creating Operation:

@tf_export("GraphKeys")
class GraphKeys(object):
...
  def create_op(
      self,
      op_type,                                                             
      inputs,
      dtypes,  # pylint: disable=redefined-outer-name
      input_types=None,
      name=None,
      attrs=None,
      op_def=None,                                                         
      compute_shapes=True,                                                 
      compute_device=True):
  ...
    with self._mutation_lock():
      ret = Operation(
          node_def,
          self,
          inputs=inputs,
          output_types=dtypes,
          control_inputs=control_inputs,
          input_types=input_types,
          original_op=self._default_original_op,
          op_def=op_def)
      self._create_op_helper(ret, compute_device=compute_device)
    return ret

def _create_op_helper(self, op, compute_device=True):
  ...
    if compute_device:
      self._apply_device_functions(op)

def _apply_device_functions(self, op):
  ...
    for device_spec in self._device_function_stack.peek_objs():
      if device_spec.function is None:
        break
      op._set_device(device_spec.function(op))
    op._device_code_locations = self._snapshot_device_function_stack_metadata()

‘self._device_function_stack.peek_objs’ is where it peek the device name from stack.

]]>
0
<![CDATA[代餐饮料「若饭」评测记]]> http://www.udpwork.com/item/17127.html http://www.udpwork.com/item/17127.html#reviews Thu, 11 Oct 2018 11:42:40 +0800 图拉鼎 http://www.udpwork.com/item/17127.html 这是一篇关于「若饭」的体验评测报告文章。假如你想减少每天思考吃什么的频率,或者说偶尔应急的时候能快速解决吃饭问题,或者说想了解若饭、Soylent 此类代餐的食用体验,那么你应该会想要看我的这篇评测。

这篇评测不会劝人放弃正餐、拥抱代餐,我食用此类代餐的目的正如前面提到的一样,是偶尔或应急,大多数时候,我依然会享受各种美食。所以假如你有「吃这些东西还有什么意思」的想法,还是不要继续往下看了。

言归正传,先讲我和它们的故事吧。

一次体验 Soylent 的经历

最早知道此类代餐粉,是从好友@周楷雯Kevin那,当时他正在美食之都广州,却正在尝试减肥(这是我最佩服他的地方)。而他减肥的方式,正是食用 Soylent。当时 Soylent 只有袋装的冲泡粉,食用它的方式就是像泡奶粉一样泡着喝。

不久后他向我传来捷报——减肥成果喜人,虽然作为一个瘦子,我对减肥丝毫没有兴趣,但是我对 Soylent 却产生了好奇。因为我一开始就知道,这不是用来减肥的东西,但是它可以用来取代正餐,让人有吃饱了饭的感觉以及所必要的足够营养,所谓采用食用它来减肥的原因也在于此——既然已经吃饱了,那就不吃其他东西了,也就控制住了摄入,就控制住了体重,有了减肥的效果。

对这种新鲜事物无法阻挡的我,向 Kevin 表示了强烈的好奇心,Kevin 当时寄了两包给我,于是我也开始了体验之路。

收到了 Kevin 送我的 Soylent 后,老实说,因为那段日子的生活很滋润,根本没有机会吃它。直到 2016 年那次冬天爬黄山的经历,终于有了一次吃它的经历。因为要出去爬山看风景,来回酒店吃饭不方便,于是出门前在保温杯里用开水泡了 Soylent,然后在黄山四处玩耍,饿的时候就喝在保温杯里成糊状的 Soylent,还真解决了吃饭问题,同时还有力气爬山玩耍。

这是我仅有一次在户外的过程中,吃 Soylent 的经历,老实说还比较满意。但是之前就渐渐地忘记了有 Soylent 的这件事情,直到它过期…

在那次 Soylent 的经历之后,我同时也知道了若饭,因为当时分享到社交网络之后,了解到若饭这个项目,创始人是认识的一个朋友。不过当时我没有深入,也停止了尝试此类代餐食品。因为新鲜劲过了,当时也没有食用他们的场景,于是就把这事放在一边了。

开始正式的「若饭」体验评测

时间一转眼过了两年,到了 2018 年。上半年搬家到了郊外,这边的生活不像市中心那么丰富,特别是饮食方面,连外卖的选择都很少,当时我不仅卸载了几个外卖 App,还感叹式地发了一条微博:吃饭真的好麻烦…要不要囤点「若饭」和「Soylent」。

当时不经意的这么一说,被若饭注意到,于是我拿到了一箱若饭的产品——「若饭」液体版 x 15 瓶,开始了我的体验评测之路。

图为若饭与 Soylent 的两张对比图(放大图可见详细配方):

若饭和 Soylent 对比 02

若饭和 Soylent 对比 01

若饭的营养构成与生产

在开始食用若饭前,我特别向创始人讨教了一些问题,特别是关心若饭到底是由怎么东西做的、是怎么生产的。毕竟是吃进肚子里的东西,还是了解一些比较好。

他告诉我,若饭的原料都是找一些大的供应商采购来的,然后再根据自己的配比调配而成。一个简单的比方就是,健身增肌的朋友可能都听说过甚至吃过「蛋白粉」,吃蛋白粉可以补充蛋白质。与此类似的,若饭也是混合了各种营养成分的「粉」,不仅有蛋白质,还有各种维生素等,保证了人体所需的全面的营养。

而生产方面,他告诉我,若饭是委托专业的生产商进行生产,这像一种 OEM 代工的形式,生产厂商都是有合格生产资格、有丰富的食品生产经验的厂家,在食品安全方面都是可以放心的。生产过程就是调配、灌装等等…整个就是流水线生产,和大家平常偶尔能看到的食品、饮料等生产线是一样的。而且他自己在公司的另一个职位就是「首席试吃官」,自若饭研发开始至今已经差不多四年时间每天至少一餐若饭。

尽管我对食品行业不是特别了解,但能了解个初步,也是我对若饭信任的一步。

我的食用频率

我大概吃了一个多月才完全喝完这 15 瓶若饭。基本上不管平常还是周末,只要是我不外出的时候,我都会考虑吃它。这个频繁说不准,有时连续三天会每天喝一瓶,有时间隔一天或两天才会喝一瓶。

基本上,我只在午餐和晚餐时喝若饭,不在早餐阶段喝,一来我是的早餐比较简单,如果喝一顿若饭的话,感觉会偏「饱」,所以我就只用若饭来代替午餐或晚餐。这 15 瓶若饭,午餐与晚餐大概各占一半。但是我不会连续喝,即当天午餐和晚餐同时喝若饭:原因和我不会在同一天吃两顿麦当劳一样,怕吃腻就好久都不想吃了。

于是,我比较随意(隔三差五)又有点规律(不连续吃两顿))的食用正好贴合了我前面所说的目的——吃代餐不是因为想用它完全取代正餐,而是偶尔或应急。我觉得,这个食用场景,应该也是大多数人的食用场景。

关于「口感与味道」

对代餐评测的一个重要的问题就是「口感与味道如何」,不然即使这个东西营养全面,如果难以下咽的话,也是不想吃的。

若饭的首款液体瓶装代餐,是咖啡口味的。我自己的体验也是可以品尝到比较明显的咖啡味,据说它大概有 1/8 杯美式咖啡的含量,我觉得只要不是对咖啡敏感的人,都不会感到过度。除去咖啡的味道,那应该是豆浆类的味道了,这部分是淡淡的,不会特别特别突出。

说起口感的话,若饭总体是液体饮料,而不是食品,你能感受到固体颗粒的存在,但不会特别大,不会是你喝粥的时候感受到的大米粒。总体可能有点接近 NFC 果汁,这就是为何若饭建议在喝之前先摇一摇,这是为了把固体颗粒摇匀后,喝起来感觉会好一些。

如果一句话总结,就是:融合豆浆与咖啡的口味,总体带有固体颗粒的饮料吧。口感与味道这个比较主观,所以建议每个人都自己亲自品尝一下。

饱腹感

谈完了「口感与味道」,接下来聊聊最重要的「饱腹感」。严格来讲「饱腹感」有两个含义,一种是:吃了能不能有「饱腹感」;另一种是「饱腹感」的时间有多长,即吃完后到再次感到饥饿这中间有多长。我在这节都会做相关覆盖。

另外,我觉得「饱腹感」是与「口感与味道」一样应该并列的一种评价代餐饮料的指标,试想一想,即使有一种代餐,非常好喝,也营养全面,但是,你喝完后一小时就饿了…然后开始想下一顿了,这还叫什么「代」餐?

对若饭能提供的饱腹感,我比较满意的。首先,它确实能提供饱腹感,在饥饿的喝完它,马上就能感到不饿了,就像吃完了一顿正餐,这就是我们吃它的首要目的了。

其次,关于「饱腹时长」,它能提供至少 3 小时,平均约 4 小时多的饱腹感。这是我在自己的试吃过程,我自己记录下吃的时间以及感到饿的时间最后算出来的,平均就在这 3 ~ 4 小时之间。

因为每餐的饱腹感时长,常常会受你前一顿饭的影响,基本上你在越饿的时候吃,那么饱腹感时长就会越短——即在越饿的时候吃饭,下一顿饿的通常也越快。比如,你不吃早饭就吃午饭,那么下午也会饿得更快。

因而我的这个饱腹感测试,没有选择这种极端的情况,我没有在很饿的时候才去喝若饭,就像平常一样,可能觉得中午时间到了,就去吃,晚饭时间到了,就去吃。这样给我带来了差不多 4 小时 30 分的饱腹时长。仅有一次比较挑比较极端的时间,我到了比较饿的时候才喝若饭,然后到首次感觉肚子饿的时间,过了约 3 小时。

因为测了几次基本上都在 4 小时多这个范围内,所以往后几次,我已经不严格记录开吃和肚子感到再饿的时间。

总之,大概就是中午 12 点吃完饭,下午 4 点多可能会饿;傍晚 18:00 吃完饭,晚上 22:00 可能会饿。这可能是对我本人(比较容易饿的体质)的比较普遍的感觉了。这就是为啥我在公司上班的时候,对公司能提供下午茶这件事情是比较认可且欢迎的事情,因为我平常吃正餐也是差不多这样的饥饿曲线。

关于「若饭」可能的长期体验

对于主要是代餐的「若饭」来说,重点就是上面几样感受了。但可能还会有人会好奇,代餐是没问题了,但这东西,长期食用,到底有没有问题?我在这里肯定无法回答这个问题的,毕竟我只有在这一个多的时间吃过他,即使完全没有问题也不能以此得出结论可以长期食用它。

但是我这里可以提出一个其他问题:若你长期食用同一种食品(比如只是蔬菜,只是肉,只是垃圾食品…),中间没有其他东西调剂,会不会有问题?我想多数人一定会觉得,这会营养失衡吧?相比之下,若饭至少在构成上是满足了人体长期所需要的营养,相比之下肯定比其他的更合适长期食用的。

总之,若饭满足了我对于代餐的需求。

对「若饭」的其他补充和建议

目前为止,我的试吃体验都是发生在屋里,我觉得食用若饭非常合适的场景之一就是在户外,就像我第一次吃 Soylent 一样。无论是去户外玩,或者是因为工作职业原因,身边吃不到合适的正餐的时候,吃若饭这种代餐实在是太合适不过了。关键是它非常便携,和带一瓶矿泉水一样,带个三瓶就能解决一天吃饭问题了,一点压力都没有。

关于对此产品的建议,我目前能想到的就是,希望能出更多的口味出来,尽管目前的咖啡口味还不错,但是同种口味容易喝腻,如果能出个三四种口味出来,那就最好不过了。

另外,在产品包装、宣传上,也可以进一步进行改进,可以更加生动活动、或者更加科技有趣,这里可能给不了具体的建议,总之就是让更多的人可以通过第一印象就对它产生兴趣或好奇。

总结

以上便是我对若饭的一个月的试吃的不完全总结,有所疏漏或错误在所难免,希望能继续和大家针对这个进行交流。

如果能有人看到我写的这篇评测,并因此发现了自己一直在找寻的东西,或者因此让生活更方便了一些,那就是最好的事情了。

假如你看完文章后对其感兴趣,可以在微信公众号关注「若饭」,并且用「TUALATRIX」作为优惠码下单,满 100 减 20。

若饭优惠码.png

Enjoy~

本站架设于Linode 东京机房

]]>
这是一篇关于「若饭」的体验评测报告文章。假如你想减少每天思考吃什么的频率,或者说偶尔应急的时候能快速解决吃饭问题,或者说想了解若饭、Soylent 此类代餐的食用体验,那么你应该会想要看我的这篇评测。

这篇评测不会劝人放弃正餐、拥抱代餐,我食用此类代餐的目的正如前面提到的一样,是偶尔或应急,大多数时候,我依然会享受各种美食。所以假如你有「吃这些东西还有什么意思」的想法,还是不要继续往下看了。

言归正传,先讲我和它们的故事吧。

一次体验 Soylent 的经历

最早知道此类代餐粉,是从好友@周楷雯Kevin那,当时他正在美食之都广州,却正在尝试减肥(这是我最佩服他的地方)。而他减肥的方式,正是食用 Soylent。当时 Soylent 只有袋装的冲泡粉,食用它的方式就是像泡奶粉一样泡着喝。

不久后他向我传来捷报——减肥成果喜人,虽然作为一个瘦子,我对减肥丝毫没有兴趣,但是我对 Soylent 却产生了好奇。因为我一开始就知道,这不是用来减肥的东西,但是它可以用来取代正餐,让人有吃饱了饭的感觉以及所必要的足够营养,所谓采用食用它来减肥的原因也在于此——既然已经吃饱了,那就不吃其他东西了,也就控制住了摄入,就控制住了体重,有了减肥的效果。

对这种新鲜事物无法阻挡的我,向 Kevin 表示了强烈的好奇心,Kevin 当时寄了两包给我,于是我也开始了体验之路。

收到了 Kevin 送我的 Soylent 后,老实说,因为那段日子的生活很滋润,根本没有机会吃它。直到 2016 年那次冬天爬黄山的经历,终于有了一次吃它的经历。因为要出去爬山看风景,来回酒店吃饭不方便,于是出门前在保温杯里用开水泡了 Soylent,然后在黄山四处玩耍,饿的时候就喝在保温杯里成糊状的 Soylent,还真解决了吃饭问题,同时还有力气爬山玩耍。

这是我仅有一次在户外的过程中,吃 Soylent 的经历,老实说还比较满意。但是之前就渐渐地忘记了有 Soylent 的这件事情,直到它过期…

在那次 Soylent 的经历之后,我同时也知道了若饭,因为当时分享到社交网络之后,了解到若饭这个项目,创始人是认识的一个朋友。不过当时我没有深入,也停止了尝试此类代餐食品。因为新鲜劲过了,当时也没有食用他们的场景,于是就把这事放在一边了。

开始正式的「若饭」体验评测

时间一转眼过了两年,到了 2018 年。上半年搬家到了郊外,这边的生活不像市中心那么丰富,特别是饮食方面,连外卖的选择都很少,当时我不仅卸载了几个外卖 App,还感叹式地发了一条微博:吃饭真的好麻烦…要不要囤点「若饭」和「Soylent」。

当时不经意的这么一说,被若饭注意到,于是我拿到了一箱若饭的产品——「若饭」液体版 x 15 瓶,开始了我的体验评测之路。

图为若饭与 Soylent 的两张对比图(放大图可见详细配方):

若饭和 Soylent 对比 02

若饭和 Soylent 对比 01

若饭的营养构成与生产

在开始食用若饭前,我特别向创始人讨教了一些问题,特别是关心若饭到底是由怎么东西做的、是怎么生产的。毕竟是吃进肚子里的东西,还是了解一些比较好。

他告诉我,若饭的原料都是找一些大的供应商采购来的,然后再根据自己的配比调配而成。一个简单的比方就是,健身增肌的朋友可能都听说过甚至吃过「蛋白粉」,吃蛋白粉可以补充蛋白质。与此类似的,若饭也是混合了各种营养成分的「粉」,不仅有蛋白质,还有各种维生素等,保证了人体所需的全面的营养。

而生产方面,他告诉我,若饭是委托专业的生产商进行生产,这像一种 OEM 代工的形式,生产厂商都是有合格生产资格、有丰富的食品生产经验的厂家,在食品安全方面都是可以放心的。生产过程就是调配、灌装等等…整个就是流水线生产,和大家平常偶尔能看到的食品、饮料等生产线是一样的。而且他自己在公司的另一个职位就是「首席试吃官」,自若饭研发开始至今已经差不多四年时间每天至少一餐若饭。

尽管我对食品行业不是特别了解,但能了解个初步,也是我对若饭信任的一步。

我的食用频率

我大概吃了一个多月才完全喝完这 15 瓶若饭。基本上不管平常还是周末,只要是我不外出的时候,我都会考虑吃它。这个频繁说不准,有时连续三天会每天喝一瓶,有时间隔一天或两天才会喝一瓶。

基本上,我只在午餐和晚餐时喝若饭,不在早餐阶段喝,一来我是的早餐比较简单,如果喝一顿若饭的话,感觉会偏「饱」,所以我就只用若饭来代替午餐或晚餐。这 15 瓶若饭,午餐与晚餐大概各占一半。但是我不会连续喝,即当天午餐和晚餐同时喝若饭:原因和我不会在同一天吃两顿麦当劳一样,怕吃腻就好久都不想吃了。

于是,我比较随意(隔三差五)又有点规律(不连续吃两顿))的食用正好贴合了我前面所说的目的——吃代餐不是因为想用它完全取代正餐,而是偶尔或应急。我觉得,这个食用场景,应该也是大多数人的食用场景。

关于「口感与味道」

对代餐评测的一个重要的问题就是「口感与味道如何」,不然即使这个东西营养全面,如果难以下咽的话,也是不想吃的。

若饭的首款液体瓶装代餐,是咖啡口味的。我自己的体验也是可以品尝到比较明显的咖啡味,据说它大概有 1/8 杯美式咖啡的含量,我觉得只要不是对咖啡敏感的人,都不会感到过度。除去咖啡的味道,那应该是豆浆类的味道了,这部分是淡淡的,不会特别特别突出。

说起口感的话,若饭总体是液体饮料,而不是食品,你能感受到固体颗粒的存在,但不会特别大,不会是你喝粥的时候感受到的大米粒。总体可能有点接近 NFC 果汁,这就是为何若饭建议在喝之前先摇一摇,这是为了把固体颗粒摇匀后,喝起来感觉会好一些。

如果一句话总结,就是:融合豆浆与咖啡的口味,总体带有固体颗粒的饮料吧。口感与味道这个比较主观,所以建议每个人都自己亲自品尝一下。

饱腹感

谈完了「口感与味道」,接下来聊聊最重要的「饱腹感」。严格来讲「饱腹感」有两个含义,一种是:吃了能不能有「饱腹感」;另一种是「饱腹感」的时间有多长,即吃完后到再次感到饥饿这中间有多长。我在这节都会做相关覆盖。

另外,我觉得「饱腹感」是与「口感与味道」一样应该并列的一种评价代餐饮料的指标,试想一想,即使有一种代餐,非常好喝,也营养全面,但是,你喝完后一小时就饿了…然后开始想下一顿了,这还叫什么「代」餐?

对若饭能提供的饱腹感,我比较满意的。首先,它确实能提供饱腹感,在饥饿的喝完它,马上就能感到不饿了,就像吃完了一顿正餐,这就是我们吃它的首要目的了。

其次,关于「饱腹时长」,它能提供至少 3 小时,平均约 4 小时多的饱腹感。这是我在自己的试吃过程,我自己记录下吃的时间以及感到饿的时间最后算出来的,平均就在这 3 ~ 4 小时之间。

因为每餐的饱腹感时长,常常会受你前一顿饭的影响,基本上你在越饿的时候吃,那么饱腹感时长就会越短——即在越饿的时候吃饭,下一顿饿的通常也越快。比如,你不吃早饭就吃午饭,那么下午也会饿得更快。

因而我的这个饱腹感测试,没有选择这种极端的情况,我没有在很饿的时候才去喝若饭,就像平常一样,可能觉得中午时间到了,就去吃,晚饭时间到了,就去吃。这样给我带来了差不多 4 小时 30 分的饱腹时长。仅有一次比较挑比较极端的时间,我到了比较饿的时候才喝若饭,然后到首次感觉肚子饿的时间,过了约 3 小时。

因为测了几次基本上都在 4 小时多这个范围内,所以往后几次,我已经不严格记录开吃和肚子感到再饿的时间。

总之,大概就是中午 12 点吃完饭,下午 4 点多可能会饿;傍晚 18:00 吃完饭,晚上 22:00 可能会饿。这可能是对我本人(比较容易饿的体质)的比较普遍的感觉了。这就是为啥我在公司上班的时候,对公司能提供下午茶这件事情是比较认可且欢迎的事情,因为我平常吃正餐也是差不多这样的饥饿曲线。

关于「若饭」可能的长期体验

对于主要是代餐的「若饭」来说,重点就是上面几样感受了。但可能还会有人会好奇,代餐是没问题了,但这东西,长期食用,到底有没有问题?我在这里肯定无法回答这个问题的,毕竟我只有在这一个多的时间吃过他,即使完全没有问题也不能以此得出结论可以长期食用它。

但是我这里可以提出一个其他问题:若你长期食用同一种食品(比如只是蔬菜,只是肉,只是垃圾食品…),中间没有其他东西调剂,会不会有问题?我想多数人一定会觉得,这会营养失衡吧?相比之下,若饭至少在构成上是满足了人体长期所需要的营养,相比之下肯定比其他的更合适长期食用的。

总之,若饭满足了我对于代餐的需求。

对「若饭」的其他补充和建议

目前为止,我的试吃体验都是发生在屋里,我觉得食用若饭非常合适的场景之一就是在户外,就像我第一次吃 Soylent 一样。无论是去户外玩,或者是因为工作职业原因,身边吃不到合适的正餐的时候,吃若饭这种代餐实在是太合适不过了。关键是它非常便携,和带一瓶矿泉水一样,带个三瓶就能解决一天吃饭问题了,一点压力都没有。

关于对此产品的建议,我目前能想到的就是,希望能出更多的口味出来,尽管目前的咖啡口味还不错,但是同种口味容易喝腻,如果能出个三四种口味出来,那就最好不过了。

另外,在产品包装、宣传上,也可以进一步进行改进,可以更加生动活动、或者更加科技有趣,这里可能给不了具体的建议,总之就是让更多的人可以通过第一印象就对它产生兴趣或好奇。

总结

以上便是我对若饭的一个月的试吃的不完全总结,有所疏漏或错误在所难免,希望能继续和大家针对这个进行交流。

如果能有人看到我写的这篇评测,并因此发现了自己一直在找寻的东西,或者因此让生活更方便了一些,那就是最好的事情了。

假如你看完文章后对其感兴趣,可以在微信公众号关注「若饭」,并且用「TUALATRIX」作为优惠码下单,满 100 减 20。

若饭优惠码.png

Enjoy~

本站架设于Linode 东京机房

]]>
0
<![CDATA[Git 原理入门]]> http://www.udpwork.com/item/17126.html http://www.udpwork.com/item/17126.html#reviews Wed, 10 Oct 2018 18:07:14 +0800 阮一峰 http://www.udpwork.com/item/17126.html Git 是最流行的版本管理工具,也是程序员的必备技能之一。

即使天天使用它,很多人也未必了解它的原理。Git 为什么可以管理版本?git add、git commit这些基本命令,到底在做什么,你说得清楚吗?

这篇文章用一个实例,解释 Git 的运行过程,帮助你理解 Git 的原理。

一、初始化

首先,让我们创建一个项目目录,并进入该目录。

$ mkdir git-demo-project
$ cd git-demo-project

我们打算对该项目进行版本管理,第一件事就是使用git init命令,进行初始化。

$ git init

git init命令只做一件事,就是在项目根目录下创建一个.git子目录,用来保存版本信息。

$ ls .git

branches/
config
description
HEAD
hooks/
info/
objects/
refs/

上面命令显示,.git内部还有一些子目录,这里先不解释它们的含义。

二、保存对象

接下来,新建一个空文件test.txt。

$ touch test.txt

然后,把这个文件加入 Git 仓库,也就是为test.txt的当前内容创建一个副本。

$ git hash-object -w test.txt

e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

上面代码中,git hash-object命令把test.txt的当前内容压缩成二进制文件,存入 Git。压缩后的二进制文件,称为一个 Git 对象,保存在.git/objects目录。

这个命令还会计算当前内容的 SHA1 哈希值(长度40的字符串),作为该对象的文件名。下面看一下这个新生成的 Git 对象文件。

$ ls -R .git/objects

.git/objects/e6:
9de29bb2d1d6434b8b29ae775ad8c2e48c5391

上面代码可以看到,.git/objects下面多了一个子目录,目录名是哈希值的前2个字符,该子目录下面有一个文件,文件名是哈希值的后38个字符。

再看一下这个文件的内容。

$ cat .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391

上面代码输出的文件内容,都是一些二进制字符。你可能会问,test.txt是一个空文件,为什么会有内容?这是因为二进制对象里面还保存一些元数据。

如果想看该文件原始的文本内容,要用git cat-file命令。

$ git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

因为原始文件是空文件,所以上面的命令什么也看不到。现在向test.txt写入一些内容。

$ echo 'hello world' > test.txt

因为文件内容已经改变,需要将它再次保存成 Git 对象。

$ git hash-object -w test.txt

3b18e512dba79e4c8300dd08aeb37f8e728b8dad

上面代码可以看到,随着内容改变,test.txt的哈希值已经变了。同时,新文件.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad也已经生成了。现在可以看到文件内容了。

$ git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad

hello world

三、暂存区

文件保存成二进制对象以后,还需要通知 Git 哪些文件发生了变动。所有变动的文件,Git 都记录在一个区域,叫做"暂存区"(英文叫做 index 或者 stage)。等到变动告一段落,再统一把暂存区里面的文件写入正式的版本历史。

git update-index命令用于在暂存区记录一个发生变动的文件。

$ git update-index --add --cacheinfo 100644 \
3b18e512dba79e4c8300dd08aeb37f8e728b8dad test.txt

上面命令向暂存区写入文件名test.txt、二进制对象名(哈希值)和文件权限。

git ls-files命令可以显示暂存区当前的内容。

$ git ls-files --stage

100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0   test.txt

上面代码表示,暂存区现在只有一个文件test.txt,以及它的二进制对象名和权限。知道了二进制对象名,就可以在.git/objects子目录里面读出这个文件的内容。

git status命令会产生更可读的结果。

$ git status

要提交的变更:
    新文件:   test.txt

上面代码表示,暂存区里面只有一个新文件test.txt,等待写入历史。

四、git add 命令

上面两步(保存对象和更新暂存区),如果每个文件都做一遍,那是很麻烦的。Git 提供了git add命令简化操作。

$ git add --all

上面命令相当于,对当前项目所有变动的文件,执行前面的两步操作。

五、commit 的概念

暂存区保留本次变动的文件信息,等到修改了差不多了,就要把这些信息写入历史,这就相当于生成了当前项目的一个快照(snapshot)。

项目的历史就是由不同时点的快照构成。Git 可以将项目恢复到任意一个快照。快照在 Git 里面有一个专门名词,叫做 commit,生成快照又称为完成一次提交。

下文所有提到"快照"的地方,指的就是 commit。

六、完成提交

首先,设置一下用户名和 Email,保存快照的时候,会记录是谁提交的。

$ git config user.name "用户名" 
$ git config user.email "Email 地址"

接下来,要保存当前的目录结构。前面保存对象的时候,只是保存单个文件,并没有记录文件之间的目录关系(哪个文件在哪里)。

git write-tree命令用来将当前的目录结构,生成一个 Git 对象。

$ git write-tree

c3b8bb102afeca86037d5b5dd89ceeb0090eae9d

上面代码中,目录结构也是作为二进制对象保存的,也保存在.git/objects目录里面,对象名就是哈希值。

让我们看一下这个文件的内容。

$ git cat-file -p c3b8bb102afeca86037d5b5dd89ceeb0090eae9d

100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    test.txt

可以看到,当前的目录里面只有一个test.txt文件。

所谓快照,就是保存当前的目录结构,以及每个文件对应的二进制对象。上一个操作,目录结构已经保存好了,现在需要将这个目录结构与一些元数据一起写入版本历史。

git commit-tree命令用于将目录树对象写入版本历史。

$ echo "first commit" | git commit-tree c3b8bb102afeca86037d5b5dd89ceeb0090eae9d

c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

上面代码中,提交的时候需要有提交说明,echo "first commit"就是给出提交说明。然后,git commit-tree命令将元数据和目录树,一起生成一个 Git 对象。现在,看一下这个对象的内容。

$ git cat-file -p c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

tree c3b8bb102afeca86037d5b5dd89ceeb0090eae9d
author ruanyf  1538889134 +0800
committer ruanyf  1538889134 +0800

first commit

上面代码中,输出结果的第一行是本次快照对应的目录树对象(tree),第二行和第三行是作者和提交人信息,最后是提交说明。

git log命令也可以用来查看某个快照信息。

$ git log --stat c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

commit c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
Author: ruanyf 
Date:   Sun Oct 7 13:12:14 2018 +0800

    first commit

 test.txt | 1 +
 1 file changed, 1 insertion(+)

七、git commit 命令

Git 提供了git commit命令,简化提交操作。保存进暂存区以后,只要git commit一个命令,就同时提交目录结构和说明,生成快照。

$ git commit -m "first commit"

此外,还有两个命令也很有用。

git checkout命令用于切换到某个快照。

$ git checkout c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

git show命令用于展示某个快照的所有代码变动。

$ git show c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

八、branch 的概念

到了这一步,还没完。如果这时用git log命令查看整个版本历史,你看不到新生成的快照。

$ git log

上面命令没有任何输出,这是为什么呢?快照明明已经写入历史了。

原来git log命令只显示当前分支的变动,虽然我们前面已经提交了快照,但是还没有记录这个快照属于哪个分支。

所谓分支(branch)就是指向某个快照的指针,分支名就是指针名。哈希值是无法记忆的,分支使得用户可以为快照起别名。而且,分支会自动更新,如果当前分支有新的快照,指针就会自动指向它。比如,master 分支就是有一个叫做 master 指针,它指向的快照就是 master 分支的当前快照。

用户可以对任意快照新建指针。比如,新建一个 fix-typo 分支,就是创建一个叫做 fix-typo 的指针,指向某个快照。所以,Git 新建分支特别容易,成本极低。

Git 有一个特殊指针HEAD, 总是指向当前分支的最近一次快照。另外,Git 还提供简写方式,HEAD^指向HEAD的前一个快照(父节点),HEAD~6则是HEAD之前的第6个快照。

每一个分支指针都是一个文本文件,保存在.git/refs/heads/目录,该文件的内容就是它所指向的快照的二进制对象名(哈希值)。

九、更新分支

下面演示更新分支是怎么回事。首先,修改一下test.txt。

$ echo "hello world again" > test.txt

然后,保存二进制对象。

$ git hash-object -w test.txt

c90c5155ccd6661aed956510f5bd57828eec9ddb

接着,将这个对象写入暂存区,并保存目录结构。

$ git update-index test.txt
$ git write-tree

1552fd52bc14497c11313aa91547255c95728f37

最后,提交目录结构,生成一个快照。

$ echo "second commit" | git commit-tree 1552fd52bc14497c11313aa91547255c95728f37 -p c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

785f188674ef3c6ddc5b516307884e1d551f53ca

上面代码中,git commit-tree的-p参数用来指定父节点,也就是本次快照所基于的快照。

现在,我们把本次快照的哈希值,写入.git/refs/heads/master文件,这样就使得master指针指向这个快照。

$ echo 785f188674ef3c6ddc5b516307884e1d551f53ca > .git/refs/heads/master

现在,git log就可以看到两个快照了。

$ git log

commit 785f188674ef3c6ddc5b516307884e1d551f53ca (HEAD -> master)
Author: ruanyf 
Date:   Sun Oct 7 13:38:00 2018 +0800

    second commit

commit c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
Author: ruanyf 
Date:   Sun Oct 7 13:12:14 2018 +0800

    first commit

git log的运行过程是这样的:

  1. 查找HEAD指针对应的分支,本例是master
  2. 找到master指针指向的快照,本例是785f188674ef3c6ddc5b516307884e1d551f53ca
  3. 找到父节点(前一个快照)c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
  4. 以此类推,显示当前分支的所有快照

最后,补充一点。前面说过,分支指针是动态的。原因在于,下面三个命令会自动改写分支指针。

  • git commit:当前分支指针移向新创建的快照。
  • git pull:当前分支与远程分支合并后,指针指向新创建的快照。
  • git reset [commit_sha]:当前分支指针重置为指定快照。

十、参考链接

(完)

文档信息

]]>
Git 是最流行的版本管理工具,也是程序员的必备技能之一。

即使天天使用它,很多人也未必了解它的原理。Git 为什么可以管理版本?git add、git commit这些基本命令,到底在做什么,你说得清楚吗?

这篇文章用一个实例,解释 Git 的运行过程,帮助你理解 Git 的原理。

一、初始化

首先,让我们创建一个项目目录,并进入该目录。

$ mkdir git-demo-project
$ cd git-demo-project

我们打算对该项目进行版本管理,第一件事就是使用git init命令,进行初始化。

$ git init

git init命令只做一件事,就是在项目根目录下创建一个.git子目录,用来保存版本信息。

$ ls .git

branches/
config
description
HEAD
hooks/
info/
objects/
refs/

上面命令显示,.git内部还有一些子目录,这里先不解释它们的含义。

二、保存对象

接下来,新建一个空文件test.txt。

$ touch test.txt

然后,把这个文件加入 Git 仓库,也就是为test.txt的当前内容创建一个副本。

$ git hash-object -w test.txt

e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

上面代码中,git hash-object命令把test.txt的当前内容压缩成二进制文件,存入 Git。压缩后的二进制文件,称为一个 Git 对象,保存在.git/objects目录。

这个命令还会计算当前内容的 SHA1 哈希值(长度40的字符串),作为该对象的文件名。下面看一下这个新生成的 Git 对象文件。

$ ls -R .git/objects

.git/objects/e6:
9de29bb2d1d6434b8b29ae775ad8c2e48c5391

上面代码可以看到,.git/objects下面多了一个子目录,目录名是哈希值的前2个字符,该子目录下面有一个文件,文件名是哈希值的后38个字符。

再看一下这个文件的内容。

$ cat .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391

上面代码输出的文件内容,都是一些二进制字符。你可能会问,test.txt是一个空文件,为什么会有内容?这是因为二进制对象里面还保存一些元数据。

如果想看该文件原始的文本内容,要用git cat-file命令。

$ git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

因为原始文件是空文件,所以上面的命令什么也看不到。现在向test.txt写入一些内容。

$ echo 'hello world' > test.txt

因为文件内容已经改变,需要将它再次保存成 Git 对象。

$ git hash-object -w test.txt

3b18e512dba79e4c8300dd08aeb37f8e728b8dad

上面代码可以看到,随着内容改变,test.txt的哈希值已经变了。同时,新文件.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad也已经生成了。现在可以看到文件内容了。

$ git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad

hello world

三、暂存区

文件保存成二进制对象以后,还需要通知 Git 哪些文件发生了变动。所有变动的文件,Git 都记录在一个区域,叫做"暂存区"(英文叫做 index 或者 stage)。等到变动告一段落,再统一把暂存区里面的文件写入正式的版本历史。

git update-index命令用于在暂存区记录一个发生变动的文件。

$ git update-index --add --cacheinfo 100644 \
3b18e512dba79e4c8300dd08aeb37f8e728b8dad test.txt

上面命令向暂存区写入文件名test.txt、二进制对象名(哈希值)和文件权限。

git ls-files命令可以显示暂存区当前的内容。

$ git ls-files --stage

100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0   test.txt

上面代码表示,暂存区现在只有一个文件test.txt,以及它的二进制对象名和权限。知道了二进制对象名,就可以在.git/objects子目录里面读出这个文件的内容。

git status命令会产生更可读的结果。

$ git status

要提交的变更:
    新文件:   test.txt

上面代码表示,暂存区里面只有一个新文件test.txt,等待写入历史。

四、git add 命令

上面两步(保存对象和更新暂存区),如果每个文件都做一遍,那是很麻烦的。Git 提供了git add命令简化操作。

$ git add --all

上面命令相当于,对当前项目所有变动的文件,执行前面的两步操作。

五、commit 的概念

暂存区保留本次变动的文件信息,等到修改了差不多了,就要把这些信息写入历史,这就相当于生成了当前项目的一个快照(snapshot)。

项目的历史就是由不同时点的快照构成。Git 可以将项目恢复到任意一个快照。快照在 Git 里面有一个专门名词,叫做 commit,生成快照又称为完成一次提交。

下文所有提到"快照"的地方,指的就是 commit。

六、完成提交

首先,设置一下用户名和 Email,保存快照的时候,会记录是谁提交的。

$ git config user.name "用户名" 
$ git config user.email "Email 地址"

接下来,要保存当前的目录结构。前面保存对象的时候,只是保存单个文件,并没有记录文件之间的目录关系(哪个文件在哪里)。

git write-tree命令用来将当前的目录结构,生成一个 Git 对象。

$ git write-tree

c3b8bb102afeca86037d5b5dd89ceeb0090eae9d

上面代码中,目录结构也是作为二进制对象保存的,也保存在.git/objects目录里面,对象名就是哈希值。

让我们看一下这个文件的内容。

$ git cat-file -p c3b8bb102afeca86037d5b5dd89ceeb0090eae9d

100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    test.txt

可以看到,当前的目录里面只有一个test.txt文件。

所谓快照,就是保存当前的目录结构,以及每个文件对应的二进制对象。上一个操作,目录结构已经保存好了,现在需要将这个目录结构与一些元数据一起写入版本历史。

git commit-tree命令用于将目录树对象写入版本历史。

$ echo "first commit" | git commit-tree c3b8bb102afeca86037d5b5dd89ceeb0090eae9d

c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

上面代码中,提交的时候需要有提交说明,echo "first commit"就是给出提交说明。然后,git commit-tree命令将元数据和目录树,一起生成一个 Git 对象。现在,看一下这个对象的内容。

$ git cat-file -p c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

tree c3b8bb102afeca86037d5b5dd89ceeb0090eae9d
author ruanyf  1538889134 +0800
committer ruanyf  1538889134 +0800

first commit

上面代码中,输出结果的第一行是本次快照对应的目录树对象(tree),第二行和第三行是作者和提交人信息,最后是提交说明。

git log命令也可以用来查看某个快照信息。

$ git log --stat c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

commit c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
Author: ruanyf 
Date:   Sun Oct 7 13:12:14 2018 +0800

    first commit

 test.txt | 1 +
 1 file changed, 1 insertion(+)

七、git commit 命令

Git 提供了git commit命令,简化提交操作。保存进暂存区以后,只要git commit一个命令,就同时提交目录结构和说明,生成快照。

$ git commit -m "first commit"

此外,还有两个命令也很有用。

git checkout命令用于切换到某个快照。

$ git checkout c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

git show命令用于展示某个快照的所有代码变动。

$ git show c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

八、branch 的概念

到了这一步,还没完。如果这时用git log命令查看整个版本历史,你看不到新生成的快照。

$ git log

上面命令没有任何输出,这是为什么呢?快照明明已经写入历史了。

原来git log命令只显示当前分支的变动,虽然我们前面已经提交了快照,但是还没有记录这个快照属于哪个分支。

所谓分支(branch)就是指向某个快照的指针,分支名就是指针名。哈希值是无法记忆的,分支使得用户可以为快照起别名。而且,分支会自动更新,如果当前分支有新的快照,指针就会自动指向它。比如,master 分支就是有一个叫做 master 指针,它指向的快照就是 master 分支的当前快照。

用户可以对任意快照新建指针。比如,新建一个 fix-typo 分支,就是创建一个叫做 fix-typo 的指针,指向某个快照。所以,Git 新建分支特别容易,成本极低。

Git 有一个特殊指针HEAD, 总是指向当前分支的最近一次快照。另外,Git 还提供简写方式,HEAD^指向HEAD的前一个快照(父节点),HEAD~6则是HEAD之前的第6个快照。

每一个分支指针都是一个文本文件,保存在.git/refs/heads/目录,该文件的内容就是它所指向的快照的二进制对象名(哈希值)。

九、更新分支

下面演示更新分支是怎么回事。首先,修改一下test.txt。

$ echo "hello world again" > test.txt

然后,保存二进制对象。

$ git hash-object -w test.txt

c90c5155ccd6661aed956510f5bd57828eec9ddb

接着,将这个对象写入暂存区,并保存目录结构。

$ git update-index test.txt
$ git write-tree

1552fd52bc14497c11313aa91547255c95728f37

最后,提交目录结构,生成一个快照。

$ echo "second commit" | git commit-tree 1552fd52bc14497c11313aa91547255c95728f37 -p c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

785f188674ef3c6ddc5b516307884e1d551f53ca

上面代码中,git commit-tree的-p参数用来指定父节点,也就是本次快照所基于的快照。

现在,我们把本次快照的哈希值,写入.git/refs/heads/master文件,这样就使得master指针指向这个快照。

$ echo 785f188674ef3c6ddc5b516307884e1d551f53ca > .git/refs/heads/master

现在,git log就可以看到两个快照了。

$ git log

commit 785f188674ef3c6ddc5b516307884e1d551f53ca (HEAD -> master)
Author: ruanyf 
Date:   Sun Oct 7 13:38:00 2018 +0800

    second commit

commit c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
Author: ruanyf 
Date:   Sun Oct 7 13:12:14 2018 +0800

    first commit

git log的运行过程是这样的:

  1. 查找HEAD指针对应的分支,本例是master
  2. 找到master指针指向的快照,本例是785f188674ef3c6ddc5b516307884e1d551f53ca
  3. 找到父节点(前一个快照)c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
  4. 以此类推,显示当前分支的所有快照

最后,补充一点。前面说过,分支指针是动态的。原因在于,下面三个命令会自动改写分支指针。

  • git commit:当前分支指针移向新创建的快照。
  • git pull:当前分支与远程分支合并后,指针指向新创建的快照。
  • git reset [commit_sha]:当前分支指针重置为指定快照。

十、参考链接

(完)

文档信息

]]>
0