SinoSky
SinoSky

学习 记录 分享 发现


  • 首页

  • 关于

  • 分类

  • 标签

  • 归档

  • 搜索
close

<译> MongoDB 与 MySQL 对比

发表于 2016-09-29   |   分类于 码农与 IT   |  

原文:MongoDB and MySQL Compared

概述

关系数据库在企业级应用中使用已有数十年的历史,自从 MySQL 在 1995 年发布之后,它成为其中最受欢迎且成本较低的选择之一。然而,随着近年来数据爆炸及储存成本降低,像 MongoDB 这样的非关系数据库开始出现,以解决新型应用的需求。对于传统应用,MongoDB 同样能增强甚至是替代已有的关系数据库。

MySQL 是什么?

MySQL 是一个广受欢迎的关系数据库管理系统(RDBMS),由甲骨文公司开发、发行和维护,开放源代码。如同其他的关系数据库,MySQL 在表中存储数据、使用结构化查询语言(SQL)访问数据。在 MySQL 中,你需要根据需求提前定义表结构、处理在不同表中的不同字段间的关系。相关联的数据可能存储在不同表中,但可通过多表连接查询访问数据。使用这种存储方式,重复数据是最少的。

MongoDB 是什么?

MongoDB 是由 MongoDB 公司开发的开源数据库,它使用类 JSON 文档存储数据,数据结构可变。关联信息同样存储在一起,通过 MongoDB 查询语句可快速访问。MongoDB 使用动态结构,这意味着你无需提前定义数据结构,就可以创建一条记录来存储像字段值或字段类型等信息。你可以通过新增或删除字段来改变记录(我们称之为文档)的结构。这种数据模型可以表示层次关系、存储数组或其他更复杂的数据结构。在集合中的文档无须拥有一致的字段,非规范化的数据是很常见的。MongoDB 在设计时还考虑到了高可用性和可扩展性,包括开箱即用的复制和自动分片。

术语及概念

MySQL 的许多概念在 MongoDB 中都有类似的,下表列出了一些常见的概念。

MySQL MongoDB
Table 表 Collection 集合
Row 行 Document 文档
Column 列 Field 字段
Joins 连接 Embedded documents, linking 内嵌文档,连接

功能对比

如 MySQL 一样,MongoDB 提供了丰富的特性和功能,远远超出了简单的键值存储所能提供的。MongoDB 拥有查询语言、高度实用的二级索引(包括文本搜索和空间数据)、用于数据分析的强大聚合框架等功能。通过使用 MongoDB 的这些功能,你可以访问更多不同的数据类型和更大规模的数据。

MySQL MongoDB
丰富的数据模型 无 有
可变的数据结构 无 有
强类型数据 有 有
局部数据 无 有
字段更新 有 有
易编程 无 有
复杂的事务 有 无
操作审计 有 有
自动分片 无 有

查询语言

MySQL 和 MongoDB 都有丰富的查询语言,一份详细的列表可在 MongoDB 文档中找到。

MySQL MonogDB
INSERT INTO users (user_id, age, status) VALUES ('bcd001', 45, 'A') db.users.insert({user_id: 'bcd001', age: 45, status: 'A'})
SELECT * FROM users db.users.find()
UPDATE users SET status = 'C' WHERE age > 25 db.users.update({ age: { $gt: 25 } }, { $set: { status: 'C' } }, { multi: true })

为什么使用 MongoDB 而不是 MySQL?

使用 MongoDB 可以更快得构建应用,处理多种多样的数据类型,更高效的管理大规模应用,不同规模的企业都可以使用 MongoDB。

MongoDB 文档映射很接近现代的、面向对象的编程语言,开发很简便。使用 MongoDB,可以省去在代码中使用复杂的对象关系映射(ORM)层来转换对象与相关的表。

MongoDB 灵活的数据模型意味着你可以根据业务发展的需要修改数据结构。例如,The Weather Channel 花费了数周修改 MySQL 的数据库结构,如果使用 MongoDB 则只需花费几小时。

MongoDB 还支持在多个分布式数据中心间伸缩,提供高可用性和可扩展性,像 MySQL 这样的关系数据库无法做到。当数据量和吞吐量增长时,MongoDB 无须停机即可轻松扩展,也不需要修改程序。相较之下,扩展 MySQL 则需要更多自定义工作。百度从 MySQL 迁移到 MongoDB 以支持快速增长的业务,作为中国互联网服务的巨头,其使用 MongoDB 集群管理超过 1PB 的数据,为 100 多个应用提供服务。

MongoDB 的常见使用场景有哪些?

MongoDB 作为一个通用数据库,可以用于各种场景,最常见的场景包括单页面应用、物联网、移动应用、实时数据分析、个性化服务、产品目录和内容管理等。

什么时候更适合使用 MySQL?

虽然现代应用需要像 MongoDB 这样灵活的、可伸缩的系统,但仍有很多场景需要像 MySQL 这样的关系数据库。比如,需要复杂的事务、多行查找的应用(如复式簿记系统)就是一个很好的列子。MongoDB 无法简单替代在关系数据模型和 SQL 上构建的传统应用。

一个具体的例子是运行在旅行预订系统后的订单引擎,这通常涉及到复杂的交易。虽然核心预订系统可以运行在 MySQL 上,但与用户紧密相关的部分——内容提供、社交网络、会话管理最好放在 MongoDB 中。

MongoDB 和 MySQL 可以一起使用吗?

有很多在开发中同时使用 MongoDB 和 MySQL 的例子。在一些情况下,在工作中选择一个正确的工具是非常重要的。比如,许多电子商务应用混合使用 MongoDB 和 MySQL。而拥有很多不同属性产品的产品目录,MongoDB 的灵活数据模型更加适合。另一方面,像结单系统这样拥有复杂事务的系统,最好使用 MySQL 或其他关系数据库。

在其他情况下,新的业务要求企业使用 MongoDB 作为他们应用的下一代组件。例如,世界领先的交易管理软件和服务提供商之一——Sage 集团,把 MongoDB 集成进其广受中型公司欢迎的企业资源计划(ERP)解决方案中。Sage 的客户现在能享有功能齐备且高度可定制化的服务。尽管许多 Sage 的产品构建于 MySQL 之上且仍在 MySQL 上运行,但围绕用户体验的新功能是运行在 MongoDB 之上的。

除了这些少数例外,我们相信 MongoDB 是一个比 MySQL 更好的选择,因为 MongoDB有灵活的数据模型和可扩展的架构。

想要了解更多?快来下载《RDBMS 到 MongoDB 迁移指南》

关系数据库有许多限制,因为它们是为运行现今的应用而开发的,再加上数据和用户的增长,已无法满足需求。为了应对这些挑战,像音乐电视网和思科这样的公司,已经成功从关系数据库迁移到了 MongoDB。在这本白皮书中,你可以了解到:

  • 如何一步步从关系数据库迁移到 MongoDB。
  • 相关技术方面的考虑,如关系数据模型与文档数据模型的结构设计间有何差异。
  • 索引、查询、应用整合和数据迁移。

下载白皮书

译后感:译完才发现,我又找回了当年翻译 meteor 时的感觉——理解不能……算了,都写完了,就这样吧……

浅谈设计模式和面向重构编程

发表于 2016-09-28   |   分类于 程序员   |  

这两天面试被问到了什么是“设计模式”,作为野路子码农,我不知道什么是设计模式,只知道 MVC 等软件架构,既然被问到了,那就了解一下吧。看了几个模式后我发现,其实在平时写代码的时候已经用到过部分模式了,只是当时并不知道而已。

关于设计模式,已经有各种专业书籍和文章了,我就不在这里献丑了,只谈谈对于“什么时候应该学习设计模式”的看法。

初学编程你根本不需要知道什么设计模式,甚至连 OOP 是什么都不需要知道,写个 hello world 都要了解什么是设计模式,简直是本末倒置。作为程序员,你当然需要知道什么是设计模式,但是当你连怎么写一个通用框架都不知道的时候,甚至是连没有隐藏 bug 的代码都写不出来的时候,又何必去背诵设计模式的定义呢?当你已经用过各种主流框架,看过其代码,即便是不知道什么是设计模式,一个框架运行原理也能大致清楚的时候,才应该去学习设计模式,因为这时候,你已经不需要去背各种设计模式的定义,也能写出符合某一设计模式的代码了。多写、多读代码是学习设计模式的最好方式了。

因为刚接触编程的时候在数据类型上吃过亏,年初去华大基因给冬令营的学生讲 Python 的时候,写完 hello world 我就跟学生讲起了 Python 的数据类型,然而台下的学生并没有什么兴趣,最后我发现我竟然走了应试教育的老路,写个 hello world 需要知道什么是数据类型吗?写个 hello world 需要用到类型转换吗?学习编程,最好的方式是“站在巨人的肩膀上”,先去抄个 hello world,然后改改别人的代码,再造几个轮子,慢慢地你就能靠编程吃饭了。当然理论和算法也很重要,但回过头来再补也无妨。

我个人不喜欢用很重的框架,这些框架是能极大得提高开发效率,比如 Django 自带一个管理后台,直接调用就能省去写后台的麻烦,但这些框架也带来了一些我不需要的功能,限制了我写代码的方式。所以在个人项目中,我更喜欢用一些很轻的框架,自己实现想要的功能,也能提高自己的设计能力。当然在工作中我不会这么做,毕竟完成工作任务永远是第一位的。

虽然我是野生码农,但也写了三年代码,一般需求已经不需要想怎么去构建代码了。对于复杂的逻辑,如果一时想不出更优雅的实现方式,我会先完成需求,然后过一段时间再来看,说不定就有好方法了(也说不定需求改了或者 PM 把功能砍了 :P)。大部分时候,重构代码并不能提升程序运行速度、提高用户体验,反而容易出现新的 bug。但是作为程序员,重构代码使其更优雅是职业素养,无关用户体验。

前些日子我花时间简单研究了一下编码问题,其实大部分时候你根本无需关心 ASCII 跟 Unicode 有什么区别,只知道用 UTF-8 就好了,只有真的遇到编码问题的时候才需要去了解它。我碰到几家公司笔试或面试时问 HTTP status code,但估计我说 RFC 2616(现已被 RFC 7230——RFC 7237 替换),大部分出题的估计都不知道是啥。人人都有不了解的知识,像我面试时如果扯什么(非)对称加密、编码问题,甚至是生信相关(当然我不敢在生物专业的人面前扯,生信领域我也是半吊子)的话题,大部分面试官也只能点头,但我会尽量避免扯这些东西,因为公司招我是来干活、解决问题的,在工作不相关的领域有多深入又有何意义呢?所以面试时我尽量实话实说,问啥答啥,说些跟工作内容有关的东西。面试不过短短几十分钟的时间,很难全面地了解一个人的技术水平,所以“Talk is cheap. Show me the code.”。

不忘初心,方得始终——写在开博四周年之际

发表于 2016-09-23   |   分类于 杂文   |  

其实究竟什么时间开始写博客的,我也记不清了,只是前几天查到 sinosky.org 这个域名是四年前的今天注册的,便打算写些东西以作纪念。

前两天在 V2EX 发了一求职帖,没啥效果,有人说我简历没有写清楚“做过什么”。说实话,我真不会写简历,没做过什么牛逼的事情,吹逼也不会,写详细了又担心泄漏公司业务(后来发现其实根本没人关心)。那我这两年在究竟工作中做了什么?总结一下,四个字——CRUD,写各种业务逻辑、实现各种需求罢了。要是能参与到项目中去倒也无妨,可经常是设计稿都出来了,我才知道这个项目,最后变成看原型图和设计稿写代码,毫无成就感。当然,这也跟我没有积极主动地去参与有关。

最初学编程仅仅是出于对计算机的热爱,那时候也不懂什么互联网,只觉得搞个网站挺酷的,便自己搭了一个博客,还邀请同学一块写。从来没想过赚钱,更没想过以此谋生。幸而遇到现任老板,几封邮件便把我招至深圳。虽感谢知遇之恩,但也发现公司有很多问题。离职的想法,其实在公司转型之初就有了,不然也不会去面试。当时对于基因检测这个行业毫无概念,再加上刚工作有了点闲钱,便想涨工资,以买更多以前学生时代买不起的东西。只是当时眼高手低,准备不足,并没有找到一份好工作。

说来惭愧,从接触编程到现在,四年有余,竟从未独立写过一个功能完备的网站。至于原因,一是不想造个简单的轮子,二是懒。这两年用过很多框架,可框架终究只是个工具,说白了,人搞出来的东西,没几年就过时了。像我以前认真研究过 init scripts 的写法,还写了一些脚本分享。可自从各大发行版换用 systemd 以后,别说 init scripts,就连 shell 我都没怎么写过。靠着一两个热门的框架,短期内是能找到一份好工作,可互联网日新月异,早晚会被淘汰,这也是我为什么不喜欢在简历上写我熟悉哪些框架的原因。

那什么不会过时呢?基础理论知识、算法、快速解决问题的能力、对未知事物的好奇心,可惜这两年光顾着鼓捣各种工具和框架了,忽视了理论和算法的学习。其实我并不太喜欢运维工作,日常运维不过是用各种工具而已,全靠经验积累,很难系统学习,不同发行版、不同系统或软件版本上的运维工作都有所不同,只是平常要折腾自己的服务器,才有所了解。其实算法我也不是一点也不会,只是平常写得少,也都是基础的算法,面试的时候要求手写就尴尬了。理论知识也是如此,不可能一点概念都没有,但真要问起,只能说个大概。

最近一年越来越觉得工作没劲,一方面参与不到公司的项目中去,另一方面日常工作又很难接触到新技术,工资涨幅也不能令我满意。如果即不能做想做的事,也不能在技术上有什么突破、创新,还挣不到钱的话,那还不如回家种地。(哎,咋又忘了我家没地呢……)中秋假期前,老板的一句玩笑话,使我意识到不能再这样下去了。提出离职后,老板极力挽留,这是让我不曾想到的。虽然老板承诺加薪,但是经过再三考虑,我觉得,对于做喜欢的事还是做赚钱的事,我更愿意选择前者,所以就有了 V 站的帖子。

在博客中写一些个人感悟,不是为了给谁看,也不是为了博取同情,仅仅是想把一些想法记录下来,就像日记一样。至于有没有人看,看完有何评价,倒是无所谓了。罢了,写这么多反正也没卵用,我还是去改简历吧。

孙岳的简历

发表于 2016-09-19   |   分类于 杂文   |  

基本资料

姓名 性别 年龄 工作经验 求职方向 现居
孙岳 男 22 岁 2.5 年 web 后端经验 PHP/Python 深圳

个人博客: https://www.sinosky.org

GitHub: https://github.com/jat001

邮箱: chat@jat.email

 

个人简介

  • 热爱技术,自学编程,喜欢折腾,学习能力强,乐于开源。
  • 拥有快速定位及解决问题的能力。
  • 善于面向 Google 和 Stack Overflow 编程。
  • 常年混迹于 GitHub,给不同类型的项目提过 issue 和 PR。
  • 乐于分享,善于总结,个人博客已写四年。
  • 代码洁癖,乐于重构代码使其更优雅。
  • 玩过各种类型的游戏,喜欢科幻和奇幻类小说。

 

工作&项目经验

深圳市微客互动有限公司 / 深圳市早知道科技有限公司

2014 年 5 月 —— 2016 年 9 月

WeCenter

类知乎的开源论坛程序的开发和面向企业用户的实施,在 GitHub 上已有 350 多个 stars。

工作职责:PHP 开发及运维

  • 社区系统功能开发,从数据表设计到后端逻辑开发,包括数据库的增删改查、前端数据交互 API 等。
  • 为企业用户实施部署 WeCenter,包括环境配置、Bug 修复及售后技术支持。
  • 推动项目开源至 GitHub,目前仍由我维护。

 

WeGene

公司转型之作,面向普通消费者的基因检测服务。

工作职责:PHP 开发、运维、爬虫等

  • 业务系统后端开发,在 WeCenter 的原有框架下大规模根据业务需求重新设计和编写后端服务。
  • 利用 Python 及 Scrapy 框架开发爬虫,抓取相关文献及数据库并解析入库。
  • 配合数据分析团队构建从分析计算到数据展示的自动化流程。主要工作内容包括数据交互 API 的开发以及使用 Celery 框架开发计算任务调度系统。
  • 利用 PHP 实现了部分生物数据分析的简单算法,如通过基因数据判定用户性别、对用户在演化树上的节点位置进行最优匹配等。
  • 本地及阿里云服务器的运维,利用 Python 开发了部分自动化运维脚本。
  • MySQL 数据库的性能优化、数据备份等日常操作。

 

离职原因

因公司行业(基因检测)和发展方向(生物信息)与个人发展方向(互联网相关)不同,在公司转型近两年后,决定离职。

 

技术栈

  • 语言:熟练使用 PHP 进行 Web 开发;熟练掌握 Python 及其常用框架。
  • 数据库:熟练掌握 MySQL,也在生产环境下使用过 MongoDB/Redis。
  • GNU/Linux:熟练使用各主流发行版,可直接在 Linux 服务器上开发。
  • 代码管理:熟练掌握 Git 及团队代码管理。

 

期望公司

  • 希望贵司是互联网相关行业。
  • 希望贵司研发团队有技术追求,在不影响公司业务、完成开发任务的前提下,乐于尝试新技术。
  • 希望贵司开发流程规范、文档齐全,最好有单元测试、持续集成等自动化流程,或乐于推动相关流程规范化。
  • 希望贵司尊重员工,没有加班文化。
  • 希望贵司位于南山或福田。

 

期望薪资

15K

 

领导评价

review

review

 

下载本简历的 PDF 版本 | 下载本简历的 Word 版本

字符编码的奥秘

发表于 2016-09-17   |   分类于 码农与 IT   |  

谨以本文献给所有被编码问题坑过的程序猿。

 

起因

前段时间整理散落在各处博文图片,有几个中文文件名的图片要上传,便像平常一样,用 Sublime SFTP 插件直接上传,没想到直接报错了。

SFTP error

我十分确定服务器上使用的编码是 UTF-8,配置文件也没有错,那到底是什么原因呢,只有看代码了。由于这个插件不开源,只好使用反编译工具 uncompyle6。

debug 过程省略……

最终我找到了引发 bug 的代码,为了方便说明,有修改。

1
2
local_encoding = locale.getpreferredencoding(do_setlocale=True)
remote_basename = str(basename.encode('utf-8'), local_encoding)

经过测试,无论是使用 chcp 65001 还是 set PYTHONIOENCODING=UTF-8 ,locale.getpreferredencoding() 始终返回 cp936……

所以,整理一下,最终出现问题的就是这段了:

1
basename.encode('utf-8').decode('cp936')

为什么使用 UTF-8 编码后的文件名再用 CP 936 解码会出错呢?本文就来简单介绍下字符编码的奥秘及其发展简史。

 

背景

有计算机基础的人可能都知道,文本以二进制形式在计算机中存储和通过通信网络传播的。那怎么把人类可读的文字,转换成机器可以存储和识别的信息呢,于是就有了字符编码。

 

字符(character)是一段文字的最小单位,它可能是汉字、阿拉伯数字、英文字母、日语假名、标点符号或其他有意义的内容。

字符集(character set)是许多字符的集合,它可能只包含一种语言(比如 ASCII),也可能包含多种语言(比如 Unicode)。

字符编码(character encoding)就是定义和实现把这些字符集的编码成二进制的方式。

很多字符集也定义了对这些字符的编码方案,比如 GB 2112,所以导致有些时候往往将两者混用,对于包含字符有限的简单字符集(一个字符只占用一字节)来说,也没有区分的必要。

 

ASCII

提到字符编码,就不得不说 ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)。

受电报代码(摩尔斯电码是其中最著名的)的启发,ANSI(American National Standards Institute,美国国家标准学会)的前身——ASA(American Standards Association,美国标准协会)在 1963 年发布了用于计算机及其他通信设备的字符编码标准—— ASCII 的第一版 ASA X3.4-1963。

后来,USASI(United States of America Standards Institute,美利坚合众国标准组织,由 ASA 重组而来)在 1967 年做了一次大更新(主要是控制字符),版本为 USAS X3.4-1967。

ASCII 的最后一次更新是在 1986 年,由 ANSI 发布了 ANSI INCITS 4-1986 (R2012)。

ASCII

ASCII 编码速见表,来自于一台 1972 年生产的打印机附带的手册

 

ASCII 是一个典型的简单字符集,只定义了 128 个字符,其中还有 33 个无法显示的控制字符,且大都早已弃用。所以,ASCII 只能用于显示现代英语中的 26 个字母、阿拉伯数字和部分标点符号,但它依旧是影响最深远的字符编码,至今大部分字符编码仍向下兼容 ASCII。

 

ISO/IEC

ISO(International Organization for Standardization,国际标准化组织)大家都知道,负责制定全世界工商业国际标准的机构,它联合 IEC(International Electrotechnical Commission, 国际电工委员会)制定了一系列字符集/编码标准。

 

其中最著名的是 ISO/IEC 8859 系列标准(15 个各语言的字符集),在 ASCII 的基础上,(最多)加入 96 个字符,使其能支持当地语言(主要是欧洲)的显示。

ISO/IEC 8859

15 个 ISO/IEC 8859 字符集

 

ISO/IEC 8859 这种给每种语言单独做一套字符集的做法显然不利于国际化,而且与像英语这种由数量有限的字母所组成的音素文字不同,汉语这种语素文字仅常用字就有数千字,简单字符集无法满足。

于是,ISO/IEC 10646,或称 UCS(Universal Coded Character Set,通用字符集)就应运而生。如今,ISO/IEC 8859 不再更新,曾经负责此标准开发的工作组已转而致力于 ISO/IEC 10646 的开发。由于现时的 UCS 与 Unicode 已无太大区别,这里不做过多介绍。

 

ISO/IEC 8859 和 ISO/IEC 10646 都是字符集,而非编码。由于前者是简单字符集,不需要特殊处理,所以一些软件直接使用 ISO/IEC 8859-X 作为字符编码的名字。另外,也没有单独实现后者的编码,曾经的 UCS-2(for 2-byte Universal Character Set)已被 UTF-16 所取代。

 

Unicode

当 ISO/IEC 在制定 UCS 的时候,由 Xerox、Apple 等软件制造商于 1988 年组成的统一码联盟(The Unicode Consortium)也在致力于开发一个单一字符集,即 Unicode。1991年前后,两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一字符集而协同工作。

现时,两个项目仍都独立存在,并独立地公布各自的标准。但双方都同意保持两种标准的兼容性,并紧密地共同调整任何未来的扩展。

如今的 Unicode 字符集大致等同于 ISO/IEC 10646,比如 ISO/IEC 10646:2014 plus Amendments 1 and 2,除缺少 Adlam、Newa(均为文字)、日本电视符号和 74 个 emoji 表情外,跟 Unicode 9.0 完全一致。

 

统一码联盟是一个非营利机构,任何公司、机构或个人只要交保护费会费就能加入。

Unicode Members

Unicode 会员

 

虽然理论上,Unicode 可以从 U+0000 一直定义到 U+FFFFFF(‭16,777,215‬ + 1),但是根据 RFC 3629, 只有 U+0000 到 U+10FFFF(1,114,111‬ + 1)才是可定义的。当然,这些已定义的 code points 还有很多是未赋值的。

尽管 UCS / Unicode 拥有超过一百万个 code points,但在 2000 年之前,大多数系统和软件仍然只支持前 65,536 个 code points,即 BMP(Basic Multilingual Plane,基本多文种平面,U+0000 至 U+FFFF)。直到 2006 年,同样包含一百多万个 code points 的 GB 18030 开始实施,强制要求所有在中国境内发售或使用的所有软件都要支持,这种状况才被改变。

Unicode BMP map

BMP map

 

Unicode 只是一套字符集,对其编码的实现则称为 UTF(Unicode Transformation Format,Unicode 转换格式)。使用最广泛的 UTF 是 UTF-8(8-bit Unicode Transformation Format)。

UTF-8 使用可变长度的编码,支持一到六字节,128 个 ASCII 字符编码后只占用一字节,汉字大都占用三字节,靠后的字符占用字节要比靠前的多。这样做有什么好处呢?举个简单的例子。

比如要存储小写字母 a,使用 UTF-16 来存储要占用两字节(UTF-16 BE: 00000000 01100001 UTF-16 LE: 01100001 00000000),且总会有一个字节始终为 0x00,这显然很浪费,而用 UTF-8 来存储就只需占用一字节(01100001)。如果只是存储或传输类似一封以现代英语写成的信件的话,UTF-8 就比其他 UTF 更高效。但如果是极少使用的、位于 U+10000 至 U+10FFFF 的字符,使用 UTF-8 编码后就要占用四字节了。

 

另一种 UTF——UTF-16 还涉及到字节序(Endianness)的问题,即把最低有效位放在最高有效位的前面——小端序(little-endian,LE),还是把最低有效位放在最高有效位的后面——大端序(big-endian,BE)。

 

“endian”一词来源于乔纳森·斯威夫特的小说《格列佛游记》。小说中,小人国为水煮蛋该从大的一端(Big-End)剥开还是小的一端(Little-End)剥开而争论,争论的双方分别被称为“大端派”和“小端派”。

我下面要告诉你的是,Lilliput 和 Blefuscu 这两大强国在过去36个月里一直在苦战。战争开始是由于以下的原因:我们大家都认为,吃鸡蛋前,原始的方法是打破鸡蛋较大的一端,可是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了。因此他的父亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。老百姓们对这项命令极其反感。历史告诉我们,由此曾经发生过 6 次叛乱,其中一个皇帝送了命,另一个丢了王位。这些叛乱大多都是由 Blefuscu 的国王大臣们煽动起来的。叛乱平息后,流亡的人总是逃到那个帝国去寻求避难。据估计,先后几次有 11000 人情愿受死也不肯去打破鸡蛋较小的一端。关于这一争端,曾出版过几百本大部著作,不过大端派的书一直是受禁的,法律也规定该派任何人不得做官。

1980 年, Danny Cohen——一位网络协议的早期开发者,在其著名的论文 On Holy Wars and a Plea for Peace 中,为平息一场关于字节该以什么样的顺序传送的争论,而第一次引用了该词。

big-endian and little-endian

大端序与小端序

 

由于字节序的存在,保存文件或传输字符串流的时候,就有必要说明是以什么字节序保存或传输的,BOM(byte-order mark,字节序标记)就是在文件或字符串流开始,用于标识字节序的第一个字符。

UTF-8 没有字节序的问题,UTF-8 with BOM 是微软自己搞出来的,其中的 BOM 仅仅是为了标识该文件使用 UTF-8 编码,某些文本编辑器可能不支持。

BOM by Encoding

不同编码的 BOM

 

CJK

受汉语的影响,很多东亚语言都有汉字,但受到本土语言的发展、书写方式的不同、甚至是错别字以讹传讹等影响,各国现时使用的汉字有所出入。各国虽有自己的汉字编码规范,但互不兼容。

为了整合、统一这些汉字,ISO/IEC 和 Unicode 成立了表意文字小组(The Ideographic Rapporteur Group),负责整理“起源相同、本义相同、形状一样或稍异的表意文字”,也就有了中日韩统一表意文字(CJK Unified Ideographs),后来越南文中的喃字也加入了此计划,就合称为中日韩越(CJKV)统一表意文字,可是谁叫你来得晚呢,现在我们仍习惯使用前者。

CJKV variant glyphs

“次”的五种写法

 

CJK 或 CJKV 实际上是指中日韩(越)现时使用的相同或相近的汉字或其他字符,并非“中日韩统一表意文字”的简称,一些中文资料把 CJK 直接翻译成“中日韩统一表意文字”显然是错误的,而且 Unicode 中不仅仅只包含中日韩统一表意文字,还有其他的 CJK 字符,比如中日韩符号和标点(CJK Symbols and Punctuation)、中日韩笔画(CJK Strokes)等。

 

GB

说完了国际标准,再来说说国家标准(GB)。GB 2312、GBK 和 GB 18030 是最常见的三个字符集的国家标准,估计很多程序员都分不清它们的关系,我就按照时间顺序来简单介绍下。

 

GB 2312-80 是中华人民共和国制定的第一套关于简体中文字符集和字符编码的国家标准,全称《信息交换用汉字编码字符集·基本集》,1981 年 5 月 1 日实施。只收录 6763 个汉字和包括拉丁字母、希腊字母、日文平假名及片假名、俄语西里尔字母在内的 682 个字符,基本满足了日常需要。虽已被废弃,但仍有许多软件可以支持。

GBK 全名为《汉字内码扩展规范(GBK)》1.0版,1995 年 12 月制定和公布,属于“技术规范指导性文件”,不属于国家标准。由于 GB 2312-80 收录汉字数量有限,部分简化字、人名(如前总理朱镕基的“镕”字,其实这才是关键)、繁体字及日韩所用汉字并未收录。于是微软利用 GB 2312-80 未使用的编码空间,收录 GB 13000.1-93(等同于 ISO/IEC 10646.1:1993 和 Unicode 1.1)全部字符(共 20,902 个汉字)制定了 GBK。

GBK encoding

GBK 编码方式

GB 18030-2005 全称《信息技术 中文编码字符集》,是中华人民共和国现时最新的字符集国家标准,2006 年 5 月 1 日实施,与 GB 2312-80 完全兼容,与 GBK 基本兼容,共收录 70,244 个简繁、日韩汉字和少数民族文字。与 UTF-8 一样都采用可变长度的编码,编码后占用 一、二或四字节。GB 18030-2005 内的单、双字节编码部分,和四字节编码部分收录的中日韩统一表意文字扩展 A 区汉字,为强制性标准,其他部分则属于规范性标准。在中华人民共和国境内发售或使用的所有软件,都需要支持这个同时包含一、二和四字节的编码方式。

GB 18030 encoding

GB 18030 与 Unicode

虽然 GB 18030-2005 已推行多年,但是直到现在,简体中文版 Windows 上 non-Unicode 程序的默认编码仍然是 GBK(CP 936)。

 

除了大陆政府制定的汉字字符集/编码标准,同时期也有很多汉字编码标准。比如 Big5,流行于港澳台地区等繁体中文地区,但非官方标准,只是业界标准,为繁体中文版 Windows 上 non-Unicode 程序的默认编码(CP 950)。流行于日本地区的 Shift_JIS(CP 932)包含了日语汉字,后来被收录进中日韩统一表意文字。

 

最早的汉字编码是中文电码(Chinese telegraph code)。1873年,法国驻华人员威基杰参照《康熙字典》的部首排列方法,挑选了常用汉字6800多个,编成了第一部汉字电码本《电报新书》。中文电码表采用了四位阿拉伯数字作代号,从 0001 到 9999 按四位数顺序排列,用四位数字表示最多一万个汉字、字母和符号。

 

Windows Code Pages

除了 UTF-8、UTF-16 等通用字符编码,还有很多区域性的字符编码。Windows 作为一个在全球发售的操作系统,自然也要支持,于是就有了 Windows code pages(Windows 代码页)——一份 MicroSoft Windows 所兼容的字符编码列表,及其在 Windows 中的别名。

因为微软最初是给 IBM 做 MS-DOS(其 IBM PC 专用版本为 PC-DOS,两者差异不大)系统起家的,所以 Windows code pages 实际上是继承于 IBM PC / DOS (OEM) code pages,包括 CP 936。

 

Windows code pages 微软以前称之为 ANSI code pages,为什么改名呢?

Windows code pages 中的第一个(非继承自 OEM code pages)——1252(Windows West European Latin 1)确实是根据 ANSI 的草案(即后来成为 ISO 标准的 ISO/IEC 8859-1,更为人所知的别名是 Latin-1 或“西欧语言”)制定的,但是后面的 code pages 就不是了。用了一段时间后,微软才发现自己用错术语,于是就改名了。可惜用得太久了,很多地方仍保留着错误的旧名字。至今,如果你打开记事本、选择另存为仍能看到 ANSI。

ASNI on Windows

另存为“ANSI”编码

 

Windows NT 内核虽早已支持 Unicode,但 CMD(命令提示符)和 PowerShell 并不(完全)支持,默认字符集不是 Unicode。

Change the default charset on Windows

修改 Windows 上 non-Unicode 程序的默认编码

 

前面我提到过,CP 936 是简体中文版 Windows 上 non-Unicode 程序的默认编码,其最初版本和 GB 2312 一模一样,后来在推出 Windows 95 时增加了绝大部分 GBK 中的字符。

虽然 CP 936 通常被视为等同于 GBK,连 IANA(Internet Assigned Numbers Authority,互联网号码分配局)也视 CP936 为 GBK 的别名。但相比较一下,GBK 比 CP 936 多定义了 95 个字符(15 个非汉字及 80 个汉字),都是当时未收录进 UCS / Unicode 的字符。虽然新版 Unicode 早已收录这些字符,但 CP 936 至今未修订。

GB 18030 所对应的 Code Page 为 54936,从 Windows XP 起开始加入,但实际并未完全支持。不过由于 GB 18030 向下兼容 GBK,所以大部分情况下并无问题。至于原因,北大中文论坛有相关讨论。

 

Python Standard Encodings

Python 作为一个跨平台的语言,自然也要维护一套自己支持的编码列表,以适应各种(非)官方的别名及 code pages,于是就有了下表。

Python Standard Encodings

Python Standard Encodings(节选)

其中 Unified Chinese 应该是 Python 作者自己搞出来的名字,我搜索不到任何结果,就字面意思来看,应该是包含简繁的中文编码。

Python 支持 GB 18030,更具体点是 GB 18030-2000,但不支持所对应的 Windows Code Page 54936。

 

结语

最后,当我们回过头来看最初的问题,答案就很简单了。

因为作者使用命令(CMD)而不是 API 调用第三方软件,但 CMD 默认字符集不是 Unicode,而且远端的服务器也不一定是用 Unicode 编码,所以作者想出了这么蛋疼的方法去转码,以保证文件名不会出现乱码。

由于很多编码都向下兼容于 ASCII(128 个 ASCII 字符编码一致,包括 UTF-8、GBK 和 GB 18030 等),所以如果是仅仅使用编码后仅占用单字节的现代英语作为文件名,很难发现这个问题,结果这个 bug 就隐藏了这么久。

 

那么在服务端仍使用 UTF-8 的情况下,我们用向下兼容于 GBK、code range 又与 Unicode 相似的 GB 18030 替换 GBK 能解决这个问题吗?

这是一道陷阱题,回答“可以”的同学请回顾前面关于 UTF-8 和 GB 18030 的相关内容。code range 指的是字符集所能定义的范围,对比对象是 GB 18030 与 Unicode 这两个字符集,跟 UTF-8 这个字符编码没有关系。

UTF-8 支持一到八字节,而 GB 10830 只支持 一、二、四字节,还要考虑每个字节的范围,肯定不兼容。汉字使用 UTF-8 编码后,大都占用三字节,比如“的”字(U+7684),编码后的 hex 为 E7 9A 84。

 

You should NEVER EVER decode an encoded string by using an unmatched encoding.

 

虽然 Unicode 在某些字符上有错误,很多生僻字甚至是小语种都没有收录,但它是目前在全世界范围内最通用的字符集,而且还在不断更新(最新一版是 2016年6月21日 发布的 9.0.0),UTF-8 则是其最受欢迎的一种编码实现方式。所以我的看法是:在没有特殊需求的情况下,

请使用 UTF-8 (without BOM)!

 

相关资料

  • 相关中英文维基百科,链接太多,不一一放出
  • Unicode 9.0 Character Code Charts
  • Unicode Frequently Asked Questions
  • Unicode character table
  • MicroSoft Code Page Identifiers
  • Windows Best Fit
  • MSDN - Glossary of Terms Used on this Site
  • Python Standard Encodings

 

让 CMD 默认使用 UTF-8 编码

发表于 2016-09-12   |   分类于 小技巧   |  

使用 Windows 开发的小伙伴们(不要问我为什么不用 Mac……)应该都知道,中文版 Windows 上的 CMD 默认使用 cp936(你用记事本另存为 ANSI 编码其实跟这个编码是一样的),根据巨硬的定义(其实是 IBM 定的,有兴趣的可以去看维基百科),它代表 gb2312(话说巨硬自己都知道应该使用 Unicode,为什么还不改 CMD 的默认编码……)。

可能有些小伙伴也知道,使用 CHCP 命令可以查看和修改当前的 code page,但是每次打开 CMD 都打一遍命令实在是太麻烦了,有什么更好的办法呢?还真有。

HKEY_CURRENT_USER\SOFTWARE\Microsoft\Command Processor\Autorun(当前用户)或 HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor\Autorun (全局)可以控制每次打开 CMD 运行的命令或批处理文件,跟 Unix 里的 ~/.bashrc 作用一致。我们只需要把它设置成批处理文件的路径(记得用 \\ 而不是 \)或一行命令就好了。

让 CMD 默认使用 UTF-8 编码

让 CMD 默认使用 UTF-8 编码

实时性能监测工具 netdata 简介

发表于 2016-09-06   |   分类于 应用软件   |  

作为半个运维,我用过和了解过很多基于 web 的服务器监控软件,但很多软件要么配置复杂、文档不全(以各种“企业级”监控为代表),要么对用户不友好、画出来的图很难看(以各种开源软件为代表),要么资源占用比服务器上的其他软件还高(各种全家桶)。

直到我遇到了 netdata,才发现原来开源的监控软件也能做得这么好看,而且是 ajax 实时渲染的,比那些隔一会儿刷个图片的软件不知道高到哪里去了。

netdata

安装

如果你像我一样在使用 Arch Linux 或 Gentoo Linux,官方源里已经有该软件了,直接使用包管理器安装即可。

如果你在用其他 Linux 发行版,官方提供了一键安装脚本(相信我,这跟那些坑爹的 lnmp 的一键安装脚本不一样,自从用过那些脚本后,我再也不相信“一键”了……)和一键依赖安装脚本(如果你懒到连 apt-get 或 yum 都不愿意用的话),你只需要使用 git clone 整个仓库然后运行安装脚本就好了。

升级也很方便,只要 git update 然后再重新运行安装脚本就好了,当然要记得把你的配置文件放到安装目录外(只是出于安全考虑,脚本不会覆盖已有的配置文件)。

除了安装文档外,官方还提供了其他的使用说明,如果是正常使用,我相信你不会需要其他文档了。面向 stackoverflow 运维的日子一去不复返了,再也不用因为各种稀奇古怪的问题而到处 google 了(曾经用过某软件,跑在 web server 后面竟然获取不到当前目录,害我折腾了半天才解决)。

配置

netdata 几乎可以开箱即用,即使像我这样的强迫症患者,也没写几行配置文件。

真实时

得益于 RESTful API 和 ajax 实时渲染,netdata 可以实现真正的“实时”监控。web 开发者也可以利用这些 API 来制作自己的监控页面。

netdata

低占用

netdata 所展示的所有数据,全部来自于 Linux 系统内置的统计信息,所以几乎没有 Disk I/O。

插件

官方自带了很多软件的监控插件,只需要简单配置即可使用。可能是因为自带的插件已经能满足大部分需求,第三方插件并不多。

告警

作为一个监控软件,netdata 的告警功能在最近几个 commits 已经越来越完善了,不过所有的设置都要通过配置文件来完成,不太方便修改(毕竟不是企业级监控,连个存配置文件的数据库都没有。可以,这很 Linux)。

netdata

历史记录

这恐怕是 netdata 最大的缺陷了,所有数据重启服务后就会丢失,而且查看历史数据竟然是用拖的……

netdata

分布式

分布式是啥?能吃吗?client-server?不懂……都有 RESTful API 了,要啥自行车,在所有服务器上都装上 netdata,想看啥自己调 API 去。

netdata

netdata 作为一个实时监控软件,满足了大部分用户仅仅是看下系统资源占用的需求,但是去中心化和专注于实时监控可能并不能满足企业用户的服务器监控需求。

如果你对 netdata 感兴趣,不妨去看下官方的 demo 页面 和 wiki,或部署在本服务器上的 netdata。

北欧模式、社会主义与共产主义

发表于 2016-01-07   |   分类于 杂文   |  

本文来自于今天与同事的一次讨论,我原本只想批判共产主义是不可能实现的乌托邦,以及在中共统治下的言论自由问题。而同事却拿出北欧模式说事,说“北欧模式接近于共产主义”,在我质疑并请他拿出证据的时候,他竟提出“质疑很容易、成本很低,质疑者应该先拿出证据”的可笑论调。

 

友情提示:鄙人非重点院校政治或哲学系出身的高材生,相关知识均来自于由西方敌对势力所支持的维基百科和屈指可数的几本并不专业、也不十分相关的书籍,乃一文盲加民科。如您看得起我,愿意花时间来指正本文的错误,还烦请多写几句,写清论点和论据,以免鄙人难以理解。

 

在我看来,共产主义和资本主义最大的不同在于其分配制度与经济模式。在资本主义社会,生产资料归私人所有,政府较少甚至是不干预市场行为,同时保障私有财产不受侵犯。但缺乏管控的市场经济容易形成贫者愈贫富者愈富的马太效应,加剧贫富差距,引发社会动荡。而共产主义主张消灭私有产权,按需分配生产资料,试图建立一种没有阶级制度、没有国家和政府的社会,人们可以根据自己的兴趣,自愿地参与集体工作。实现共产主义的前提是社会生产力达到充分的发展和极度的发达,以及集体利益高于个人利益的集体主义精神。

然而可笑的是,至今并没有哪个国家真正实现了“共产主义”,因为马克思的共产主义社会原本是指一个没有国家机器(包括了没有政府)、社会生产资料完全公有制的社会,政治组织形式上倾向于无党制,而许多标榜马克思主义的政权是由共产主义政党一党专政的国家。

 

北欧五国实际上实行的是社会民主主义,现代社会民主主义能否被称为社会主义仍然有极大争论,但许多社会民主主义者已经不再自认是社会主义者了。现代的社会民主主义者支持自由市场和公平贸易、广泛的社会福利体制、全民教育与医保、高税率、保障劳工权益等。而更加偏左的民主社会主义则支持公有制和分权式计划经济,政治上实行普选、多党制、司法独立、政治自由。无论是社会民主主义还是民主社会主义,都与共产主义相去甚远,也不是马克思主义理论中的是资本主义社会向共产主义社会过渡的社会主义社会。

从积极的方面来讲,北欧模式这种高福利、高税收社会促进了财富的再分配,有助于缩小贫富差距,更有助于减少由出身而造成的教育不公。但是,高福利使许多人不用工作也能拿到接近于正常薪水的失业补助,导致一些人懒惰,不利于社会进步;高税率使得工人努力工作得到的薪水都很大一部分被政府拿走,当然,这些钱并没有被政府拿去贪污、腐败,而是用在了福利社会的建设上,真正做到了取之于民、用之于民。

 

写这篇文章的目的并不是想抨击或歌颂某一意识形态,其实对普通的老百姓来说,无论谁执政,只要能让老百姓过上好日子,就会拥护谁。但是,也希望某党能放低身段,不要整天鼓吹爱国就是爱党的荒谬言论。就现阶段而言,没有一个政治意识形态是完美的,将来也不会有,坚持一党独裁终会自食恶果。也希望某党能开放互联网,以更开明的心态倾听反对声音,保障公民的言论自由等基本人权。

PHP 小技巧(一)

发表于 2015-10-29   |   分类于 程序员   |  

曾经我以为 PHP 很简单,但后来发现我错了,不是 PHP 简单,而是 PHP 容易入门;不是 PHP 写不出好代码,而是很多 PHP 程序员只关心如何实现,却不关心如何优雅地实现。

弱类型也有类型

PHP 是弱类型的语言,但不代表没有类型,PHP 会隐式转换类型,你也可以强制转换。

像 $_GET $_POST 这样的外部变量,初始类型都是字符串。

当判断一个有可能被转换为 FALSE 的变量是否存在时,请使用 isset($foo) 而不是 !$foo。当 $foo 真的不存在时,后者会报 Notice: Undefined variable;当后者的值为 0 空字符串 NULL 等的时候,后者会返回 TRUE。

foreach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function foo()
{
echo 'a';
return [1, 2, 3];
}
foreach (foo() as $v)
{
}
// 输出 a

据某资深程序员说,在 PHP 4 中,会输出 ‘aaa’,因为每移动一次指针,foo() 都会重新执行一次,但是在 PHP 5 中没有重现。

巧用闭包和引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$foo = 'Hello';
$bar = function ($arg) use (&$foo)
{
$foo .= $arg;
};
$bar(' World.');
echo $foo;
// 输出 Hello World.
$foo = 'foo';
$bar('bar');
// 输出 foobar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$bar = function ($foo) use (&$bar)
{
$foo--;
if ($foo > 5)
{
return $bar($foo);
}
return $foo;
};
echo $bar(10);
// 输出 5

阅后即焚

1
2
3
4
5
6
7
8
9
<?php
$file = '/path/to/file';
readfile($file);
fastcgi_finish_request();
unlink($file);

巴士之乱

发表于 2015-08-27   |   分类于 互联网   |  

近日,打车行业的巨头滴滴快的公司也推出了巴士服务——滴滴巴士,加入了火热的定制巴士市场的竞争。这是即嗒嗒巴士、嘟嘟巴士和小猪巴士等公司后,又一家推出定制巴士服务的公司。但是同前几家公司一样,滴滴巴士一上线,也遇到了很多问题。

 

宣传力度不够

滴滴巴士在线下并没有做任何推广和宣传,甚至在滴滴打车的官网上,也找不到任何关于滴滴巴士的信息。从能找到的新闻来看,滴滴巴士只在微信上有过推广。而其他公司则做得更好,特别是嗒嗒巴士,几乎垄断了公交车站的广告位。

由于定制巴士在线下的宣传力度不够,在车站也没有任何站牌等标识,很多人在看到巴士之前,并不知晓其存在。而定制巴士实行的是先买票后乘车的模式,买票是在线上进行的,所以在未满员的情况下,司机对没有车票的乘客也是睁一只眼闭一只眼,甚至对刚开通或冷门的线路,还配备专人招揽客源免费乘车。

 

运行线路、发车时间、停靠站点设置不合理

这个问题是所有巴士公司共有的,很多热门线路,无论发车时间还是站点设置,所有公司几乎都一样。不仅没有起到分流的作用,还加重了拥堵。

发车时间集中在高峰开始后的半个小时内,停靠站点也都集中在公司、小区多,人流量密集的几个大站,途经的小站则不做停靠。

同一条线路往往只有一趟巴士,如果误了车,不仅没有其他车次可选,也无法退票。

拿我明天下班从 深南香蜜立交 到 桃源村 这段路程来说,嗒嗒、小猪、滴滴三家公司竟然出奇得“默契”,全都是 深南香蜜立交 到 龙辉花园,途径 桃源村,发车时间也都是 18:15。如果有事误了车,没有其他任何替代的巴士,只能去挤公交。而路线相近的公交车 326 路,一直到晚上 8 点,都还人满为患。

定制巴士的站点往往设置在公交车站,它的出现也干扰了公交车的正常运行,特别是在首站,由于需要等待发车时间,只能停在路边,严重影响了公交车进出站。

 

空载严重

宣传力度不够、线路设置不合理、重复导致了大量巴士空载,空载的客车不仅造成了资源的浪费,也加重了高峰时期的拥堵情况。

 

车辆来源混乱、司机不熟悉线路

因为政策限制,所有巴士公司的车辆来源都是有资质的租赁公司,很多都没有固定的车型和外观,仅仅是在车窗顶部贴一个横幅,有的甚至直接放块牌子了事。在这点,嗒嗒和滴滴巴士略胜一筹,不仅车型统一,外观也都一致。

不过还是有很多巴士,连始发和到达站的牌子都没有,已经买票的乘客只能靠车牌号辨识,潜在客户也会因为不知道这条线路而流失。而忘记买票或误了其他公司巴士的乘客,虽然知道有这趟线路,但不知道其目的地,只能问司机,可很多司机只知道大体方向,却不知道具体站名。

 

反思

仿佛历史重演,从打车到专车,再到顺风车,参与竞争的每一家公司无不在用价格战打压对手,同质化严重,每当有一个新的竞争对手的加入,总能拉起新一轮的价格战。最终,在打车市场,最大的两家公司滴滴和快的合并;在专车市场,Uber 和后来加入的滴滴平分秋色;而在顺风车市场,由于各家公司都起步不久,再加上滴滴和百度的加入,鹿死谁手有待分晓。

再看外卖市场,推出不久的百度外卖因为其巨大的优惠力度,迅速占领了市场,迫使饿了么和美团外卖重新打起了价格战。而外卖市场至今没有成熟的盈利模式,各个平台都是靠补贴吸引用户和商家,更别提收费了。

同质化的竞争不仅造成了资源的浪费,也使得竞争变成了纯粹的价格战,发展变成了烧钱,投资变成了投机。

更有些创业者,整天想的不是怎么盈利,而是怎么拿到更多融资给自己发工资,更幻想着被大公司收购,好让自己的股权变现。

12…16
Jat

Jat

学习新知,记录心得,分享成果,发现未知。

154 日志
15 分类
331 标签
RSS
GitHub Steam
Creative Commons
© 2012 - 2016 Jat
Licensed under CC BY-NC-SA
Powered by Hexo
Theme NexT.Muse by IIssNan