Shared posts

08 Dec 06:44

为什么说汽车行业的未来,不属于现在的汽车生产商?

by 黄 美菁

car_in_the_future

汽车行业的未来是如此令人兴奋,你几乎能够碰到它了——在那个自动驾驶电动车的世界中,没有了每年 125 万的交通死亡人数,引起气候变化的气体排放量大大减少,城市的停车场变成公园,醉驾和分心驾驶的危险不复存在。我们似乎都知道自己想要的是什么。但是,谁会把这个前景带给我们?

我相信,这个不可避免的转换将会创造出颠覆传统汽车制造业的、极为清晰的推动力量, 而且,正如其它被科技改变的行业(报纸、旅行社、音乐行业、传统零售业等等),许多新赢家可能会出现。让我们看看原因何在。

1.创新

随着每个行业都变成了科技行业,它的创新步伐必须加速。那些习惯于把摩尔定律当做信息时代唯一确定性特征的人们知道,快速创新是持久存在的事情。如果你不能快速创新,你的竞争者就会去做,而你就在这一轮技术发展中落后了。

快速创新的传统在科技行业是常见的,但是,工业时代的公司却缺乏这个传统。思考一下汽车工业的产品周期有多长吧——新汽车模型的开发需要花费 3 至 5 年,然后,在上市后,这款车型会在市场上停滞六年时间,缺乏有意义的改进或新的创新。得到新功能的唯一方法?买一辆新汽车吧。

当涉及到汽车时,科技行业肯定会选择不同的道路。我们思考的是,构建硬件平台并且将其部署到市场上。在此平台上,我们能经常升级操作系统。而操作系统允许开发者创造数以百万计的 app,把新功能带给用户。

我们知道,用户期望着产品进行意义的升级,并且是通过无线完成的。当需要对核心硬件进行创新时,科技公司会快速推进,通常是 1 到 2 年的周期。

一个很好的例子是手机。如今,消费者能够快速更新汽车上的移动功能(例如导航),但是,他们通常是从手机上得到的,而不是传统的汽车制造商。

汽车行业进行快速创新的复杂之处在于,汽车制造商基本上是汽车的组装者。它们自己制造的汽车部件很少,相反,几乎所有的关键部件都外包给了一线的供应商,比如博世和大陆集团。看看下面这幅图:

supplier

当你产品的大部分都是供应商设计和制造的时候,你或许就会欠缺关键的工程主导地位,无法进行深度和快速的产品创新,难以与竞争对手区别开来。当一个新玩家进入汽车行业后,它们也肯定会从一线或者二线的供应商那里获取大部分的部件,但是,它们很可能会在一些领域进行自主创新,而这些领域是今天的汽车制造商和供应商都不太擅长的。我们下面会谈到这些领域。

(我知道,一些读者会指出,苹果也把大量的 iPhone 部件外包给第三方供应商。尽管如此,苹果自己设计了最重要的部件——CPU、系统和许多内置应用。这篇文章试着去解释为何这是至关重要的。另外,苹果与供应商深度合作,在最有可能让终端用户受益的领域,进行一些直接的开发。而大多数的汽车公司则未能做到这一点。)

2.电动化

特斯拉、Google、苹果和其他新入场的汽车制造商都将它们的设计专注于电动汽车。理由有很多,而最大的是因为电动发动机比传统的内燃发动机简单多了。从某些方面来说,此种复杂性去除后,制造伟大汽车产品的核心技能也被重新定义——与内燃发动机、汽化器、变速箱、排气装置、排放控制系统和燃油经济性管理说再见吧;欢迎电池、电量优化、充电系统和引擎控制器进场。

这些新功能中,一部分是消费电子制造商很熟悉的东西,因为它们在电池续航寿命和电量优化上已经努力一段时间了,尽管是一种很小规模的努力。而且一些汽车公司已经制造出了电动汽车。但是,完全以此(电动车)为基础构建整个组织的话,将会形成一种新的专注和创新的曲线,抛弃掉那些将此当做副项目的公司。

3.软件

或许,汽车行业最重要的转变是它转向了软件。汽车业的未来将很大程度上依赖于软件开发者。没错,现有的内燃发动机汽车确实也有一个包含许多代码的嵌入式系统,能够处理从 HVAC 到自动传动等一切事情。但事实上,把这么多层的软件整合起来是一件复杂的事情,给传统的汽车制造商带来了许多烦恼,毕竟这不是它们的专业领域。除此之外,未来的汽车将会以更深入且不一样的方式利用软件。

当然,我们知道,特斯拉(目前)和苹果(将来)正试图重新想象驾驶员和汽车之间的界面,而且,它们的中控台(很可能)是非常漂亮的,而且将会给那些汽车制造商觉得我们需要看到的、多余的刻度盘带来巨大改进(你上次必须检查最大功率转速和发动机温度是什么时候?)。好的硬件、软件和用户体验设计师会是这一切的根基。

不过,配备了 ADAS 系统以及日益增强的自动化能力的未来汽车将会需要做出数兆亿的驾驶决策,而其根据则是大量的感应器数据。视觉、LiDAR、声纳和其它的感应器将与实时的网络数据、来自其它车辆的数据,甚至是城市环境数据源结合起来。

这些数据将会被实时分析,有可能结合汽车和网络上的计算数据,做出驾驶决策。如此复杂的 AI 系统将会是有适应能力的机器学习系统,它会不断地更新自己的决策模型。map

理解这些后,你就不会奇怪于 Google 今天在自动驾驶开发上的领先地位了。Google 的搜索引擎正是此类系统规模效应的实例,而且,Google 的许多核心开发特长是基于云端的预测系统的。

传统的汽车公司可能不擅长于这些领域的主要原因有两个。首先,全球最好的 AI 工程师、数据科学家和云端计算专家们很少在汽车公司工作。尽管这些公司也拥有一些有才华的工程师,而且近年来许多基于硅谷的研究中心是汽车公司创办的,但是,对于特别厉害的技术架构师和数据科学家来说,Google、特斯拉、苹果和 Uber 这样的公司仍然具有极大的吸引力。他们想要用软件颠覆汽车行业。第二个原因则是数据。

4.数据

将摄像头、传感器与 Mobileye(此公司主要产品是基于摄像头的高级驾驶辅助系统) 芯片连接起来后,去做一些对齐车道或者自适应巡航控制是非常容易的事情。但要达到真正的无人驾驶则难得多,因为首先要让系统去学习。

我们现在还没有一套可以编程到汽车中的既有规则,让它可以预测和避免可能出现的一切危险情况。高效的自动驾驶程序必须利用机器学习能力去发展出一种复杂的模型,让它可以适应各种不同环境。而要达到最佳的效果,机器学习系统就需要大量的数据。

你还记得 Google 提供的“Free 411”服务吗?实际上,他们这样做并不是出于大方,而是为了收集到数百万的声音和言语模式,以此来调教现在用于 Google Now 的语音识别系统。

Google 习惯于利用大规模数据去达到其他人不能企及的优秀表现。正是这种网络效应使得 Google 搜索的表现依然超越一众竞争者。Google 目前拥有 63% 的搜索市场份额,它就是比任何人的数据都要多。它能看到比任何人更多的点击和搜索动作,并可以此训练自己的算法。

self_driving

同样的数据规模优势也会影响无人驾驶汽车。这就是为什么 Google 让它 48 辆无人驾驶汽车组成的车队行驶 120 万英里的原因——为了收集数据和调教它们的系统。在两辆停放的汽车中缓慢走过的行人?这是一种危险情况。太阳光反射出有一个水坑在你的左边?这并不危险。

而特斯拉也是非常重视数据的。你可以从 VentureBeat 这篇文章窥探一二。

2、关键区分点是“ 车队学习”

特斯拉制造的每一款 Model S——甚至那些缺乏必要感应器,从而无法实现自动导航的版本——会把行驶数据传送给公司,只要汽车主人允许。

数据的增长速度大概是每天 150 万英里,它们来自全球行驶在路上的约 10 万辆 Model S。

这些数据被(以匿名形式)整合到地图之中,让自动导航系统看到汽车应该选择的准确道路,或者不选择那条道路,然后将此覆盖到第三方的道路地图之上。

“每个驾驶员都是训练自动导航的高效专业教练员,” 马斯克解释说。特斯拉汽车网络正不断地了解汽车究竟可以行驶在哪些路段。

因此,系统的能力“将随着时间不断改进,既从那些训练它的所有专业驾驶员,” 他说,“也从软件功能上。”——后者指的是添加新功能。

最好的无人驾驶汽车会是那些处于最大网络或者车队中,和网络内其他成员分享数据并从中学习的汽车。对于现有的汽车生产商来说,这就是一个问题。它们没有任何数据。如果它们要加入这个行列,就需要在现有的车型上加上设备来收集数据,并且回到实验室中调教自己的系统。(我听说 Uber 打算做这件事,它们的驾驶路径数据可是非常大的。)

而且,在那些传统汽车生产商生产的无人驾驶汽车上,几乎所有的零件都来自一级供应商。那些供应商也没有数据。实际上,在收集数据上,它们面临的是更严重的问题,因为它们不能和驾驶者产生直接联系——它们拿不到我们的数据。

5.和消费者建立直接关系

之前我曾经写过,当人们的注意力从主流媒体转移到社交媒体平台,品牌就需要和消费者建立直接的关系。汽车行业最需要颠覆的部分是现有的生产商/经销商模式。在这个模式中,汽车公司把车卖给经销商,然后经销商把车以(高得特别离谱的价格)卖给消费者。这个想法真的已经过时了。

第一家成功地与消费者建立直接关系的汽车品牌是特斯拉。他们做对了。他们没有经销商,而是有展示厅。他们不会对我们乱开价。(我记得有一次听汽车公司为他们这种行为辩解,说是消费者真的喜欢讨价还价。)汽车工业将如何应对这种现代互动方式,让一家汽车公司去真的了解他们的消费者吗?而为了保护经销商,美国某汽车经销商协会还向特斯拉发起了诉讼

现代汽车公司不会再使用传统的经销商网络。他们会直接把车卖给消费者,并且和他们形成长期关系。

6.高层的轻视

从那些汽车公司最有影响力的执行官口中,我们听到了汽车公司不会把我们带到美好未来的最后一个信号:

“我觉得,就像非常多的硅谷科技公司,他们觉得自己比世界上的汽车公司聪明,可以做得更好。这是不可能的事。”

“没有任何理由相信苹果会在电动汽车上取得财务上的成功。电动车会是亏本买卖。如果我是股东,我会坐立不安的。”

-Bob Lutz,通用汽车前副主席

“我完全想不到谁会成为第一个售卖无人驾驶汽车的人。”-Mark Fields,福特 CEO

“今天处于汽车行业中的是我们,而不是他们。”-Mark Reuss,通用产品开发总监(当谈到 Google 的时候)

你可能还记得黑莓联合 CEO(Jim Balsillie)说的这些话

[苹果和 iPhone]是进入这个已经竞争激烈的行业的新手,在这个行业中,消费者有非常多的选择……但是说这将会给黑莓带来重大改变,我觉得这是夸张了。(2007.2)

苹果 iPhone 是很好,但确实是给用户带来了真正的挑战。试试在 iPhone 的触摸屏上打一下网页关键词,那真的是一个挑战。你看不到你打的是什么。(2007.11)

确实,苹果、Google、Uber 还有其他从事未来汽车的新公司对已经持续 107 年的汽车行业一窍不通。但关键在于,未来的汽车行业将会完全不同于过去。

我相信,现有的很多汽车厂商会生产出有自动驾驶功能的汽车,而且还会有非常优秀的产品。逐渐地,他们会生产出可以完全自动驾驶的汽车。与此同时,许多超级强大且有想法的企业家会带来更多不可思议的硬件和软件体验。他们会努力从根本上重新定义消费者对汽车的期望。

创新和颠覆的历史告诉我们,在重要的技术平台变革的拐点,新玩家会出现,并且从现有玩家那里夺取相当多的市场份额和利润。我们觉得,我们已经到了这种变革的浪尖 。合理的做法是严肃对待此种转变。我知道,我们正是这样做的。

本文全文译自 Medium,原文标题 The Auto Industry Won’t Create The Future。作者 David Pakman。这篇文章描述了电动车和无人驾驶技术的到来给传统汽车行业带来的颠覆性改变,科技公司,而不是传统汽车公司将在此领域大有作为。爱范儿积木、黄美菁翻译出品。

题图来自:YouTube

插图来自:Medium

 

Work hard, play hard.

#欢迎关注爱范儿认证微信公众号:AppSolution(微信号:appsolution),发现新酷精华应用。



爱范儿 | 原文链接 · 查看评论 · 新浪微博


07 Dec 12:41

常用命令归纳:Linux/Oracle/JVM/Git

by 四火

经常用到一些命令,还总是忘掉的,就简单列在这里。总是现查也挺麻烦的。

Linux:

  • top mem consumer: sudo ps -aux | sort -k4nr | head -5 or top, then press M
  • connection number: netstat -an | grep ESTABLISHED | wc –l
  • process number: ps -ef | wc -l
  • threads of a process: ps uH p <pid> | wc -l
  • .tar: tar -xvf archive.tar and tar -cvf archive.tar file1
  • .tar.gz: tar -cvfz archive.tar.gz dir1 and tar -xvfz archive.tar.gz
  • .zip: zip -r archive.zip ./folder and unzip archive.zip -d ./
  • empty a file: sudo cat /dev/null
  • capture all the output and run in the background: bb > /tmp/a.txt 2>&1 &
  • list the latest changed files: ls -lht | head (log文件一大堆的时候用来找最新的)
  • check folder size: du -h –max-depth=1 ./ (磁盘空间不够的时候经常用来看谁是罪魁祸首)
  • search for 10M+ files: find . -size +10M
  • ssh -qTfnN -D 9999 username@hostname
  • ssh tunnel_host_name -L 60553:target_host_name:60553

Oracle:

  • SELECT CONVERT(DESCRIPTION, 'WE8ISO8859P1', 'UTF8') FROM TABLE_NAME;
  • TAB, ALL_TABLES, USER_TABLES
  • SELECT SEGMENT_NAME, BYTES FROM USER_SEGMENTS WHERE SEGMENT_TYPE = 'TABLE';
  • USER_USERS, USER_ROLE_PRIVS, SESSION_PRIVS;
  • ALL_INDEXES, USER_INDEXES
  • V$SESSION
  • 最大连接数:SELECT VALUE FROM V$PARAMETER WHERE NAME = 'PROCESSES';
  • 当前连接数:SELECT COUNT(1) FROM V$PROCESS;
  • 表空间:SELECT T.TABLESPACE_NAME, ROUND(SUM(BYTES/(1024*1024)),0) TS_SIZE FROM DBA_TABLESPACES T, DBA_DATA_FILES D WHERE T.TABLESPACE_NAME = D.TABLESPACE_NAME GROUP BY T.TABLESPACE_NAME;
  • 文件大小:SELECT TABLESPACE_NAME, FILE_ID, FILE_NAME, ROUND(BYTES/(1024*1024),0) TOTAL_SPACE FROM DBA_DATA_FILES ORDER BY TABLESPACE_NAME;
  • V$ROLLNAME, V$ROLLSTAT, V$UNDOSTAT, V$TRANSACTION

JVM:

  • Tomcat debug: -agentlib:jdwp=transport=dt_socket,address=8088,server=y,suspend=n
  • JProfiler: -agentpath:/opt/jprofiler7/bin/linux-x64/libjprofilerti.so=port=8080,nowait
  • JConsole: -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=3306 -Dcom.sun.management.jmxremote
  • Print default parameters: java -XX:+PrintFlagsFinal -version
  • jmap -histo <pid>
  • jmap -heap <pid>
  • jmap -permstat <pid>
  • jmap -dump:format=b,file=/xxx.hprof <pid>
  • jhat -baseline 1.hprof 2.hprof
  • jstack -l <pid>
  • jstat -gc <pid>
  • jstat -gccapacity <pid>
  • jstat -compiler <pid>
  • jstat -class <pid>
  • 另外,有一些JVM问题定位的工具,总结在这里
Git:
  • git clone <repo>
  • git config –list
  • git diff –staged
  • add后,commit前的撤销:git rm –cached
  • commit后的回滚:git reset –hard commit-id,比如:git reset –hard HEAD

 

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接《四火的唠叨》

分享到:
06 Dec 02:15

在MacBook Pro上设置Java开发环境

by fzr

好吧,我去了地球的另一边,并且因为我的PC不在旁边,只有一台MacBook Pro可以用于开发。这篇文章应该被看作是一个加强书签,我列出了使得MacBook能实现目的的所有必需安装的工具,即用于Java和稍后也会用于JavaScript的开发。

需要提一下的是,直到现在,我仍然是Windows用户(XP / 7)和Linux(Ubuntu /Mint/Cent OS)。在写这篇文章的时候,我的MacBook Pro上运行的是OS X Yosemite Version 10.10.5。

 

JDK

所以先做第一件事,安装Java开发工具包(JDK),这是一个用于开发Java应用和小程序的软件开发环境。它包括Java运行环境(JRE)、解释器/装载器(Java)、编译器(javac)、归档(jar)、文档生成器(javadoc)和Java开发所需要的其他工具。

下载Mac OS X x64 .dmg各版本文件

你可以通过在终端命令行执行/usr/libexec/java_home -v 1.7命令找到JDK的安装位置:

Adrians-MacBook-Pro:ama ama$ /usr/libexec/java_home -v 1.8

/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home

Adrians-MacBook-Pro:ama ama$ /usr/libexec/java_home -v 1.7

/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home

Adrians-MacBook-Pro:ama ama$
比如当你在IntelliJ中新建项目的时候你就需要知道这些。

设置JAVA_HOME

JAVA_HOME只是一个惯例,通常用于Tomcat,其他的JavaEE程序服务器以及建立像Maven之类能找到Java生存位置的工具。

在Mac OSx 10.5以及之后版本中,Apple推荐将$JAVA_HOME设置到路径 /usr/libexec/java_home下,只是将 $JAVA_HOME导出到文件 ~/. bash_profile or ~/.profile中。

$ vim .bash_profile 

export JAVA_HOME=$(/usr/libexec/java_home)

$ source .bash_profile

$ echo $JAVA_HOME

/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home

Maven

按照上面指明的方式设置好JAVA_HOME之后,到Apache Maven Downloads网址,下载.tar.gz 或 .zip文件,然后自选一个文件夹解压——我把它放在/opt文件夹下:

tar xzvf apache-maven-3.3.3-bin.tar.gz
建议创建一个链接到Maven安装,这样假设你想更新Maven版本的时候,只需要改变链接目标:
ln -s /opt/apache-maven-3.3.3/opt/maven

然后在环境变量中设置Maven:

vim ~/.bash_profile
export M2_HOME=/path/to/maven

export M2=$M2_HOME/bin

export PATH=$M2:$PATH

关闭终端,打开一个新的。如果你想现在获取maven版本,你应该像下面这样:

ama$ mvn -version

Apache Maven 3.3.3 (7994120775791599e205a5524ec3e0dfe41d4a06; 2015-04-22T13:57:37+02:00)

Maven home: /opt/maven

Java version: 1.8.0_65, vendor: Oracle Corporation

Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/jre

Default locale: en_US, platform encoding: UTF-8

OS name: "mac os x", version: "10.10.5", arch: "x86_64", family: "mac"

还有一个选择是使用Homebrew,运行下面的命令:

brew install maven

Git

打开一个终端窗口并输入下列命令:

$git --version

下一步你需要安装Xcode。如果想开发Apple TV, Apple Watch, iPhone, iPad, and Mac上的应用,这是一个非常全面的开发者工具集。包括Xcode IDE,模拟器,以及所有创建IOS,watchOS,tvOS和OS X应用所需要的工具和框架(还包括GNU Compiler Collection-gcc)。

你可以按照上面的指示安装,但如果你不想什么都从安装包获取,你可以安装一个Homebrew(“Homebrew可以安装你需要而Apple不需要的程序。”),并运行下面的命令:

brew install gcc

brew install git

不论使用哪种方法,Git安装完成后,初始命令git -version可以看到安装版本:

$ git --version

git version 2.4.9 (Apple Git-60)

如果你使用Github,推荐你再安装一个Github Desktop。

IntelliJ

一般情况下IIntelliJ都是我最喜欢用的IDE,主要是因为前端开发需要的功能几乎都差不多。想安装它,去下载页面,按照指导安装:

安装指导

  • 下载 idea-15.dmg OS X的磁盘镜像文件。
  • 将它挂载为系统中的另一个磁盘。
  • 将IntelliJ IDEA 拷贝到应用文件夹下。

其他

NodeJS

Node.js是建立在Chrome上的 V8 JavaScript 引擎上的Javascript运行时。Node.js使用事件驱动,轻量且高效的非阻塞I/O模型。Node.js的生态系统包,npm,是世界上开源库最大的生态系统。最近它已经成为开发程序前端部分的必备工具了。

https://nodejs.org/下载最新版本的OS X(x64)。双击node-v4.2.2.pkg文件(本文写作前的最新稳定版本),按照安装说明的步骤进行。

当打开一个终端窗口,检查安装的版本看看是否工作:

$ node --version

v4.2.2

终端窗口

背景设黑

打开终端,到Terminal menu -> Preferences,选择Settings选项,将Protheme设置为默认。

为了快速测试出是否一切正常,我在German/Swiss Keyboard上生成了一个常用的UNIX Keys。

我买了Mac Book作为我旅途中的开发机器,最初让我惊讶的一点是竟然没有开发人员/终端用户常用的按键,比如[]|{}~

所以这里,我列出了我用到的Mac OS X键盘映射提示:

| pipe symbol <alt>7
backslash <alt><shift>7 = <alt>/
[ left (opening) square bracket <alt>5
] right (closing) square bracket <alt>6
{ left (opening) curly bracket <alt>8
} right (closing) curly bracket <alt>9
~ Tilde <alt>n followed by the space key
@ “At” symbol <alt>g (lowercase G)

如何测试一切正常

冒烟测试(译者注:冒烟测试是对系统功能的简单测试,强调功能的覆盖率,不验证功能的正确性)可以验证所有安装的工具功能上是否协调,它使用 JHipster 产生一个应用并更新到git仓库。

JHipster是一个 Yeoman generator,过去常用语创建一个 Spring Boot + AngularJS 的项目。

如果有什么建议请留下评论,谢谢。

相关文章

04 Dec 08:05

诊断Java中的内存泄露

by fzr

每次我怀疑有内存泄漏时,我都要翻箱倒柜找这些命令。所以,这里总结一下以备后用:

首先,我用下面的命令监视进程:

while ( sleep 1 ) ; do ps -p $PID -o %cpu,%mem,rss  ; done

(如果有的话还有New Relic)

如果你看到内存上升很快,可能是因为虚拟机设置。如果你没有明确指定JVM的内存设置,它将设置默认值给他们。要获得默认值,使用以下命令:

java -XX:+PrintFlagsFinal -version | grep -i HeapSize

如果这些都不符合你所希望的,那么你就需要指定JVM的内存设置。可以用下面的命令设置最小和最大堆大小:

java -Xms128m -Xmx256m

尽管你有了合理的内存设置,也可以监控进程,但你仍然可能看到内存随时间增加。为了进一步探究原因,你可以使用下面的命令查看对象实例的直方图:

jmap -histo $PID

如果仍然没有足够的信息,那么可以用以下命令进行堆转储:

jmap -dump:format=b,file=/tmp/dump1.hprof $PID

通常,我会用两个堆转储,然后使用下面的jhat命令比较它们:

jhat -baseline /tmp/dump1.hprof /tmp/dump2.hprof

这个命令会启动一个HTTP服务器,你可以用它来探索这两个堆转储之间的差值。在默认情况下,HTTP服务器启动7000端口,你可以在浏览器中访问该端口。

如果你有防火墙,可以通过SSH访问,那么你可以通过如下命令连接该端口:

ssh -L 7000:localhost:7000 $HOST

向下滚动到第一页的底部,你会看到两个有用的链接:

这将给你展示在不同堆转储之间所有“新”的实例,应该对你检测泄漏来自哪里有些帮助。截图如下:

Image title

然后你就拥有了一个神奇命令行的快速查看目录,以便于你需要诊断内存泄漏时使用(然而我总是忘记)。

相关文章

04 Dec 00:34

程序员的出路之一

by zhuweisky

就现在经济大环境而言,很不乐观,程序员的日子也很不好过,无论是还在找工作的、还是已经入职多年、哪怕做到项目经理技术经理的,压力都异常巨大,似乎处处充满危机。但是,仔细分析一下,出路还是有的,甚至解决温饱、过上有房有车没贷款的生活也是很可能的。首先,在如今这个浮躁的社会,大多数人的心态也是浮躁的,只要你能潜下心来,深入研究某个技术,有了一技之长,温饱问题肯定就可以先解决了。

1.一技之长

新技术层出不穷,而内核的精髓的东西却变化不大,就像.NET,从VS2003到VS2012,已经有10个年头,VS的版本不断更新,而.NET内核的最新版本也才4.0,所以,作为程序员,我们要多掌握内核的东西,精髓的东西。

我们的学习积累毛病在于:贪多、贪全、而不够深入。对于很多技术,我们都很有兴趣,对于刚兴起的技术,也紧紧跟随。但是,几乎都是蜻蜓点水、一知半解。回头想想,我们似乎什么都会一点,什么类型的项目都可以做,B/S的、C/S的、数据库的、分布式的,等等,但是,却不敢说,在某某方面,我的水平已经超越了圈中同类型的80%的人。只是我能做的,大家都会做,而且,我也没有把握比别人做得更好。

必须要让自己有价值,而自己的价值在于不可替代性或是难以替代性。如果,随便找个程序员就能把你replace掉,你的价值就很低廉了。如果在你负责的某个方面,只有20%的人超越你,那你的价值、你的重要性就凸显出来了,你与雇主的关系就从被动转向了主动,你就有了谈判的筹码。

在专业化高度分工的今天,一技之长并不是说需要你掌握某个很大的方面,而只需要你能掌握其中的某一个小的领域,并不断地深入下去。就这个小的领域来说,你花个3、5年的时间挤进前20%是非常可能的。比如,有人专门研究SqlServer数据库优化、有人专攻TCP通信、有人深入研究IIS、有人深入钻研WCF,等等。

2.打造自己的精品

当你在某个小领域钻研了3、5年后,你一定会有很多心得,积累了很多经验,其中有些经验是异常宝贵的,为什么了?因为在钻研这个领域一段时间后,会陆续碰到很多问题,而那些80%的人,在碰到某个问题时就停止向前了,在这个小领域的水平就到此为止了,而你却不断地解决这些问题,不断地超越那80%的人。

而且,很可能的一个情况是,作为几年钻研的一个副产品,你积累了一套类库或框架,而基于该类库或框架来开发该领域的项目,不仅开发速度更快,效率更高,而且项目的质量更有保证。然后,你可以把积累的这套类库/框架打造成一个精品,不断的打磨,直到某一天,可以让更多的人来用它。

当你在某一领域有了丰富的经验,或者有了自己的精品类库/框架之后,你便可以面向更广阔的市场。

3.更广阔的市场

在公司做个小白领,你的生死荣禄几乎就完全掌握在你的上司手中,你不得不关注他,被他的情绪所左右,很可能因为他的一句批评,你就整夜难眠。你觉得自己做得很好,可是他不认可。但是现在,你不需要再过分的关注他,你可以将眼光转向更广阔的市场。

互联网时代的一个好处就是,任何人都可以以非常低廉的成本来向大众市场展示自己或自己的产品,评判你价值的不再(仅仅)是你上司,而是整个市场,相比于你的上司,市场的评判会更客观、更公正。你可以把自己的经验能力说明放到自己的博客上、写专业的技术文章来分享知识、顺便推广自己,或者把积累的框架放到网上去卖,或者去项目交易平台接那些与你精通的领域对口的项目,由于在这个领域你超越了80%的人,所以,成功接到项目的可能性是非常之大的。有了这些基础,以后就算是靠技术创业也是有可能的。

如果做到了这三点,我想,你的“有房有车没贷款的生活”差不多就可以实现了。

就我个人经历而言,我花了10年的时间积累了ESFramework通信框架和OMCS语音视频框架,单靠它们的收入,满足家庭的生活开销已经足够了。我作为一个普通的程序员,既然我可以做到,我相信后来人也可以做到,甚至做得比我更好。祝福大家。

程序员的出路之一,首发于博客 - 伯乐在线

03 Dec 07:05

怎样在1秒内启动Linux

by 金灵杰

尽可能快的启动系统,对于自动化设备是非常重要的。系统能够在用户无法感知的时间内启动,也就意味着在不需要工作时,可以完全切断电源,而不是挂起进入休眠状态。本文基于Atmel AT91系列片上系统和NAND闪存,经过一系列的优化,将Linux系统启动时间,从最初的11秒,降低到最终的656毫秒。

背景知识

系统从上电到完全启动,需要经过许多过程。一个简化的启动流程大概包含:

  1. 硬件重置
  2. 启动引导程序(bootloader)
  3. 操作系统初始化
  4. 应用程序执行

其中硬件非常关键,但是硬件一般难以更改。后续的优化,主要针对引导程序、Linux内核和应用程序展开。

引导程序优化

引导程序主要完成对CPU的基础设置,处理ARM标记(ATAGS,ARM TAGS)或设备树(device trees),切换存储管理单元(MMU,Memory Management Unit)等工作。

重要通知:接下来InfoQ将会选择性地将部分优秀内容首发在微信公众号中,欢迎关注InfoQ微信公众号第一时间阅读精品内容。

对于U-Boot,常用的优化方式有:

  • 删除不不要的功能:如网络加载等,如果不需要,那么直接移除这些代码吧;
  • 关闭不需要的功能
    • 关闭内核镜像验证
    • 关闭引导程序输出
    • 关闭启动延迟
  • 将通用功能的引导程序修改成一个优化后的初始程序加载器(Initial Program Loader,IPL),对于U-Boot,可以通过SPL(Second Program Loader,第二阶段程序加载器)来实现。

内核优化

Linux内核被设计的非常灵活,可以针对需要的功能做各种配置优化。因此,优化内核对于系统启动速度是至关重要的。

首先,移除一切不要的驱动,尽可能的减少内核加载的内容,能够大大缩短系统启动时间。其次,还有很多内核选择可能需要进一步尝试,比如内核压缩方式,对于嵌入式系统来说,LZO压缩方式,通常会是一个不错的选择。最后,还可以通过定制一些启动参数,达到加快启动的目的。例如可以通过“lpj=”参数,预设每个循环需要的节拍数(loops per jiffy,lpj)的值,避免系统在启动时自动推算。这样在基于ARMv5的系统中,可以节省100ms以上的时间。

对于内核启动的优化,可以通过bootgraph.pl脚本(位于内核源码的script/bootgraph.pl)来绘制内核启动耗时图表,用以分析启动最耗时的地方。这个脚本使用非常简单,直接将dmesg的输出作为其输入,即可生成svg图表:

dmesg | perl scripts/bootgraph.pl > output.svg

生成的图表如下图,

图中每一个色段表示一个功能的初始化耗时。可以简单的关闭不需要的功能,或者针对功能进行特定的优化。

除了内核本身之外,内核所在的文件系统也对系统启动有着非常大的影响。对于使用闪存芯片作为存储的系统来说,UbiFS是一个很好的选择。它能够容忍意外断电,有着出色的挂载速度,以确保系统快速启动。

应用程序优化

内核完成系统启动之后,接来下就是执行应用程序。对于应用程序的优化,主要有两部分,一部分是由应用程序来接管启动的INIT进程,另一部分是优化应用程序的链接方式。

标准的SystemV INIT程序,需要执行一堆启动脚本。对于嵌入式系统来说,大部分是没有意义的。另一部分(比如挂载文件系统),可以由应用程序自己来实现。然后,可以在内核启动参数中通过“init=”参数,将INIT进程直接指定为应用程序。

应用依赖的动态链接库,会按照以下顺序查找:

  1. LD_PRELOAD环境变量指定的路径(一般对应文件/etc/ld.so.preload);
  2. ELF .dynamic节中DT_RPATH入口指定的路径,若DT_RUNPATH入口不存在的话;
  3. 环境变量LD_LIBRARY_PATH指定的路径,但如果可执行文件有setuid/setgid权限,则忽略这个路径;编译时指定--library-path会覆盖这个路径;
  4. ELF .dynamic节中DT_RUNPATH入口指定的路径;
  5. ldconfig缓存中的路径(一般对应/etc/ld.so.cache文件),若编译时使用了-z nodeflib的链接选项,则此步跳过;
  6. /lib,然后/usr/lib路径 ,若使用了-z nodeflib链接选项,则此步亦跳过;

因此,尽可能的将应用程序依赖的动态链接库放到优先查找的路径,可以加快链接速度。对于交叉编译环境特别需要注意,主机上的动态链接库位置和目标系统上的位置可能不一致,这会增加应用程序执行时动态链接库的加载时间。

总结

基于上面提到的三个优化点,可以将系统的启动时间,从最初的11s降低到656ms(数据参考Jan Altenberg在都柏林举行的嵌入式Linux会议上的演讲稿)。从硬件到引导程序再到内核最后到应用程序,每个启动步骤都有自己可优化的地方,经过一些简单的优化,就可以减少系统的启动时间。


感谢魏星对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入InfoQ读者交流群InfoQ好读者(已满),InfoQ读者交流群(#2)InfoQ好读者)。

03 Dec 00:43

浅谈进程同步和互斥的概念

by 宋沄剑

简介

进程同步是一个操作系统级别的概念,是在多道程序的环境下,存在着不同的制约关系,为了协调这种互相制约的关系,实现资源共享和进程协作,从而避免进程之间的冲突,引入了进程同步。

临界资源

在操作系统中,进程是占有资源的最小单位(线程可以访问其所在进程内的所有资源,但线程本身并不占有资源或仅仅占有一点必须资源)。但对于某些资源来说,其在同一时间只能被一个进程所占用。这些一次只能被一个进程所占用的资源就是所谓的临界资源。典型的临界资源比如物理上的打印机,或是存在硬盘或内存中被多个进程所共享的一些变量和数据等(如果这类资源不被看成临界资源加以保护,那么很有可能造成丢数据的问题)。

对于临界资源的访问,必须是互诉进行。也就是当临界资源被占用时,另一个申请临界资源的进程会被阻塞,直到其所申请的临界资源被释放。而进程内访问临界资源的代码被成为临界区。

对于临界区的访问过程分为四个部分:

1.进入区:查看临界区是否可访问,如果可以访问,则转到步骤二,否则进程会被阻塞

2.临界区:在临界区做操作

3.退出区:清除临界区被占用的标志

4.剩余区:进程与临界区不相关部分的代码

进程间同步和互诉的概念

进程同步

进程同步也是进程之间直接的制约关系,是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系。进程间的直接制约关系来源于他们之间的合作。

比如说进程A需要从缓冲区读取进程B产生的信息,当缓冲区为空时,进程B因为读取不到信息而被阻塞。而当进程A产生信息放入缓冲区时,进程B才会被唤醒。概念如图1所示。

1

图1.进程之间的同步

用C#代码模拟进程之间的同步如代码1所示。

class ProcessSyn
    {
        private static Mutex mut = new Mutex();

        static void Main()
        {
            Console.WriteLine("进程1执行完了进程2才能执行.......");
            Thread Thread1 = new Thread(new ThreadStart(Proc1));
            Thread Thread2 = new Thread(new ThreadStart(Proc2));
            Thread1.Start();
            Thread2.Start();
            Console.ReadKey();   
        }

        private static void Proc1()
        {
            mut.WaitOne();
            Console.WriteLine("线程1执行操作....");
            Thread.Sleep(3000);
            mut.ReleaseMutex();//V操作

        }
        private static void Proc2()
        {

            mut.WaitOne();//P操作
            Console.WriteLine("线程2执行操作....");
            mut.WaitOne();
        }
    }

 

代码1.C#模拟进程之间的同步

运行结果如图2所示。

2

图2.运行结果

进程互斥

进程互斥是进程之间的间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待。只有当使用临界资源的进程退出临界区后,这个进程才会解除阻塞状态。

比如进程B需要访问打印机,但此时进程A占有了打印机,进程B会被阻塞,直到进程A释放了打印机资源,进程B才可以继续执行。概念如图3所示。

3

图3.进程之间的互斥

用C#模拟进程之间的互斥,这里我启动了5个线程,但同一时间内只有一个线程能对临界资源进行访问。如代码2所示。

class ProcessMutex
    {
        private static Mutex mut = new Mutex();
        private const int numThreads = 5;

        static void Main()
        {

            for (int i = 0; i new Thread(new ThreadStart(UseResource));
                myThread.Name = String.Format("线程{0}", i + 1);
                myThread.Start();
            }
            Console.ReadKey();

        }

        //同步
        private static void UseResource()
        {
            // 相当于P操作
            mut.WaitOne();

            /*下面代码是线程真正的工作*/
            Console.WriteLine("{0}已经进入临界区",
                Thread.CurrentThread.Name);
            Random r = new Random();
            int rNum = r.Next(2000);

            Console.WriteLine("{0}执行操作,执行时间为{1}ms", Thread.CurrentThread.Name,rNum);
            Thread.Sleep(rNum);

            Console.WriteLine("{0}已经离开临界区rn",
                Thread.CurrentThread.Name);
            /*线程工作结束*/

            // 相当于V操作
            mut.ReleaseMutex();
        }
        //互斥

    }

代码2.C#模拟进程之间的互斥

运行结果如图4所示。

4

图4.C#模拟进程互斥

实现临界区互斥的基本方法

硬件实现方法

通过硬件实现临界区最简单的办法就是关CPU的中断。从计算机原理我们知道,CPU进行进程切换是需要通过中断来进行。如果屏蔽了中断那么就可以保证当前进程顺利的将临界区代码执行完,从而实现了互斥。这个办法的步骤就是:屏蔽中断–执行临界区–开中断。但这样做并不好,这大大限制了处理器交替执行任务的能力。并且将关中断的权限交给用户代码,那么如果用户代码屏蔽了中断后不再开,那系统岂不是跪了?

还有硬件的指令实现方式,这个方式和接下来要说的信号量方式如出一辙。但是通过硬件来实现,这里就不细说了。

信号量实现方式

    这也是我们比较熟悉P V操作。通过设置一个表示资源个数的信号量S,通过对信号量S的P和V操作来实现进程的的互斥。

P和V操作分别来自荷兰语Passeren和Vrijgeven,分别表示占有和释放。P V操作是操作系统的原语,意味着具有原子性。

P操作首先减少信号量,表示有一个进程将占用或等待资源,然后检测S是否小于0,如果小于0则阻塞,如果大于0则占有资源进行执行。

V操作是和P操作相反的操作,首先增加信号量,表示占用或等待资源的进程减少了1个。然后检测S是否小于0,如果小于0则唤醒等待使用S资源的其它进程。

前面我们C#模拟进程的同步和互斥其实算是信号量进行实现的。

一些经典利用信号量实现同步的问题

生产者–消费者问题

问题描述:生产者-消费者问题是一个经典的进程同步问题,该问题最早由Dijkstra提出,用以演示他提出的信号量机制。本作业要求设计在同一个进程地址空间内执行的两个线程。生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费。消费者线程从缓冲区中获得物品,然后释放缓冲区。当生产者线程生产物品时,如果没有空缓冲区可用,那么生产者线程必须等待消费者线程释放出一个空缓冲区。当消费者线程消费物品时,如果没有满的缓冲区,那么消费者线程将被阻塞,直到新的物品被生产出来

这里生产者和消费者是既同步又互斥的关系,首先只有生产者生产了,消费着才能消费,这里是同步的关系。但他们对于临界区的访问又是互斥的关系。因此需要三个信号量empty和full用于同步缓冲区,而mut变量用于在访问缓冲区时是互斥的。

利用C#模拟生产者和消费者的关系如代码3所示。

class ProducerAndCustomer
    {
        //临界区信号量
        private static Mutex mut = new Mutex();

        private static Semaphore empty = new Semaphore(5, 5);//空闲缓冲区
        private static Semaphore full = new Semaphore(0, 5);
        //生产者-消费者模拟
         static void Main()
         {
             Console.WriteLine("生产者消费者模拟......");
             for (int i = 1; i new Thread(new ThreadStart(Producer));
                 Thread Thread2 = new Thread(new ThreadStart(Customer));
                 Thread1.Name = String.Format("生产者线程{0}", i);
                 Thread2.Name = String.Format("消费者线程{0}", i);
                 Thread1.Start();
                 Thread2.Start();
             }
             Console.ReadKey();

         }

         private static void Producer()
         {
             Console.WriteLine("{0}已经启动",Thread.CurrentThread.Name);
             empty.WaitOne();//对empty进行P操作
             mut.WaitOne();//对mut进行P操作
             Console.WriteLine("{0}放入数据到临界区", Thread.CurrentThread.Name);
                 Thread.Sleep(1000);
             mut.ReleaseMutex();//对mut进行V操作
             full.Release();//对full进行V操作
         }
         private static void Customer()
         {
             Console.WriteLine("{0}已经启动", Thread.CurrentThread.Name);
             Thread.Sleep(12000);
             full.WaitOne();//对full进行P操作
             mut.WaitOne();//对mut进行P操作
             Console.WriteLine("{0}读取临界区", Thread.CurrentThread.Name);
             mut.ReleaseMutex();//对mut进行V操作
             empty.Release();//对empty进行V操作
         }
    }

 

代码3.使用C#模拟生产者和消费者的关系

运行结果如图5所示。

5

图5.生产者消费者C#模拟结果

读者–写者问题

    问题描述:

一个数据文件或记录,统称数据对象,可被多个进程共享,其中有些进程只要求读称为”读者”,而另一些进程要求写或修改称为”写者”。

规定:允许多个读者同时读一个共享对象,但禁止读者、写者同时访问一个共享对象,也禁止多个写者访问一个共享对象,否则将违反Bernstein并发执行条件。

 

通过描述可以分析,这里的读者和写者是互斥的,而写者和写者也是互斥的,但读者之间并不互斥。

由此我们可以设置3个变量,一个用来统计读者的数量,另外两个分别用于对读者数量读写的互斥,读者和读者写者和写者的互斥。如代码4所示。

class ReaderAndWriter
    {
        private static Mutex mut = new Mutex();//用于保护读者数量的互斥信号量
        private static Mutex rw = new Mutex();//保证读者写者互斥的信号量

        static int count = 0;//读者数量

        static void Main()
        {
            Console.WriteLine("读者写者模拟......");
            for (int i = 1; i new Thread(new ThreadStart(Reader));
                Thread1.Name = String.Format("读者线程{0}", i);
                Thread1.Start();

            }
            Thread Thread2 = new Thread(new ThreadStart(writer));
            Thread2.Name = String.Format("写者线程");
            Thread2.Start();
            Console.ReadKey();

        }

        private static void Reader()
        {

            mut.WaitOne();
            if (count == 0)
            {
                rw.WaitOne();
            }
            count++;
            mut.ReleaseMutex();

            Thread.Sleep(new Random().Next(2000));//读取耗时1S
            Console.WriteLine("读取完毕");

            mut.WaitOne();
            count--;
            mut.ReleaseMutex();
            if (count == 0)
            {
                rw.ReleaseMutex();
            }

        }
        private static void writer()
        {

            rw.WaitOne();
            Console.WriteLine("写入数据");
            rw.ReleaseMutex();

        }

 

代码4.C#模拟读者和写者问题

运行结果如图6所示。

图6.读者写者的运行结果 哲学家进餐问题 问题描述: 有五个哲学家,他们的生活方式是交替地进行思考和进餐。哲学家们公用一张圆桌,周围放有五把椅子,每人坐一把。在圆桌上有五个碗和五根筷子,当一个哲学家思考时,他不与其他人交谈,饥饿时便试图取用其左、右最靠近他的筷子,但他可能一根都拿不到。只有在他拿到两根筷子时,方能进餐,进餐完后,放下筷子又继续思考。 8

图7.哲学家进餐问题

根据问题描述,五个哲学家分别可以看作是五个进程。五只筷子分别看作是五个资源。只有当哲学家分别拥有左右的资源时,才得以进餐。如果不指定规则,当每个哲学家手中只拿了一只筷子时会造成死锁,从而五个哲学家都因为吃不到饭而饿死。因此我们的策略是让哲学家同时拿起两只筷子。因此我们需要对每个资源设置一个信号量,此外,还需要使得哲学家同时拿起两只筷子而设置一个互斥信号量,如代码5所示。

class philosopher
    {
        private static int[] chopstick=new int[5];//分别代表哲学家的5只筷子
        private static Mutex eat = new Mutex();//用于保证哲学家同时拿起两双筷子
        static void Main()
        {
            //初始设置所有筷子可用
            for (int k = 1; k //每个哲学家轮流进餐一次
            for(int i=1;inew Thread(new ThreadStart(Philosophers));
                Thread1.Name = i.ToString();
                Thread1.Start();
            }
            Console.ReadKey();
        }
        private static void Philosophers()
        {

            //如果筷子不可用,则等待2秒
            while (chopstick[int.Parse(Thread.CurrentThread.Name)-1] !=1 || chopstick[(int.Parse(Thread.CurrentThread.Name))%4]!=1)
            {
                Console.WriteLine("哲学家{0}正在等待", Thread.CurrentThread.Name);
                Thread.Sleep(2000);
            }
            eat.WaitOne();
            //同时拿起两双筷子
            chopstick[int.Parse(Thread.CurrentThread.Name)-1] = 0;
            chopstick[(int.Parse(Thread.CurrentThread.Name)) % 4] = 0;
            eat.ReleaseMutex();
            Thread.Sleep(1000);
            Console.WriteLine("哲学家{0}正在用餐...",Thread.CurrentThread.Name);
            //用餐完毕后放下筷子
            chopstick[int.Parse(Thread.CurrentThread.Name)-1] = 1;
            chopstick[(int.Parse(Thread.CurrentThread.Name)) % 4] = 1;
            Console.WriteLine("哲学家{0}用餐完毕,继续思考", Thread.CurrentThread.Name);
        }
    }

 

代码5.C#模拟哲学家用餐问题

运行结果如图7所示。

7

图8.哲学家问题运行结果

总结

本文介绍了进程的同步和互斥的概念,临界区的概念,以及实现进程同步互斥的方式,并解决了3种实现同步的经典问题,并给出了相应的C#模拟代码。操作系统对于进程的管理是是计算机编程的基础之一,因此掌握这个概念会使你的内功更上一层:-D

浅谈进程同步和互斥的概念,首发于博客 - 伯乐在线

03 Dec 00:42

Java分析器Top 5:VisualVM、JProfiler、Java Mission Control、YourKit和自定义工具(英文)

by 唐尤华

Java分析器Top 5:VisualVM、JProfiler、Java Mission Control、YourKit和自定义工具(英文)

The post Java分析器Top 5:VisualVM、JProfiler、Java Mission Control、YourKit和自定义工具(英文) appeared first on 头条 - 伯乐在线.

02 Dec 05:38

浅谈操作系统对内存的管理

by 宋沄剑

简介

内存是计算机中最重要的资源之一,通常情况下,物理内存无法容纳下所有的进程。虽然物理内存的增长现在达到了N个GB,但比物理内存增长还快的是程序,所以无论物理内存如何增长,都赶不上程序增长的速度,所以操作系统如何有效的管理内存便显得尤为重要。本文讲述操作系统对于内存的管理的过去和现在,以及一些页替换的算法的介绍。

 

对于进程的简单介绍

在开始之前,首先从操作系统的角度简单介绍一下进程。进程是占有资源的最小单位,这个资源当然包括内存。在现代操作系统中,每个进程所能访问的内存是互相独立的(一些交换区除外)。而进程中的线程所以共享进程所分配的内存空间。

在操作系统的角度来看,进程=程序+数据+PCB(进程控制块)。这个概念略微有点抽象,我通过一个类比来说吧:比如,你正在厨房做饭,你一边看着菜谱一边按照菜谱将原料做成菜,就在这时,你儿子进来告诉你他擦破了腿,此时你停下手中的工作,将菜谱反扣过来,然后找来急救书按照书中的内容给你儿子贴上创口贴,贴完后你继续回去打开菜谱,然后继续做饭。在这个过程中,你就好比CPU,菜谱就好比程序,而做菜的原料就好比数据。你按照程序指令加工数据,而急救工作好比一个更高优先级的进程,中断了你当前做饭的工作,然后你将菜谱反扣过来(保护现场),转而去处理高优先级的进程,处理完毕后你继续从刚才的页读菜谱(恢复现场),然后继续执行做菜这个进程。

在简单介绍完进程的概念后,我们来转入内存。

 

没有内存抽象的年代

在早些的操作系统中,并没有引入内存抽象的概念。程序直接访问和操作的都是物理内存。比如当执行如下指令时:

mov reg1,1000

 

这条指令会毫无想象力的将物理地址1000中的内容赋值给寄存器。不难想象,这种内存操作方式使得操作系统中存在多进程变得完全不可能,比如MS-DOS,你必须执行完一条指令后才能接着执行下一条。如果是多进程的话,由于直接操作物理内存地址,当一个进程给内存地址1000赋值后,另一个进程也同样给内存地址赋值,那么第二个进程对内存的赋值会覆盖第一个进程所赋的值,这回造成两条进程同时崩溃。

没有内存抽象对于内存的管理通常非常简单,除去操作系统所用的内存之外,全部给用户程序使用。或是在内存中多留一片区域给驱动程序使用,如图1所示。

图1.没有内存抽象时,对内存的使用

第一种情况操作系统存于RAM中,放在内存的低地址,第二种情况操作系统存在于ROM中,存在内存的高地址,一般老式的手机操作系统是这么设计的。

如果这种情况下,想要操作系统可以执行多进程的话,唯一的解决方案就是和硬盘搞交换,当一个进程执行到一定程度时,整个存入硬盘,转而执行其它进程,到需要执行这个进程时,再从硬盘中取回内存,只要同一时间内存中只有一个进程就行,这也就是所谓的交换(Swapping)技术。但这种技术由于还是直接操作物理内存,依然有可能引起进程的崩溃。

所以,通常来说,这种内存操作往往只存在于一些洗衣机,微波炉的芯片中,因为不可能有第二个进程去征用内存。

 

内存抽象

在现代的操作系统中,同一时间运行多个进程是再正常不过的了。为了解决直接操作内存带来的各种问题,引入的地址空间(Address Space),这允许每个进程拥有自己的地址。这还需要硬件上存在两个寄存器,基址寄存器(base register)和界址寄存器(limit register),第一个寄存器保存进程的开始地址,第二个寄存器保存上界,防止内存溢出。在内存抽象的情况下,当执行

mov reg1,20

 

这时,实际操作的物理地址并不是20,而是根据基址和偏移量算出实际的物理地址进程操作,此时操作的实际地址可能是:

mov reg1,16245

 

在这种情况下,任何操作虚拟地址的操作都会被转换为操作物理地址。而每一个进程所拥有的内存地址是完全不同的,因此也使得多进程成为可能。

但此时还有一个问题,通常来说,内存大小不可能容纳下所有并发执行的进程。因此,交换(Swapping)技术应运而生。这个交换和前面所讲的交换大同小异,只是现在讲的交换在多进程条件下。交换的基本思想是,将闲置的进程交换出内存,暂存在硬盘中,待执行时再交换回内存,比如下面一个例子,当程序一开始时,只有进程A,逐渐有了进程B和C,此时来了进程D,但内存中没有足够的空间给进程D,因此将进程B交换出内存,分给进程D。如图2所示。

图2.交换技术

 

通过图2,我们还发现一个问题,进程D和C之间的空间由于太小无法另任何进程使用,这也就是所谓的外部碎片。一种方法是通过紧凑技术(Memory Compaction)解决,通过移动进程在内存中的地址,使得这些外部碎片空间被填满。还有一些讨巧的方法,比如内存整理软件,原理是申请一块超大的内存,将所有进程置换出内存,然后再释放这块内存,从而使得从新加载进程,使得外部碎片被消除。这也是为什么运行完内存整理会狂读硬盘的原因。另外,使用紧凑技术会非常消耗CPU资源,一个2G的CPU没10ns可以处理4byte,因此多一个2G的内存进行一次紧凑可能需要好几秒的CPU时间。

上面的理论都是基于进程所占的内存空间是固定的这个假设,但实际情况下,进程往往会动态增长,因此创建进程时分配的内存就是个问题了,如果分配多了,会产生内部碎片,浪费了内存,而分配少了会造成内存溢出。一个解决方法是在进程创建的时候,比进程实际需要的多分配一点内存空间用于进程的增长。一种是直接多分配一点内存空间用于进程在内存中的增长,另一种是将增长区分为数据段和栈(用于存放返回地址和局部变量),如图3所示。

图3.创建进程时预留空间用于增长

 

当预留的空间不够满足增长时,操作系统首先会看相邻的内存是否空闲,如果空闲则自动分配,如果不空闲,就将整个进程移到足够容纳增长的空间内存中,如果不存在这样的内存空间,则会将闲置的进程置换出去。

当允许进程动态增长时,操作系统必须对内存进行更有效的管理,操作系统使用如下两种方法之一来得知内存的使用情况,分别为1)位图(bitmap) 2)链表

使用位图,将内存划为多个大小相等的块,比如一个32K的内存1K一块可以划为32块,则需要32位(4字节)来表示其使用情况,使用位图将已经使用的块标为1,位使用的标为0.而使用链表,则将内存按使用或未使用分为多个段进行链接,这个概念如图4所示。

图4.位图和链表表示内存的使用情况

 

使用链表中的P表示进程,从0-2是进程,H表示空闲,从3-4表示是空闲。

使用位图表示内存简单明了,但一个问题是当分配内存时必须在内存中搜索大量的连续0的空间,这是十分消耗资源的操作。相比之下,使用链表进行此操作将会更胜一筹。还有一些操作系统会使用双向链表,因为当进程销毁时,邻接的往往是空内存或是另外的进程。使用双向链表使得链表之间的融合变得更加容易。

还有,当利用链表管理内存的情况下,创建进程时分配什么样的空闲空间也是个问题。通常情况下有如下几种算法来对进程创建时的空间进行分配。

  •      临近适应算法(Next fit)—从当前位置开始,搜索第一个能满足进程要求的内存空间
  •      最佳适应算法(Best fit)—搜索整个链表,找到能满足进程要求最小内存的内存空间
  •      最大适应算法(Wrost fit)—找到当前内存中最大的空闲空间
  •      首次适应算法(First fit) —从链表的第一个开始,找到第一个能满足进程要求的内存空间

 

虚拟内存(Virtual Memory)

虚拟内存是现代操作系统普遍使用的一种技术。前面所讲的抽象满足了多进程的要求,但很多情况下,现有内存无法满足仅仅一个大进程的内存要求(比如很多游戏,都是10G+的级别)。在早期的操作系统曾使用覆盖(overlays)来解决这个问题,将一个程序分为多个块,基本思想是先将块0加入内存,块0执行完后,将块1加入内存。依次往复,这个解决方案最大的问题是需要程序员去程序进行分块,这是一个费时费力让人痛苦不堪的过程。后来这个解决方案的修正版就是虚拟内存。

虚拟内存的基本思想是,每个进程有用独立的逻辑地址空间,内存被分为大小相等的多个块,称为页(Page).每个页都是一段连续的地址。对于进程来看,逻辑上貌似有很多内存空间,其中一部分对应物理内存上的一块(称为页框,通常页和页框大小相等),还有一些没加载在内存中的对应在硬盘上,如图5所示。

图5.虚拟内存和物理内存以及磁盘的映射关系

 

由图5可以看出,虚拟内存实际上可以比物理内存大。当访问虚拟内存时,会访问MMU(内存管理单元)去匹配对应的物理地址(比如图5的0,1,2),而如果虚拟内存的页并不存在于物理内存中(如图5的3,4),会产生缺页中断,从磁盘中取得缺的页放入内存,如果内存已满,还会根据某种算法将磁盘中的页换出。

而虚拟内存和物理内存的匹配是通过页表实现,页表存在MMU中,页表中每个项通常为32位,既4byte,除了存储虚拟地址和页框地址之外,还会存储一些标志位,比如是否缺页,是否修改过,写保护等。可以把MMU想象成一个接收虚拟地址项返回物理地址的方法。

因为页表中每个条目是4字节,现在的32位操作系统虚拟地址空间会是2的32次方,即使每页分为4K,也需要2的20次方*4字节=4M的空间,为每个进程建立一个4M的页表并不明智。因此在页表的概念上进行推广,产生二级页表,二级页表每个对应4M的虚拟地址,而一级页表去索引这些二级页表,因此32位的系统需要1024个二级页表,虽然页表条目没有减少,但内存中可以仅仅存放需要使用的二级页表和一级页表,大大减少了内存的使用。

 

页面替换算法

因为在计算机系统中,读取少量数据硬盘通常需要几毫秒,而内存中仅仅需要几纳秒。一条CPU指令也通常是几纳秒,如果在执行CPU指令时,产生几次缺页中断,那性能可想而知,因此尽量减少从硬盘的读取无疑是大大的提升了性能。而前面知道,物理内存是极其有限的,当虚拟内存所求的页不在物理内存中时,将需要将物理内存中的页替换出去,选择哪些页替换出去就显得尤为重要,如果算法不好将未来需要使用的页替换出去,则以后使用时还需要替换进来,这无疑是降低效率的,让我们来看几种页面替换算法。

最佳置换算法(Optimal Page Replacement Algorithm)

最佳置换算法是将未来最久不使用的页替换出去,这听起来很简单,但是无法实现。但是这种算法可以作为衡量其它算法的基准。

 

最近不常使用算法(Not Recently Used Replacement Algorithm)

这种算法给每个页一个标志位,R表示最近被访问过,M表示被修改过。定期对R进行清零。这个算法的思路是首先淘汰那些未被访问过R=0的页,其次是被访问过R=1,未被修改过M=0的页,最后是R=1,M=1的页。

 

先进先出页面置换算法(First-In,First-Out Page Replacement Algorithm)

这种算法的思想是淘汰在内存中最久的页,这种算法的性能接近于随机淘汰。并不好。

 

改进型FIFO算法(Second Chance Page Replacement Algorithm)

这种算法是在FIFO的基础上,为了避免置换出经常使用的页,增加一个标志位R,如果最近使用过将R置1,当页将会淘汰时,如果R为1,则不淘汰页,将R置0.而那些R=0的页将被淘汰时,直接淘汰。这种算法避免了经常被使用的页被淘汰。

 

时钟替换算法(Clock Page Replacement Algorithm)

虽然改进型FIFO算法避免置换出常用的页,但由于需要经常移动页,效率并不高。因此在改进型FIFO算法的基础上,将队列首位相连形成一个环路,当缺页中断产生时,从当前位置开始找R=0的页,而所经过的R=1的页被置0,并不需要移动页。如图6所示。

6

图6.时钟置换算法

 

最久未使用算法(LRU Page Replacement Algorithm)

LRU算法的思路是淘汰最近最长未使用的页。这种算法性能比较好,但实现起来比较困难。

 

下面表是上面几种算法的简单比较:

算法 描述
最佳置换算法 无法实现,最为测试基准使用
最近不常使用算法 和LRU性能差不多
先进先出算法 有可能会置换出经常使用的页
改进型先进先出算法 和先进先出相比有很大提升
最久未使用算法 性能非常好,但实现起来比较困难
时钟置换算法 非常实用的算法

 

上面几种算法或多或少有一些局部性原理的思想。局部性原理分为时间和空间上的局部性

1.时间上,最近被访问的页在不久的将来还会被访问。

2.空间上,内存中被访问的页周围的页也很可能被访问。

 

总结

本文简单介绍了操作系统对内存的管理。这些基础概念对于很多开发人员是很有帮助的。内存管理中还有一种分段式管理,也就是一个进程可以拥有多个独立的逻辑地址,以后有时间了再补上一篇。

浅谈操作系统对内存的管理,首发于博客 - 伯乐在线

01 Dec 09:08

帝都求生者系列,雾霾天你可能需要这些神器 丨 清单

by 王 飞

silenthill

广州过来的编辑一大早就说了句,天啊!

从北京今天的空气指数来看,这似乎已经不是雾霾防护那么简单了。

所以我们列举了如下几个产品,在健康的前提下,我们希望这份清单里的产品可以覆盖你的家庭和出行。

优酷视频链接

戴森空气净化扇 Pure Hot + Cool

售价:5690 元

除去精良的做工,在这个极具设计感的产品中,你甚至还能感受到一股扑面而来的“性冷淡”风。

Dyson-purehot+cool-Amplifier-Purifier-Photo-By-Hao-Ying-8

我司郝影编辑曾参加这款产品的媒体体验会,将其描述为:无论是在暖风还是冷风模式下,都可实现空气净化功能,可捕捉 99.95% 小至 PM0.1 的细微颗粒(官方数据)。可以说,既能陪你度过漫漫寒夜,也能共你炎炎夏日,即便是雾霾天也不怕。

Dyson-purehot+cool-Amplifier-Purifier-Photo-By-Hao-Ying-6

看起来它是一个椭圆形的空气槽,外形跟此前的产品变化不大,但在产品的底座附近设计了密密麻麻的进气孔,并内置了玻璃纤维滤网,用此来净化空气。

这款产品官方定价为 5690 元,光算一个暖风扇来说确实有些贵,不过附带了空气净化功能看起来还算心理平衡一些,不过戴森的品牌以及外形设计,“高颜值溢价”还算是可以接受的。

布鲁雅尔净化器 303

售价:3399 元

家用空气净化器方面,很多欧美用户会选择这个品牌。

瑞典设计,中国制造,所以整体的外观算是典型的北欧电器设计风格:外部看起来很简单,甚至都没有采用一些液晶屏。

blueair

整体的三围尺寸为 63.5 * 44 * 25cm,官方表示适应面积在 22 平方米,CADR 值为 280,净化空气率为 99.97%,适合在卧室、书房以及小型办公室使用。

据我所知,在北京的很多外企公司,官方机构多采用这个品牌的产品。总体来说,产品给人很稳定以及可靠的印象。

飞利浦车载净化器 SmartAir 330

售价:2680 元

出门躲进车里就可以了么?不,出门开车还是需要一款车载空气净化器的。

phillips

这款飞利浦 SmartAir 330 提供两种供能方式,一是直插车载电源,供行车时使用(同时可以为电池充电)。二是一个杯形电池,让你可以下车后接上,然后在 app 里设置定时开关,保证上车前空气已经被净化。

4

而且在 SmartAir 330 上还设计了呼吸灯,红黄蓝用来代表车内的空气质量。

ifanr 也刚刚测试了这款车载净化器:它甚至可以根据你吐出一口烟做出变红灯、风扇转速拉升的瞬态响应,并且在几分钟内将车内空气质量监测灯变为绿色。

东西是好东西,缺点是价格稍稍有点贵,官方报价为 2680 元。

特斯拉 Model X

售价:目前需 100000 元预订金

我们为什么要推荐特斯拉 Model X 这辆车呢?

section-hero-background

我们知道这个汽车的电力驱动、450km 续航以及鹰翼门等特性,并且 Model X 拥有感知车辆周围环境的汽车主动防护技术,在这里还有内部防护系统:空气过滤系统。

section-allwheel

在官方的介绍中,空气过滤系统可以提供三种模式:外循环、内循环以及生化武器防御模式,Model X 在内部可以通过气压保护乘客安全。

不过貌似到现在网络上也没这个防护系统具体的使用方式以及使用测试,特斯拉 CEO Elon Musk 表示,这个生化武器防御模式可以强大到过滤生化武器里的化学气体。

所以,生化武器防御模式对于北京的雾霾过滤应该是小意思。

科莱丽洗脸仪 Mia 2

售价:1250 元

出行回家变成的“雾霾脸”,到家第一件事没有什么比洗干净脸更爽的了。

所以我们推荐了这款科莱丽洗脸仪 Mia 2。

clarisonic-mia-2

你可能会问,我用手洗脸就挺好吧?问题在于,手洗只能清洁表面,而 Mia 2 可以有效的清除脸部的粉尘,以及彻底清洁毛孔,甚至对宅男脸的黑头粉刺、晒斑都能进行有效的护理。

还有,皮肤本身分泌的油脂,加上现在恶略的天气,这种复杂情况如果是女生化妆出门脸部清洁更是麻烦,这也是 Mia 2 洗脸最在行的地方。

不过问题在于想买这个产品的都是女生,看文章的应该都是男生,所以你会给她(他)买么?

 

 

题图来自:寂静岭 插图来自:tesla,ponikuta,

Life is short, play more.

#欢迎关注爱范儿认证微信公众号:AppSolution(微信号:appsolution),发现新酷精华应用。



爱范儿 | 原文链接 · 查看评论 · 新浪微博


28 Nov 02:10

互联网+汽车=?

by guest

编者按:本文梳理了互联网和汽车之间可能有的化学反应。作者张子陶博士,真格基金副总裁,关注汽车、招聘等领域,在36氪发表过《白领招聘已饱和,蓝领招聘还空着呢》,引发后续讨论;杨晏慈,真格基金投资研究员,研究内容涉及汽车、生鲜电商、海淘等诸多领域。

为什么“互联网+汽车”这么火?

互联网时代下任何行业的发展都离不开关于“场景变现”的讨论,如今资本十分热捧“互联网+汽车”的概念,我认为其中最核心的原因在于,传统汽车行业与互联网相结合后衍生出来的诸多互联网产品能够满足越来越多样化的C端需求以及能够覆盖越来越广的消费场景。

场景变现,可理解为通过深耕多种场景并设计多样符合消费者需求的产品进行商业变现的活动。围绕汽车,根据商品使用阶段我们可划分为购车前(资讯/比价/社区论坛等)、购车时(比价/团购/直销/汽车金融等)、用车时(出行导航/加油/停泊车等)、用车后(保险/洗车/维修保养等)以及二手车流通(拍卖/竞价/检验等)五个阶段,每阶段与互联网结合都不仅更加高效高质地满足了传统消费需求,更创造出新的用户需求。随着移动互联网渗透率的提高,活跃的移动端顺势成为满足用户需求、能够有效变现的新型增长点。

互联网如何改造传统汽车产业链?

传统汽车产业链中,由于供应链前端(如下图阴影部分所示)过于依赖线下的重资产且参与者不涉及C端用户,因此互联网思维很难为这部分供应链带来正向效用,反而是最接近客户的后端经销商所涉及的零售、二手车转卖和车后服务等业务更早更容易与互联网相结合创造出新的商业模式。

Image title 
互联网对行业的改造主要集中在两点:信息流通效率的提高,以及新渠道的开发与运营。此前的诸多文章已对这个问题做过很详尽的分析,此处不再赘述。值得我们关注的是,发展到现在为止,传统汽车产业链经过互联网的改造后,阵营林立的新车电商和模式多样的二手车电商通过互联网进行信息整合优化了消费者的购车换车流程,让消费者更方便地拥有车,而诸多汽车后市场服务O2O企业则通过互联网对接不同的服务提供商和需求方,方便车主购车后的各类服务消费。

目前互联网+汽车行业的发展已然渗透进toC端的各大消费体验场景,同时toB端的服务场景也在不断地被挖掘。接下来我们就对这三类消费场景做更加深入的研究与探讨。

新车电商

在传统的汽车行业中,多方面商业痛点的存在给汽车电商带来可观的发展机遇。如下图所示,针对B端和C端的不同痛点,汽车电商都能够以创新的方式解决痛点,在互联网的支持下创造出新的商业价值。

Image title

目前,新车电商扮演的还只是一个营销、集客和引流的渠道。但是如下图所示,未来新车电商的发展模式还有很大的想象空间。不过值得注意的是,汽车产品的特殊性和售后服务的重要性决定汽车电商缺乏独立生存的基础,始终需要实体渠道为消费者提供选车、试驾、上牌、保养、维修等一系列无法在网上提供的服务,即线下门店对于新车交易不仅仅是销售渠道更是服务渠道。

Image title

目前,新车电商已经步入发展启动期,如下图所示,整个市场也呈现出四大阵营角逐新车电商的格局:

Image title

纵观四大阵营的发展模式,虽然模式迥异,但各有优劣:

Image title

经过分析,我们可以发现,新车电商大部分仍停留在线上线下的信息交互、用户导流和交易撮合,成熟的盈利模式仍有待探索。

Image title

从未来发展的角度来看,笔者认为较为理想的模式是全电商模式,即汽车电商平台通过汽车团购等多种业态从整车厂拿车,切入在线交易环节,车后服务提供等环节与4S店合作,售后将用户再次导流至平台促进社区交流,在不打击经销商与整车厂利益的同时,打造汽车生活生态圈。

二手车电商

近年来,年轻消费者已经成为汽车购买的主力军,对于他们来讲,汽车不再是资产而更多的是代步工具和消耗品,伴随着消费者经济生活水平的提高和现代消费观念的转变,二手车市场得到了迅猛发展,也吸引了越来越多的创业者和投资者的驻足。目前,我国二手车市场发展尚处早期,未来仍旧有很大的发展空间。

纵观传统二手车产业链,三大行业痛点困扰着整个行业的发展:

Image title

但是随着移动互联的发展,基于互联网的中国二手车市场呈现出新的生机,如下图所示,众多二手车电商都在探索着不同的发展模式。

Image title

在此我们主要分析上图中大家所熟知的C2C、C/B2B、B2C模式,究竟这些模式到底都是怎么玩的,优劣势分别是什么,请看下面两张对比表格:

Image title

通过对比三种模式,我们可以发现,二手车电商始终围绕着车源和客源两大要素变换着各种商业模式。究竟在车源和客源分散程度都很高的二手车领域会跑出什么模式,笔者经过采访诸多业内专家,总结出以下观点供大家参考——短期内,国内二手车市场依然是卖方市场,得车源者得天下,2B模式优势更为明显;而长期看来,二手车将逐步转为买方市场,得客源者得天下,2C模式的优势将凸显出来。

车后服务

根据中国汽车工业协会的资料显示,受我国乘用车存量不断增长、保有汽车车龄结构老龄化明显等因素影响,我国汽车后市场规模激增,潜力如此之大的细分市场自然让不少创业团队和投资人蠢蠢欲动。目前,按照市场上诸多车后企业的发展现状来看,汽车后市场主要分为五大领域,如下图所示:车主服务、洗车服务、维修保养服务以及汽车金融、电商销售平台等。

Image title

下面,我们按用户消费频次从高到低的顺序重点分析一下车主服务、洗车和维修保养三个领域。(由于汽车金融涉及诸多金融专业知识、配件销售平台也偏向电商业务,这两个领域不在我们重点讨论范围)

首先,我们来看被我们大多人忽视的但却是最高频的车主服务领域,这其中包括停车代泊、加油及其他周边服务。如下图所示,经过分析市场特征和领域发展现状我们可以发现,其虽然潜力不小但发展模式仍有待考察和验证:

Image title

其次,我们看洗车行业,洗车服务门槛较低,其本身很少作为独立经营的实体存在,目前的创业公司都在烧钱吸引用户,很难形成粘性并向高门槛的维修保养服务转化,因此经常作为高利润服务的附加服务或者入口服务而存在。互联网洗车行业随着行业的发展出现了如下图两种比较主流的模式:

Image title

不管哪种模式,由于上门服务本身质量难保障、客单价很低、边际成本也较高,因此上门洗车即使有省时方便等优势,也很难发展成一块独立业务,更多的只是被当作APP拉新入口来做。

最后,我们再来看目前最活跃的维修保养领域。该领域之所以被投资人所重点关注,是因为:一方面,行业毛利高,线上化程度低,若与互联网结合进一步降低渠道获客成本,能产生独立的商业模式;另一方面,该市场规模近6000亿,盘子够大,机会够多,且该行业进入门槛较高,玩家分散,尚未出现领军企业。目前,市场上由于对供应商、线下店、消费者三者资源组织的方式不同,维养O2O平台已演化出三种模式:

Image title

综合以上三大块重点讨论内容,笔者认为,不管是以加油、洗车还是维养作为入口切入都可以在短时间内通过补贴上量,但是想由此接入更多的后市场服务则需要面对持续补贴烧钱维持用户留存和相关增值服务使用转化率低下的问题。其中笔者最看好的是车主服务这个领域,洗车客单价低而维养频次低的现实情况会导致这两个领域很多公司会被迫进入挂羊头卖狗肉的商业模式(譬如推销保险或贷款)。无论如何,想要在车后领域做出一个赚钱的生意还需不断地探索和实践。

互联网+汽车的终局?

总结目前互联网+汽车的发展,虽然我们看到互联网巨头纷纷涉足与车相关的业务,或者一些领域已经出现了领先玩家,但这些在传统汽车行业的基础之上所做出的互联网尝试才仅仅是个开始,所谓的巨头和领先玩家在各自的细分领域所占的市场份额依旧很小,对于整个汽车行业的影响尚没有舆论所展现的那么大。

深究其原因,有两点值得一提:一方面,汽车行业是一个非常非常传统的行业,任何互联网+的尝试都离不开线下实体店的落地支持,这就让创业者在试水互联网+汽车的时候要面临线上线下相结合的多线作战局面,而目前大多数互联网+汽车的项目并没有从根本上解决行业痛点,没有创造新的价值,更多的是在服务上利用互联网做一些改良;另一方面,由于我国传统汽车行业非常分散,举经销商的例子来说,即使是根基深厚的TOP25经销商经过多年的打拼和积累也只占据20%不到的市场份额,因此,如果互联网想要对汽车行业有所“颠覆”,还有很长的路要走。

汽车领域的发展空间潜力巨大,以上这些还只是笔者基于产业现状的一些粗浅观点。未来互联网终究会以怎样的形式改造传统汽车行业,我们还需拭目以待。

25 Nov 00:50

紧咬 9 行代码,索赔超 10 亿美元

by aoi

背景 

甲骨文和谷歌之间的 Java 版权诉讼案从 2010 年 8 月起,已经有 5 年多了。2015 年 6 月,美国高等法院驳回谷歌的上诉,维持原判(即:判定 API 受版权保护,谷歌侵权)。

2015 年 10 月案件退回美国地区法院,由 William Alsup 法官再次审理。这位法官曾在 2012 年推翻了陪审团认为谷歌侵权的意见,称 API 不应该受版权保护。

这个案子最近又有新进展了。William Alsup 法官邀请了一位经济学教授来评估 Android 的 9 行代码是否侵权,这彻底的激怒了甲骨文。甲骨文表示这位教授根本没有能力评估代码。

备受争议的 9 行代码,可入选最贵的代码段

OpenJDK 中的那 9 行代码:

private static void rangeCheck(int arrayLen, int fromIndex, int toIndex) {
        if (fromIndex > toIndex)
            throw new IllegalArgumentException("fromIndex(" + fromIndex +
                       ") > toIndex(" + toIndex+")");
        if (fromIndex < 0)
            throw new ArrayIndexOutOfBoundsException(fromIndex);
        if (toIndex > arrayLen)
            throw new ArrayIndexOutOfBoundsException(toIndex);
    }

Google 用了的那 9 行代码:

private static void rangeCheck(int arrayLen, int fromIndex, int toIndex) {
        if (fromIndex > toIndex)
            throw new IllegalArgumentException("fromIndex(" + fromIndex +
                       ") > toIndex(" + toIndex+")");
        if (fromIndex < 0)
            throw new ArrayIndexOutOfBoundsException(fromIndex);
        if (toIndex > arrayLen)
            throw new ArrayIndexOutOfBoundsException(toIndex);
    }

这两段代码都是 Joshua Bloch 写的。

Bloch 曾经在 Sun 公司写 Java API。2004 年他加入谷歌,2008年加入Android项目。不过,在谷歌工作时,他仍然在给由 Sun 公司控制的 OpenJDK 项目贡献代码。

他的贡献之一,基于 TimSort 算法改进了数组排序的实现。旧算法和新算法中都有 rangeCheck 方法,所以他从旧实现中复制了,作为“a temporary measure”。

Joshua Bloch 何许人也?

Joshua Bloch,Java 大牛。Java 程序员应该都听过,即便你没有听过他的名字,那也肯定听过他的这本书《Effective Java》。

早在1996年,适逢 Java 刚刚崭露头角。先是1月份发布JDK1.0,然后是5月底在旧金山召开首届JavaOne大会,年末又是JDK1.1紧跟其后。正是在Java技术如火如荼、大展拳脚的背景之下,JoshuaBloch来到了Sun。2004 年7月初,就在J2SE5.0发布在即,Jusha Bloch刚刚荣获Sun“杰出工程师(Distinguished Engineer)”的称号之时,他突然离开Sun而去了正值发展态势迅猛的Google,成为了Google的Java首席架构师。

2012 年 8 月,Bloch 离开了 Google。

关于索赔金额

  • 彭博社 2015年11月19日报道:《Oracle Says Judge’s Expert Biased in $1 Billion Google-Java Case》
  • PC World 2011年1月20日报道:《Google Disputes Possible $6 Billion Java Lawsuit Price-tag》
  • ZDnet 2011年1月17日报道:《Oracle wants billions from Google over Android: And just might get it》

 

参考

HN & Maja D’Hondt & 百科

紧咬 9 行代码,索赔超 10 亿美元,首发于博客 - 伯乐在线

24 Nov 00:42

IBM承诺将推动Spark的发展

by Alex Giamas

上个月早些时候在拉斯维加斯举行的IBM Insight 2015大数据分析峰会上,IBM公司宣布将主要承担Apache Spark项目。正如IBM公司之前所说的“这可能将会是下个十年中最重要的开源项目”,充分展现了IBM公司对于Apache Spark的重要性充满信心。IDC的报道指出,未来80%的云应用程序将会是数据密集型的,Apache Spark的实时分析功能可以领先一步将无用的数据转变为实用的。IBM公司在六月Spark Summit会议上宣布将在未来几年在Apache Spark项目上总投资三百万美元。这项投资包括在未来几年内,3500名IBM公司开发者在Apache Spark和相关项目上工作。

这项最新的宣布扩展了IBM公司之前的投入,它在Apache Spark上的投资增加了一倍来获取IBM公司云平台的成功。IBM公司同时宣布Spark as a Service将成为它的BlueMix平台的核心技术支持。这意味着开发者和数据科学家可以从他们的应用程序中获取实时分析。IBM公司对于Apache Spark的投入表明该公司将改写它的一些应用程序,使它们兼容Apache Spark。这些应用程序包含BigInsights, Data Science Workbench 以及 SPSS Analytics Server and Modeler。

IBM公司也会将它的SystemML机器学习技术开源,并将与Databricks在Spark机器学习发展方面合作。最后,IBM公司计划参加与AMPLabDataCampMetiStreamGalvanizeBig Data University一同设计现场和MOOC(大型开放式网络课程)的课程,旨在培养出超过一百万的数据工程师和数据科学家。

IBM公司对于Apache Spark的分析项目可以免费试用,在正式使用后价格为0.7/实例小时(instance hour)。

查看英文原文:IBM Commits to Advance Apache Spark


感谢张龙对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入InfoQ读者交流群InfoQ好读者(已满),InfoQ读者交流群(#2)InfoQ好读者)。

23 Nov 08:14

高效 Macbook 开发之道:工具篇

by 伍翀

程序员就像工匠,若想高效地编写出漂亮的代码,就得要有一把好”锤子”——好的开发工具。就像老罗提出匠心与情怀,程序员对于手中的工具也是饱含工匠情怀的。所以,本文就讲讲那些我用出情怀的高效工具们。

Macbook

毋庸置疑,首先你得有台Macbook,这是脱离鼠标提升效率的第一步。所以本文基本上都是推荐Mac上的工具。

笔记&编辑器

MWeb

我的笔记需求很简单,1. 支持Markdown与预览 2. 支持笔记分类管理 3. 简洁美观。哦,要是能直接在Markdown中粘贴图片就更好了。MWeb是我目前用过这么多产品里唯一全符合这些要求的。已购。EvenNote不支持Markdown,太重。Mou缺少文档管理。Cmd Markdown,离线版还有待改进。

Sublime

Sublime是一款具有丰富扩展功能的编辑器。作为前端开发者,完全可以用如此轻量的工具作为前端IDE。

Atom

Atom的推出就是要取代Sublime的。两者功能差不多,可以说Atom深受Sublime哲学的影响。Atom对于包管理更加方便,代码补全也是出色的功能之一。优秀的界面设计,让我这视觉动物忍不住就用上了。就是相对Sublime而言,做的有些重了。

开发工具

IntelliJ IDEA

Java IDE的不二之选。强大,强大,强大,记得一定要上Ultimate版,资金充足就付费,不充足就先用破解,记得靠IDEA赚到钱了得回来补上。用惯后会极大提高开发速度。重复代码自动检查、代码规范提示等功能还能帮你纠正编码规范。快捷键尽量用默认的,不要用Eclipse快捷键,虽然一开始会有点难以适应,但是用久了会发现爽的飞起。IDEA是可以为之单独写篇文章安利的产品,此处不再多说。另外Jetbrains家族的产品都很良心,RubyMine、Pycharm、WebStorm都是不错的IDE。

Dash

Dash是一个API文档浏览器,以及代码片段管理工具。作为一名程序员,每天必不可少的动作就是查各种API文档,为了搜一个函数打开好几个web窗口是很常见的事。Dash可以提高我们的效率,尤其是我为它绑定了shift+space的快捷键之后,在全屏IDE中我可以直接呼出dash查询想要的类/函数。已购。

iTerm 2

自带的Terminal其实也还行,不过有很多理由让我们用iTerm 2。例如设置主题、各种快捷键、方便的复制查找。再配合上Oh My Zsh ,简直爽到爆!

绿色上网

ShadowSocks X

Shadowsocks在Mac上的客户端。[蜡烛]

Surge.app

iOS 9的一个神级API,以及给力的app开发者,终于带给iOS用户们一个安全、低成本、最大网络速度、无连接状态、国内外分流的完美解决方案。终于可以在碎片时间获取国外的最新资讯了。

其他

欧陆词典

Mac自带的字典其实已经很方便了,三指轻按在阅读英文文档时非常方便,但不能满足查单词的需求。而Mac上的词典确实比较少,也就这款用的比较顺手,我绑定了option+space快捷键,可以轻松从顶部呼出搜索栏。

奇妙清单

奇妙清单是一款任务管理工具,可用于记事提醒、工作安排、待办清单、项目管理等工作,重点的是它免费且跨平台支持 iOS、Android、Windows、Mac、网页版等。虽然同类优秀的TODO产品众多,不过这款产品清一色的五星好评值得你拥有。目前我一直用它来管理工作、生活、学习上的事项,用的很顺。支持国产免费软件。

OminiGraffle

作为程序员,免不了要画些流程图什么的。OmniGraffle绝对是Mac上最好用的流程图软件,画出来的图颜值爆表。当然,这是收费的。

Chrome插件

关于Chrome插件我这里只推荐两个吧。一个是围脖是个好图床,可以方便的通过粘贴、拖拽将图片上传到新浪微博图床,并拿到链接。另一个是Proxy SwitchyOmega,SwitchySharp的升级版,搭配ss能代理工具使用。

高效 Macbook 开发之道:工具篇,首发于博客 - 伯乐在线

23 Nov 03:40

文章: Apache Ignite 初探

by 李玉珏

Apache Ignite 内存数组组织框架是一个高性能、集成和分布式的内存计算和事务平台,用于大规模的数据集处理,比传统的基于磁盘或闪存的技术具有更高的性能,同时他还为应用和不同的数据源之间提供高性能、分布式内存中数据组织管理的功能。

在Ignite以前,大规模、大数据量、高并发企业级或者互联网应用为了解决数据缓存、降低数据库负载、提高查询性能等突出问题,很多采用了 Hazelcast或者Oracle Coherence或者GemFire(比如12306网站)或者目前应用越来越广泛的Redis等缓存技术,本文对这些相关的技术做了简单的比较,基本内容来源于其官方网站,进行了翻译整理,方便更多的人了解他。

1 Apache Ignite是什么

Apache Ignite内存数组组织框架是一个高性能、集成和分布式的内存计算和事务平台,用于大规模的数据集处理,比传统的基于磁盘或闪存的技术具有更高的性能,同时他还为应用和不同的数据源之间提供高性能、分布式内存中数据组织管理的功能。

2 Ignite历史

Ignite来源于尼基塔·伊万诺夫于2007年创建的GridGain系统公司开发的GridGain软件,尼基塔领导公司开发了领先的分布式内存片内数据处理技术-领先的Java内存片内计算平台,今天在全世界每10秒它就会启动运行一次。他有超过20年的软件应用开发经验,创建了HPC和中间件平台,并在一些创业公司和知名企业都做出过贡献,包括Adaptec, Visa和BEA Systems。尼基塔也是使用Java技术作为服务器端开发应用的先驱者,1996年他在为欧洲大型系统做集成工作时他就进行了相关实践。

2014 年3月,GridGain公司将该软件90%以上的功能和代码开源,仅在商业版中保留了高端企业级功能,如安全性,数据中心复制,先进的管理和监控等。 2015年1月,GridGain通过Apache 2.0许可进入Apache的孵化器进行孵化,很快就于8月25日毕业并且成为Apache的顶级项目,9月28日即发布了1.4.0版,应该说发展、迭代速度非常之快。该技术相关资料较少,但确是一个很有潜力的技术,解决了大规模、大数据量、高并发企业级或者互联网应用面临的若干痛点。

重要通知:接下来InfoQ将会选择性地将部分优秀内容首发在微信公众号中,欢迎关注InfoQ微信公众号第一时间阅读精品内容。

3 Ignite和Hadoop以及Spark的关系

Ignite和Hadoop解决的是不同的问题,即使在一定程度上可能应用了类似的底层基础技术。Ignite是一种多用途,和OLAP/ OLTP内存中数据结构相关的,而Hadoop仅仅是Ignite原生支持(和加速)的诸多数据来源之一。

Spark 是一个和Ignite类似的项目。但是Spark聚焦于OLAP,而Ignite凭借强大的事务处理能力在混合型的OLTP/ OLAP场景中表现更好。特别是针对Hadoop,Ignite将为现有的Map/Reduce,Pig或Hive作业提供即插即用式的加速,避免了推倒重来的做法,而Spark需要先做数据ETL,更适合新写的分析应用。

4 和类似技术的对比

在Ignite以前,大规模、大数据量、高并发企业级或者互联网应用为了解决数据缓存、降低数据库负载、提高查询性能等突出问题,很多采用了Hazelcast或者Oracle Coherence或者GemFire(比如12306网站)或者目前应用越来越广泛的Redis等缓存技术,本文对这些相关的技术做了简单的比较,基本内容来源于其官方网站,进行了翻译整理,方便更多的人了解他。

4.1 Ignite和Hazelcast

Apache Ignite和Hazelcast都提供了富数据网格的特性,解决了可扩展的分布式集群环境下在内存中对数据进行缓存和分区的问题。

Ignite和Hazelcast在缓存的方式上是有很多不同的,同时支持事务和数据的查询,下面的表格列出了一些主要的不同点,这些都是我们在选择内存数据网格产品时需要特别关注的。

序号

对比项目

Apache Ignite

Hazelcast

1

聚焦于开源

Ignite是一个Apache的开源项目,还在不断的增加新特性,对C++、.NET/C#和Node.js的支持也会很快到来。

Hazelcast正在持续的减少开源版本的功能,更多的功能加入了企业版中,比如堆外存储,持续查询,Web-Session集群,SSL加密支持等。

2

JCache(JSR107)

Ignite完全兼容JCache (JSR 107)缓存规范

Hazelcast完全兼容JCache (JSR 107)缓存规范

3

堆外存储

Ignite根据用户配置支持将数据存储在堆内或者堆外

Hazelcast仅在商业版中提供堆外存储的功能

4

堆外索引

只要配置了堆外存储,Ignite就会在堆外存储索引(为了不影响使用堆内内存的用户应用。)

不支持

5

持续查询

Ignite支持持续查询,比如允许客户端和服务器端订阅数据变化的持续通知

Hazelcast仅在商业版中提供持续查询的功能。

6

SQL查询

Ignite支持完整的SQL(ANSI-99)语法以查询内存中的数据

Hazelcast仅对SQL提供有限的支持(只有几个关键字)

7

关联查询

Ignite支持完整的SQL关联,包括跨多个缓存的关联,比如:select * from A a, B b where a.b_id = b.id

Hazelcast不支持任何的关联查询,不管用不用SQL,如果需要,开发者需要手工处理多个查询的结果。

8

查询一致性

Ignite提供完整的查询一致性,即查询是在一个特定的快照中执行的,查询开始之后的数据更新不影响查询的结果。

Hazelcast查询是不一致的,这是可能的,查询结果的一部分将看到一定的更新,而另一部分则不会。

9

查询容错

Ignite查询是容错的,即查询结果始终是一致的不会受到集群拓扑发生变化的影响,比如节点的加入,退出或崩溃。

Hazelcast查询是不容错的,即查询结果在集群拓扑发生变化时不一致,而数据正在后台重新平衡。

10

数据一致性

Ignite支持内存中数据的原子性和事务一致性,不管数据存储在分区或者复制缓存中。

Hazelcast仅在分区缓存中支持原子性和事务一致性,而存储在复制缓存中的数据没有任何事务一致性的保证。

11

SSL加密

Ignite为所有的网络传输提供SSL加密,包括客户端和服务器端以及服务器之间。

Hazelcast仅在商业版中提供SSL加密功能。

12

Web-Session集群

Ignite为所有已知的应用服务器提供Web-Session的缓存和集群化支持。

Hazelcast仅在商业版中提供Web-Session集群化支持。

13

计算网格

Ignite提供集群上的M/R,Fork/Join和基本的分布式lambda处理,包括任务负载平衡,容错,检查点,计划任务等。

Hazelcast仅支持M/R和集群内的分布式随机任务。

14

流式网格

Ignite支持内存流,包括对数据流浮动窗口的查询和维护支持

不支持

15

服务网格

Ignite可以使用户方便地将其服务集群化,包括支持各种单例集群。

Hazelcast管理的服务不提供单例集群的功能。

16

.Net/C#,C++支持

Ignite将在1.5.0版中提供完整的内存组织API

Hazelcast仅在商业版中提供有限的客户端API支持。

17

Node.js支持

Ignite将在1.5.0版中提供Node.js的客户端API。

不支持

4.2 Ignite和Coherence

Apache Ignite和Oracle Coherence都提供了富数据网格的特性,解决了可扩展的分布式集群环境下在内存中对数据进行缓存和分区的问题。

Ignite和Coherence在缓存的方式上是有很多不同的,同时支持事务和数据的查询,下面的表格列出了一些主要的不同点,这些都是我们在选择数据网格产品时需要特别关注的。

序号

对比项目

Apache Ignite

Oracle Coherence

1

开源和闭源

Ignite是一个Apache的开源项目,并且还在不断的增加新特性,对C++、.NET/C#和Node.js的支持也会很快到来。

Coherence是一个Oracle的专有软件,并不提供开源和免费的版本。

2

JCache (JSR 107)

Ignite完全兼容JCache (JSR 107)缓存规范

Coherence完全兼容JCache (JSR 107)缓存规范

3

堆外存储

Ignite根据用户配置支持将数据存储在堆内或者堆外

Coherence对开发者提供了有限的选项支持将数据存储在堆外

4

堆外索引

只要配置了堆外存储,Ignite就会在堆外存储索引(为了不影响使用堆内内存的用户应用。)

不支持

5

SQL查询

Ignite支持完整的SQL(ANSI-99)语法以查询查询内存中的数据

不支持

6

关联查询

Ignite支持完整的SQL关联,包括跨多个缓存的关联,比如:select * from A a, B b where a.b_id = b.id

Coherence不支持任何的关联查询,不管用不用SQL,如果需要,开发者需要手工处理多个查询的结果。

7

ACID事务

Ignite提供了每台服务器每秒成千上万事务的优异性能。

Coherence因为性能原因不建议使用事务。

8

分层存储

Ignite支持分层存储模型,数据可以在堆内、堆外以及交换空间内存储和移动,上层将提供更多的存储能力,当然延迟也会增加。

不支持

9

数据流

Ignite提供内存流的支持,包括支持流数据的维护、查询和浮动窗口

不支持

10

配置

Ignite支持通过Java Bean以及原生的Spring XML集成对系统进行配置,同时也支持通过代码对系统进行方便配置的能力。

Coherence通过专有的XML格式文件进行配置,不支持通过代码进行配置。

4.3 Ignite和Gemfire

Apache Ignite和Pivotal Gemfire都提供了富数据网格的特性,解决了可扩展的分布式集群环境下在内存中对数据进行缓存和分区的问题。

Ignite和Gemfire在缓存的方式上是有很多不同的,同时支持事务和数据的查询,下面的表格列出了一些主要的不同点,这些都是我们在选择数据网格产品时需要特别关注的。

序号

对比项目

Apache Ignite

Pivotal Gemfire

1

开源和闭源

Ignite是一个Apache的开源项目,并且还在不断的增加新特性,对C++和.NET/C#和Node.js的支持也会很快到来。

Gemfire是Pivotal的专有软件。

2

JCache (JSR107)

Ignite数据网格是JCache(JSR107)规范的一个实现,该API为数据访问提供了简单易用、但是功能强大的API。

Gemfire没有实现JCache,使用专有的API。

3

堆外存储

Ignite根据用户配置支持将数据存储在堆内和堆外

Gemfire不支持将数据存储在堆外

4

SQL查询

Ignite支持完整的SQL(ANSI-99) 查询语法以查询内存中的数据。

Gemfire不支持标准的SQL语法,但是他提供了他自己的叫做OQL的对象查询语言。

5

关联查询

Ignite支持完整的SQL关联,包括跨多个缓存的关联,比如:select * from A a, B b where a.b_id = b.id

Gemfire不支持任何的跨区或者跨缓存的关联查询,如果需要,开发者需要手工处理多个查询的结果。

6

跨分区事务

Ignite支持跨分区事务,事务可以在整个集群中缓存的所有分区中执行。

Gemfire不支持跨越多个缓存分区或者节点的事务。

7

分层存储

Ignite支持分层存储模型,数据可以在堆内、堆外以及交换空间内存储和移动,上层将提供更多的存储能力,当然延迟也会增加。

不支持

8

数据流

Ignite提供内存流的支持,包括支持流数据的维护、查询和浮动窗口

不支持

9

配置

Ignite支持通过Java Bean以及原生的Spring XML集成对系统进行配置,同时也支持通过代码对系统进行方便配置的能力。

Gemfire通过专有的XML格式文件进行配置,不支持通过代码进行配置。

10

部署

Ignite节点是对等的,并且在启动时自动加入集群(不需要任何locator服务器)。

Gemfire需要启动和维护一个locator服务器,以便控制节点的加入

4.4 Ignite和Redis

Apache Ignite和Redis都提供了分布式缓存的功能,但是每个产品提供的功能特性是非常不同的。Redis主要是一个数据结构存储,但是Ignite提供了很多内存内的分布式组件,包括数据网格、计算网格、流,当然也包括数据结构。

Ignite是一个内存数据组织,并且提供了更多的功能,无法进行一个一个对应功能特性的比较,但是我们仍然能对一些数据网格功能进行比较。

序号

对比项目

Apache Ignite

Redis

1

JCache (JSR 107)

Ignite完全兼容JCache(JSR107)缓存规范

不支持

2

ACID事务

Ignite完全支持ACID事务,包括乐观和悲观并发模型以及READ_COMMITTED, REPEATABLE_READ和SERIALIZABLE隔离级别。

Redis提供了客户端乐观事务的有限支持,在并发更新情况下,客户端需要手工重试事务。

3

数据分区

Ignite支持分区缓存,类似于一个分布式哈希,集群中的每个节点都存储数据的一部分,在拓扑发生变化的情况下,Ignite会自动进行数据的平衡。

Redis不支持分区,但是他提供了副本的分片,

4

全复制

Ignite支持缓存的复制,集群中的每个节点的每个键值对都支持。

Redis不提供对全复制的直接支持。

5

原生对象

Ignite允许用户使用自己的领域对象模型并且提供对任何Java/Scala, C++和.NET/C#数据类型(对象)的原生支持,用户可以在Ignite缓存中轻易的存储任何程序和领域对象。

Redis不允许用户使用自定义数据类型,仅支持预定义的基本数据结构集合,比如Set、List、Array以及一些其他的。

6

(近)客户端缓存

Ignite提供客户端缓存最近访问数据的直接支持。

Redis不支持客户端缓存。

7

服务器端并行处理

Ignite支持在服务器端,靠近数据并行地直接执行任何Java, C++和.NET/C#代码。

Redis通常没有任何并行数据处理的能力,服务器端基本只支持LUA脚本语言,服务器端不直接支持Java, .NET,或者C++代码执行。

8

SQL查询

Ignite支持完整SQL(ANSI-99)语法以查询内存中的数据。

Redis不支持任何查询语言,只支持客户端缓存API。

9

持续查询

Ignite提供对客户端和服务器端持续查询的支持,用户可以设置服务器端的过滤器来减少和降低传输到客户端的数据量。

Redis提供客户端基于键值的事件通知的支持,然而,他不提供服务器端的过滤器,因此造成了在客户端和服务器端中更新通知网络流量的显著增加。

10

数据库集成

Ignite可以自动集成外部的数据库-RDBMS, NoSQL,和HDFS

不支持

5 总结

按照官方的说法,Ignite是很强大的整体解决方案和开发平台,功能很多而且复杂,和相关技术的比较中也没有提到缺点或者不足,这个只能使用过程中逐步发现。

从设计的角度看,Ignite对开发者非常友好,提供了丰富的、符合各种标准和规范的API,如果在已有项目或者系统中集成的话,对已有代码的侵入性或者对已有架构设计的破坏性较小,在已有架构代码中做出不是很大的修改,就可以在整个系统中加入一个数据缓存层或者内存计算层,对下可以映射各种关系库或者非关系库,对上方便的对接应用系统。

目前来看,一个显而易见的问题就是,社区刚刚建立,文档等开发资料较少,虽然 Ignite本身历史尚短,但是既然来源于历史不算短的商业软件,还是经过实际生产环境验证的,可用性肯定是有的。社区的活跃和文档的完善还需要较长的时间,应用开发商和开发者对他的认知和接受,也还需要一个过程,Ignite技术和社区是不是会像Hadoop等技术一样活跃甚至火爆,或者是不是能替代一些技术,还需要观察,路还很长。

19 Nov 08:37

运维工程师必会工具:Nmap 和 TCPdump

by 小编辑

1、NMap工具

主要功能:探测主机是否在线、扫描主机开放端口和嗅探网络服务,用于网络探测和安全扫描。

NMap支持很多扫描技术,例如:UDP、TCPconnect()、TCPSYN(半开扫描)、ftp代理(bounce攻击)、反向标志、ICMP、FIN、ACK扫描、SYN扫描和null扫描。

命令格式:Nmap [ 扫描类型 ] [ 通用选项 ] { 扫描目标说明 }

扫描类型:

-sT TCP connect()扫描,这是最基本的TCP扫描方式,用来建立一个TCP连接,如果成功则认为目标端口正在监听,否则认为目标端口没有监听程序。这种扫描很容易被检测到,在目标主机的日志中会记录大批的连接请求以及错误信息。
-sS TCP同步扫描(TCP SYN),只向目标发出SYN数据包,如果收到SYN/ACK响应包就认为目标端口正在监听,并立即断开连接;否则认为目标端口没有监听程序。所以这项技术通常称为半开扫描(half-open)。这项技术最大的好处是,很少有系统能够把这记入系统日志。不过,你需要root权限来定制SYN数据包。
-sF,-sX,-sN 秘密FIN数据包扫描、圣诞树(Xmas Tree)、空(Null)扫描模式。这些扫描方式的理论依据是:关闭的端口需要对你的探测包回应RST包,而打开的端口必需忽略有问题的包,通过这种扫描,可间接用于检测防火墙的健壮性。
-sP ping扫描,用ping方式检查网络上哪些主机正在运行。当主机阻塞ICMP  echo请求包是ping扫描是无效的。nmap在任何情况下都会进行ping扫描,只有目标主机处于运行状态,才会进行后续的扫描。
-sU UDP扫描,如果你想知道在某台主机上提供哪些UDP服务,可以使用此选项。
-sA ACK扫描,这项高级的扫描方法通常可以用来穿过防火墙。
-sW 滑动窗口扫描,非常类似于ACK的扫描。
-sR RPC扫描,和其它不同的端口扫描方法结合使用。
-b FTP反弹攻击(bounce attack),连接到防火墙后面的一台FTP服务器做代理,接着进行端口扫描。

通用选项:

-n 不做反向DNS解析,以加快扫描速度
-P0 在扫描之前,不ping主机;有些网络防火墙可能禁止ICMP请求包,使用这种扫描类型可以跳过ping测试
-PT 扫描之前,使用TCP ping确定哪些主机正在运行。
-PS 对于root用户,这个选项让nmap使用SYN包而不是ACK包来对目标主机进行扫描。
-PI 设置这个选项,让nmap使用真正的ping(ICMP echo请求)来扫描目标主机是否正在运行。
-PB 这是默认的ping扫描选项。它使用ACK(-PT)和ICMP(-PI)两种扫描类型并行扫描。如果防火墙能够过滤其中一种包,使用这种方法,你就能够穿过防火墙。
-O 这个选项激活对TCP/IP指纹特征(fingerprinting)的扫描,获得远程主机的标志,也就是操作系统类型。
-I 打开nmap的反向标志扫描功能。
 -f 使用碎片IP数据包发送SYN、FIN、XMAS、NULL。包增加包过滤、入侵检测系统的难度,使其无法知道你的企图。
-v 强烈推荐使用这个选项,它会给出扫描过程中的详细信息。
-S 在一些情况下,nmap可能无法确定你的源地址(nmap会告诉你)。在这种情况使用这个选项给出你的IP地址。
-g port 设置扫描的源端口。一些天真的防火墙和包过滤器的规则集允许源端口为DNS(53)或者FTP-DATA(20)的包通过和实现连接。显然,如果攻击者把源端口修改为20或者53,就可以摧毁防火墙的防护。
-oN 把扫描结果重定向到一个可读的文件logfilename中。
-oS 扫描结果输出到标准输出。
-A 打开操作系统探测和版本探测。

扫描目标:

目标地址 可以为IP地址,CIRD地址等。如192.168.1.2,222.247.54.5/24
-iL filename 从filename文件中读取扫描的目标。
-iR 让nmap自己随机挑选主机进行扫描。
-p 端口,这个选项让你选择要进行扫描的端口号的范围。可使用逗号分隔多个端口,减号连接一个端口范围,在列表前指定T:表示TCP端口,U:表示UDP端口
-exclude 排除指定主机。
-excludefile 排除指定文件中的主机。

端口的三种状态:

  • Open:意味着目标主机能够在这个端口使用accept()系统调用接受连接。
  • filtered:表示防火墙、包过滤和其它的网络安全软件掩盖了这个端口,禁止nmap探测其是否打开。
  • unfiltered:表示这个端口关闭,并且没有防火墙/包过滤软件来隔离nmap的探测企图。

举例说明:

1、探测指定网段是否有FTP服务的主机,不做DNS反向解析

nmap -sS n p 21192.168.0.0/24

2、探测指定服务器是否启有特定端口的服务

nmap n p T:21-25,80,110,3389sS 192.168.0.1

3、使用TCP连接扫描探测指定服务器,即使无法ping通也仍然继续探测

4、nmap -sT PO 192.168.0.1

5、探测指定服务器的操作系统类型

nmap O n 192.168.0.1

6、探测局域网段中各主机开启了哪些服务

nmap sS 192.168.0.0/24

7、探测192.168.0.0和172.16.0.0/16网段中有哪些主机在运行

nmap sP n 192.168.0.0/24 172.16.0.0/16

8、快速扫描主机开放端口

nmap -F 192.168.0.1

2、TCPDump工具

主要功能:捕获和分析数据包。

TcpDump可以将网络中传送的数据包的“头”完全截获下来提供分析。它支持针对网络层、协议、主机、网络或端口的过滤,并提供 and、or、not等逻辑语句来帮助你去掉无用的信息。

命令格式:tcpdump [ 选项 ] [ -c 数量 ] [ -i 网络接口 ] [ -w 文件名 ] [ 表达式 ]

常用选项:

  • -l:使标准输出变为缓冲行形式;
  • -c:抓包次数;
  • -nn:直接以 IP 及 Port Number 显示,而非主机名与服务名称;
  • -s :<数据包大小> 设置每个数据包的大小;
  • -i:指定监听的网络接口;
  • -r:从指定的文件中读取包;
  • -w:输出信息保存到指定文件;
  • -a:将网络地址和广播地址转变成名字;
  • -d:将匹配信息包的代码以人们能够理解的汇编格式给出;
  • -e:在输出行打印出数据链路层的头部信息;
  • -f:将外部的Internet地址以数字的形式打印出来;
  • -t:在输出的每一行不打印时间戳;
  • -v :输出稍微详细的报文信息;加一个v更详细。

 

四种表达式:过滤报文条件

  • 1、关于类型的关键字,主要包括host,net,port, 例如 host210.27.48.2,指明 210.27.48.2是一台主机,net 202.0.0.0 指明202.0.0.0是一个网络地址,port 23 指明端口号是23。如果没有指定类型,缺省的类型是host。
  • 2、确定传输方向的关键字,主要包括src, dst ,dst or src, dst and src ,这些关键字指明了传输的方向。例如 src210.27.48.2 ,指明ip包中源地址是210.27.48.2, dst net 202.0.0.0 指明目的网络地址是202.0.0.0 。如果没有指明方向关键字,则缺省是src or dst关键字。
  • 3、协议的关键字,主要包括ip,arp,tcp,udp等类型。
  • 4、三种逻辑运算,与运算是’and’,'&&’; 或运算是’or’ ,’||’; 非运算是 ‘not ‘ ‘! ‘。

其他重要的关键字如下: broadcast,less(小于),greater(大于)

举例说明:

1、截获eth0网卡10次收发所有数据包并将抓包结果保存到test文件,再读取test抓包结果文件

tcpdump i eth0 c 10 w test

tcpdump r test

2、截获来访问80端口的所有数据包(指定端口范围portrange 1-1024)

tcpdump port 80

3、截获所有来自主机114.254.151.51的进出所有数据包

tcpdump host 114.254.151.51

4、截获ip包中源地址是114.254.151.51的(目的是dst)

tcpdump src 114.254.151.51

5、截获主机114.254.151.51和主机114.254.151.52的通信

tcpdum host 114.254.151.51 and 114.254.151.52

6、截获tcp协议并且源地址114.254.151.51来访问80的端口

tcpdump tcp and src 114.254.151.51 and port 80

7、截获主机114.254.151.51除了和114.254.151.52之外的所有ip包

tcpdump ip host 114.254.151.51 and ! 114.254.151.52

8、截获长度大于1000数据包,对于DDOS攻击时,可以使用

tcpdump -i eth0 greater 1000

运维工程师必会工具:Nmap 和 TCPdump,首发于博客 - 伯乐在线

19 Nov 08:33

读研如何提高技术之我见

by aoi

你想进什么公司,和你读什么计算机方向,关系不是很大。只要是理工科的学生,面对技术岗位,其实机会是差不多的。

至于你想什么职位,那显然和你的专业/方向关系较大,比如说很多大数据职位,就明确表示要求你DM/ML/NLP/IR背景,但是这也只是说这些学生可能更有优势些,事实上,数学和统计学出身的,很多搞这个非常牛逼的。

所以,不要问选什么导师,选什么方向;也不要问我的实验室很水老板很菜我还能找到好工作吗?你要问的是:我想毕业去互联
网公司,我读研的这几年是应该打酱油呢,还是打地沟油?

想找一个好的互联网技术类、研发类工作,无非就是以下4点,你任选一点,做好了,都是有利的,哪4点?

  • 语言层面
  • 算法层面
  • 项目层面
  • 专业层面

具体分析如下:


语言层面

你是想搞C++还是Java?选定一门语言后,多看一些这方面的书。

C++,不用说了,《Effective C++》《More Effective C++》《Inside The C++ Object Model》等等。

Java,最好能看点虚拟机相关的。尤其是Java虚拟机的内存管理。以及多线程、线程池、设计模式等。

问题来了:学C++好还是学Java好呢?语言争论每天都在发生,真的好无聊。建议按照方向和兴趣来选择,不靠谱的说:

如果是想做大型游戏开发、底层研发、系统研发、驱动研发等等,就选用C/C++

如果是想做网站开发、网络研发、上层开发、Android开发等等,就选用Java

顺便说一下,有空可以学学Python


算法层面

学好算法。这个有两条路,最好是并行,那就是看书和做题。

看书的话,主要有《算法导论》、《算法》(Robert Sedgewick著)、《算法竞赛入门经典》、《挑战程序设计竞赛》

找一本认真看,认真思考。证明过程可以不看,但是算法思想最好能懂,以及实现,最好能在纸张上写出来。复杂度总要知道吧?

研一的时候,我就把算法导论上的很多算法,都自己亲自coding了下。

特别复杂的数据结构,比如红黑树、B树,没空就别搞了。互联网面试一般都不要求的。

主要是排序、查找、简单DP、贪心、图算法和搜索。 根据身边同学的面试经验,二分搜索和快速排序,是面试常备了。看书的时候,如果有
时间,可以思考几个问题:

  • 这个算法的时间空间复杂度,各是多少?如何分析?
  • 工程实现里,都有哪些trick?如何加速?
  • 这个算法可能用在哪些方面?有哪些应用?你比如说吧,求交集的算法,就广泛用在倒排索引、新浪微博共同关注、计算Jaccard系数等
    等上。

除了看书,还有就是很重要的:刷题啦。主要有pojzoj等。找一个网站,认真刷一些题目,踏踏实实的,别浮躁。


项目层面

如果实验室比较牛,基本上忙项目就足够累死累活了;不过做了相对给力的项目,对于找工作,还是有很大帮助的。基本上不用怎么愁了。
如果老板这儿没项目呢?

如果实验室比较水,那就尽量去实习。大公司的实习经验很能给简历加分。如果老板不让你出去实习。ok,既然实验室很水,说明自由时间
比较多,那还是可以干很多事。比如说,github上就有很多开源项目,你可以选择一两个著名的,阅读源码,然后尝试自己也参与进去。

说几个我比较感兴趣的开源项目吧:redisspark


专业层面

你对数据挖掘很精通,你对推荐系统很熟悉。你在NIPSSIGIR上发表论文无数;你是百度推荐大赛,阿里巴巴大数据比赛冠军常客。

也就是说,除了尝试发顶会论文,还可以参加各种比赛。本专业本领域里都有什么比赛可以参加呢?搜一下微博或者知乎,或者问下师兄师姐,不就知道了?

专业层面,可以做的东西非常多。比如,你对数据挖掘很感兴趣,那么数据挖掘中的常见模型,我们了解多少?机器学习,我们掌握到什么
程度了?举个例子:

  • SVMlogistic regression这两种model,有什么特点?各自的适用场合?
  • feature selection的常用方法有哪些?为什么lasso可以作为feature selection
  • Adaboosting为什么不容易over-fitting?你能不能从statistical view来解释解释?
  • 哦,你和我说,你不是搞学术研究,你反感Learning Theory,你说你是一个工程师,那你写了多少代码?

scikit-learnweka,以及spark,你会用哪个工具?(熟悉一种工具)

你有自己下载一些数据集跑跑实验吗?你有参加一些大数据相关的竞赛吗?(动手实验/参加比赛)

专业层面,能做的,要做的,还有很多。

哦,最好熟悉下linux的基本使用。


最后的话

如果能在读研期间做到这里的一点或者几点,到时候甚至不需要翻《编程之美》或者leetcode就毫无压力啦。 最后说几点个人感受。

  • 算法牛,项目牛,长得又帅,又精通很多语言,这种人,是不多见的。

也就是说,你想拿一个好offer,一般只要有一两点突出,就可以了。

  • 互联网面试,除了个别公司个别面试官,一般都是要考查(考查?考察?这两个词有什么区别?考察是调研、研究、分析的意思)算法的
    。你研一的时候准备,看书做题,总比研三的时候抱佛脚来的好吧?

况且,算法的学习,也不仅仅是为了面试吧?可能是受益终身的。或者,吹牛皮装逼作为谈资,也是可以的。

  • 别轻易说我就要去某某公司。到时候,你会发现,选择比你想象的多。

也有可能比你想象的还少,为什么?

就看你有没有做到我上面说的这些了。

读研如何提高技术之我见,首发于博客 - 伯乐在线

13 Nov 00:38

Linux 动态库相关知识整理

by 伯乐

动态库和静态库在C/C++开发中很常见,相比静态库直接被编译到可执行程序,动态库运行时加载使得可执行程序的体积更小,更新动态库可以不用重新编译可执行程序等诸多好处。作者是一个Linux后台开发,这些知识经常用到,所以整理了一下这方面的知识。静态库相对简单,本文只关心Linux平台下的动态库。

创建动态库

这里我把一个短小却很有用的哈希函数编译成动态库做为示例,ELFhash用于对字符串做哈希,返回一个无符号整数。

//elfhash.h
#include 
unsigned long  ELFhash(const char* key);

//elfhash.c
#include "elfhash.h"
unsigned long ELFhash(const char* key)  
{
  unsigned long h = 0, g;
  while( *key ) {
    h = ( h > 24;
    h &= ~g;
}
  return h;
}

接下来使用gcc编译以上代码,并用ld将编译的目标文件链接成动态库

gcc -fPIC -c -Wall elfhash.c  
ld  -shared elfhash.o -o libelfhash.so

其中-fPIC意思是生成位置无关的代码(Position Independent Code),适用于动态库,在多个进程中共享动态库的同一份代码。ld的-shared选项告诉链接器创建的是动态库。gcc也可以间接调用ld生成动态库

gcc -fPIC -shared -Wall -o libelfhash.so elfhash.c

使用动态库

动态库的使用方法有两种一种是隐式使用,第二种是显式使用。隐式使用的方法很简单。

#include "elfhash.h"
int main()  
{
    printf("%ldn", ElfHash("key-for-test"));
    return 0;
}

显式使用动态库需要借助以下几个函数

#include 
void *dlopen(const char *filename, int flag); //flag可以是RTLD_LAZY,执行共享库中的代码时解决未定义符号,RTLD_NOW则是dlopen返回前解决未定义符号。  
char *dlerror(void); //当发生错误时,返回错误信息  
void *dlsym(void *handle, const char *symbol); //获取符号  
int dlclose(void *handle); //关闭

应用上面几个函数,调用ELFhash实现跟隐式调用一样的功能

#include "elfhash.h"
#include 
#include 

int main() {  
    void *handle;
    unsigned long (*hash)(const char*);
    char *error;
    handle = dlopen ("./libelfhash.so", RTLD_LAZY);
    if (!handle) {
        fputs (dlerror(), stderr);
        exit(1);
    }
    hash = dlsym(handle, "ElfHash");
    if ((error = dlerror()) != NULL)  {
         fputs(error, stderr);
         exit(1);
    }
    printf ("%ldn", (*hash)("key-for-test"));
    dlclose(handle);
}

至此了解以上的知识就可以创建和使用动态库了。 实际应用中我们可能还是会遇到一些问题。

动态库的加载

动态库创建那一节,我演示如何隐式使用动态库,那么编译运行这段代码试一下。

gcc main.c -L./ -lelfhash  
./a.out //执行可执行程序
//以下是输出结果
./a.out: error while loading shared libraries: libelfhash.so: cannot open shared object file: No such file or directory

结果运行时报错,可执行程序找不到动态库。 网上有一些说法是编译时设置-L选项,但在Linux上面证明是不行的(SunOS上可行),这个选项只能在编译链接时有效, 可以让你使用-l如上面的-lelfhash。使用readelf -d a.out可以看到可执行文件依赖的动态库信息。

 0x0000000000000001 (NEEDED)  Shared library: [libelfhash.so]

可以看到这里面并没有包含动态库的路径信息。查阅一下动态链接器的文档man ld-linux.so可以发现这样一句话(有的没有,版本问题)

If a slash is found, then the dependency string is interpreted as a (relative or absolute) pathname, and the library is loaded using that pathname

这段话太长,我只截取一部分,大致就是说,当依赖中有/符号,那么会被解析成动态库加载的路径,隐式使用的例子换一种编译方法。

gcc main.c ./libelfhash.so  
./a.out
23621492 //输出正常

再用readelf -d a.out查看会发现,依赖信息中有了一个路径。

0x0000000000000001 (NEEDED)  Shared library: [./libelfhash.so]

这种方法虽然解决了问题,但是依赖中的路径是硬编码,不是很灵活。 动态链接器是如何查找的动态库的需要进一步查阅文档。关于查找的顺序有点长,这里就不直接引用了,大致是这样:

  1. (仅ELF文件) 使用可执行文件中DT_RPATH区域设置的属性,如果DT_RUNPATH被设置,那么忽略DT_RPATH(在我的Linux对应的是RPATH和RUNPATH)。
  2. 使用环境变量LD_LIBRARY_PATH,如果可执行文件中有set-user-id/set-group-id, 会被忽略。
  3. (仅ELF文件) 使用可执行文件中DT_RUNPATH区域设置的属性
  4. /etc/ld.so.cache缓存文件中查找
  5. 从默认路径/lib, /usr/lib文件目录中查找

我们需要设置RPATH或者RUNPATH,可以这样做

gcc main.c -Wl,-rpath,/home/xxx,--enable-new-dtags -L./  -lelfhash

这里的-Wl选项告诉链接器ld如果如何处理,接下来传递的-rpath(或者使用-R)告诉ld动态库的路径信息(注意-Wl,和后面选项之间不能有空格)。如果没有--enable-new-dtags那么只会设置RPATH,反之,RPATH和RUNPATH会同时被设置。使用readelf -d a.out查看结果:

0x000000000000000f (RPATH)  Library rpath: [/home/xxx]  
0x000000000000001d (RUNPATH)  Library runpath: [/home/xxx]

如果使用环境变量LD_LIBRARY_PATH,那么一般这样用 export

export LD_LIBRARY_PATH=/home/xxx;$LD_LIBRARY_PATH

RPATH和RUNPATH指定动态库的路径,用起来简单,但是也缺乏灵活性,LDLIBRARYPATH在临时测试的也是很有用的,但是在正式环境中,直接使用它也不是好的实践,因为环境变量跟用户的环境关系比较大。动态库不仅要考虑自己使用, 还有分发给别的用户使用的情况。

更通用的方法是使用ldconfig,有几种方法,先在/etc/ld.so.conf.d/目录下创建一个文件,然后把你的动态库路径写进去。或者将你的动态库放到/lib,/lib64(64位),/usr/lib,/usr/lib64(64位)然后运行sudo ldconfig重建/etc/ld.so.cache文件。

动态库版本

通常在使用第三方给的动态库的时候,都是带有版本(文件命名),可以在/usr/lib64下看到很多这样的动态库。现在我重新编译动态库,这次加上版本信息。

gcc -fPIC -shared -Wall -Wl,-soname,libelfhash.so.0  -o libelfhash.so.0.0.0 elfhash.c

每个动态库都有一个名字,如这里的libelfhash.so.0.0.0,叫real name,命名规则跟简单,通常是libxxx.so.MAJOR.MINOR.VERSION(有的时候VERSION会被省略),如果动态库在接口上的兼容性,比如删除了接口或者修改了接口参数,MAJOR增加,如果接口兼容,只是做了更新或者bug修复那么MINOR和VERSION增加。也就是说MAJOR相同的库接口都是兼容的,反之不兼容,如果使用不兼容的动态库需要重新编译可执行程序。

编译动态库时,通过给ld传递连接选项-soname可以指定一个soname, 如这里的libelfhash.so.0 只保留MAJOR,可执行程序运行加载动态库时,会加载这个指定名字的库。

动态库还有一个名字是link name,编译可执行程序时,传个链接器ld的动态库名字,通常是没有版本号以.so结尾的文件名。 一般作法是对soname创建软链。

按照这个规则来命名的动态库可以ldconfig识别,我们把libelfhash.so.0.0.0放到/usr/lib64文件夹中,执行以下指令

$sudo ldconfig -v | grep libelfhash.so
libelfhash.so.0 -> libelfhash.so.0.0.0

可以发现ldconfig根据libelfhash.so.0.0.0的信息,创建了一个soname指向real name的软链,当动态库更新(MINOR,VERSION增加),拷贝新库到相应的位置,再执行sudo ldconfig会自动更新软链指向最新的动态库,动态库更新就完成了。

总结

OK,关于Linux动态库知识整理就到这里了,这些知识虽说都是些基础,少有涉及动态库内部的一些原理,但是却很常用。整理过程中我带着疑问去阅读了ldld-linux.so的文档,收获颇丰。同样,希望本文能帮你解释遇到的部分问题或疑惑。

Linux 动态库相关知识整理,首发于博客 - 伯乐在线

10 Nov 10:52

学习数据结构与算法,成为出色的程序员

by 黄利民

“相较于其它方式,我一直热衷于推崇围绕数据设计代码,我想这也是Git能够如此成功的一大原因[…]在我看来,区别程序员优劣的一大标准就在于他是否认为自己设计的代码还是数据结构更为重要。” —— Linus Torvalds

“优秀的数据结构与简陋的代码组合远比反之的组合更好。” —— Eric S. Raymond, The Cathedral and The Bazaar

学习数据结构与算法分析会让您成为一名出色的程序员。

数据结构与算法分析是一种解决问题的思维模式。 在您的个人知识库中,数据结构与算法分析的相关知识储备越多,您将越多具备应对并解决各类繁杂问题的能力。掌握了这种思维模式,您还将有能力针对新问题提出更多以前想不到的漂亮的解决方案。

您将更深入地了解,计算机如何完成各项操作。无论您是否是直接使用给定的算法,它都影响着您作出的各种技术决定。从计算机操作系统的内存分配到RDBMS的内在工作机制,以及网络协议如何实现将数据从地球的一个角落发送至另一个角落,这些大大小小的工作的完成,都离不开基础的数据结构与算法,理解并掌握它将会让您更了解计算机的运作机理。

对算法广泛深入的学习能为您储备解决方案来应对大体系的问题。之前建模困难时遇到的问题如今通常都能融合进经典的数据结构中得到很好地解决。即使是最基础的数据结构,只要对它进行足够深入的钻研,您将会发现在每天的编程任务中都能经常用到这些知识。

有了这种思维模式,在遇到磨棱两可的问题时,您将能够想出新奇的解决方案。即使最初并没有打算用数据结构与算法解决相应问题的情况,当真正用它们解决这些问题时您会发现它们将非常有用。要意识到这一点,您至少要对数据结构与算法分析的基础知识有深入直观的认识。

理论认识就讲到这里,让我们一起看看下面几个例子。

最短路径问题

我们想要开发一个软件来计算从一个国际机场出发到另一个国际机场的最短距离。假设我们受限于以下路线:

Dijkstra 算法

从这张画出机场各自之间的距离以及目的地的图中,我们如何才能找到最短距离,比方说从赫尔辛基到伦敦?Dijkstra算法是能让我们在最短的时间得到正确答案的适用算法。

在所有可能的解法中,如果您曾经遇到过这类问题,知道可以用Dijkstra算法求解,您大可不必从零开始实现它,只需知道该算法的代码库能帮助您解决相关的实现问题。

如果你深入到该算法的实现中,您将深入理解一项著名的重要图论算法。您会发现实际上该算法比较消耗资源,因此名为A*的扩展经常用于代替该算法。这个算法应用广泛,从机器人寻路的功能实现到TCP数据包路由,以及GPS寻径问题都能应用到这个算法。

先后排序问题

您想要在开放式在线课程MOOC,Massive Open Online Courses平台上(如Udemy或Khan学院)学习某课程,有些课程之间彼此依赖。例如,用户学习牛顿力学Newtonian Mechanics课程前必须先修微积分Calculus课程,课程之间可以有多种依赖关系。用YAML表述举例如下:

# Mapping from course name to requirements
#
# If you're a physcist or a mathematicisn and you're reading this, sincere
# apologies for the completely made-up dependency tree :)
courses:
  arithmetic:         []
  algebra:            [arithmetic]
  trigonometry:       [algebra]
  calculus:           [algebra, trigonometry]
  geometry:           [algebra]
  mechanics:          [calculus, trigonometry]
  atomic_physics:     [mechanics, calculus]
  electromagnetism:   [calculus, atomic_physics]
  radioactivity:      [algebra, atomic_physics]
  astrophysics:       [radioactivity, calculus]
  quantumn_mechanics: [atomic_physics, radioactivity, calculus]

鉴于以上这些依赖关系,作为一名用户,我希望系统能帮我列出必修课列表,让我在之后可以选择任意一门课程学习。如果我选择了微积分(calculus)课程,我希望系统能返回以下列表:

arithmetic -> algebra -> trigonometry -> calculus

这里有两个潜在的重要约束条件:

  • 返回的必修课列表中,每门课都与下一门课存在依赖关系
  • 我们不希望列表中有任何重复课程

这是解决数据间依赖关系的例子,解决该问题的排序算法称作拓扑排序算法tsort,topological sort。它适用于解决上述我们用YAML列出的依赖关系图的情况,以下是在图中显示的相关结果(其中箭头代表需要先修的课程):

拓扑排序算法

拓扑排序算法

拓扑排序算法的实现就是从如上所示的图中找到满足各层次要求的依赖关系。因此如果我们只列出包含radioactivity和与它有依赖关系的子图,运行tsort排序,会得到如下的顺序表:

arithmetic
algebra
trigonometry
calculus
mechanics
atomic_physics
radioactivity

这符合我们上面描述的需求,用户只需选出radioactivity,就能得到在此之前所有必修课程的有序列表。

在运用该排序算法之前,我们甚至不需要深入了解算法的实现细节。一般来说,你可能选择的各种编程语言在其标准库中都会有相应的算法实现。即使最坏的情况,Unix也会默认安装tsort程序,运行man tsort 来了解该程序。

其它拓扑排序适用场合

  • 类似make的工具 可以让您声明任务之间的依赖关系,这里拓扑排序算法将从底层实现具有依赖关系的任务顺序执行的功能。
  • 具有require指令的编程语言适用于要运行当前文件需先运行另一个文件的情况。这里拓扑排序用于识别文件运行顺序以保证每个文件只加载一次,且满足所有文件间的依赖关系要求。
  • 带有甘特图的项目管理工具。甘特图能直观列出给定任务的所有依赖关系,在这些依赖关系之上能提供给用户任务完成的预估时间。我不常用到甘特图,但这些绘制甘特图的工具很可能会用到拓扑排序算法。

霍夫曼编码实现数据压缩

霍夫曼编码Huffman coding是一种用于无损数据压缩的编码算法。它的工作原理是先分析要压缩的数据,再为每个字符创建一个二进制编码。字符出现的越频繁,编码赋值越小。因此在一个数据集中e可能会编码为111,而x会编码为10010。创建了这种编码模式,就可以串联无定界符,也能正确地进行解码。

在gzip中使用的DEFLATE算法就结合了霍夫曼编码与LZ77一同用于实现数据压缩功能。gzip应用领域很广,特别适用于文件压缩(以.gz为扩展名的文件)以及用于数据传输中的http请求与应答。

学会实现并使用霍夫曼编码有如下益处:

  • 您会理解为什么较大的压缩文件会获得较好的整体压缩效果(如压缩的越多,压缩率也越高)。这也是SPDY协议得以推崇的原因之一:在复杂的HTTP请求/响应过程数据有更好的压缩效果。
  • 您会了解数据传输过程中如果想要压缩JavaScript/CSS文件,运行压缩软件是完全没有意义的。PNG文件也是类似,因为它们已经使用DEFLATE算法完成了压缩。
  • 如果您试图强行破译加密的信息,您可能会发现由于重复数据压缩质量更好,密文给定位的数据压缩率将帮助您确定相关的分组密码工作模式block cipher mode of operation

下一步选择学习什么是困难的

作为一名程序员应当做好持续学习的准备。为了成为一名web开发人员,您需要了解标记语言以及Ruby/Python、正则表达式、SQL、JavaScript等高级编程语言,还需要了解HTTP的工作原理,如何运行UNIX终端以及面向对象的编程艺术。您很难有效地预览到未来的职业全景,因此选择下一步要学习哪些知识是困难的。

我没有快速学习的能力,因此我不得不在时间花费上非常谨慎。我希望尽可能地学习到有持久生命力的技能,即不会在几年内就过时的技术。这意味着我也会犹豫这周是要学习JavaScript框架还是那些新的编程语言。

只要占主导地位的计算模型体系不变,我们如今使用的数据结构与算法在未来也必定会以另外的形式继续适用。您可以放心地将时间投入到深入掌握数据结构与算法知识中,它们将会成为您作为一名程序员的职业生涯中一笔长期巨大的财富。

学习数据结构与算法,成为出色的程序员,首发于博客 - 伯乐在线

09 Nov 00:58

TLS 握手优化详解

by JerryQu

随着 HTTP/2 的逐渐普及,以及国内网络环境越来越糟糕(运营商劫持和篡改),HTTPS 已经开始成为主流。HTTPS 在 TCP 和 HTTP 之间增加了 TLS(Transport Layer Security,传输层安全),提供了内容加密、身份认证和数据完整性三大功能,同时也给 Web 性能优化带来新的挑战。上次写的「使用 BoringSSL 优化 HTTPS 加密算法选择」一文中,我介绍了如何针对不同平台启用最合适的传输加密算法。本篇文章我打算继续写 HTTPS 优化 —— TLS 握手优化。

TLS 的前身是 SSL(Secure Sockets Layer,安全套接字层),由网景公司开发,后来被 IETF 标准化并改名。通常没有特别说明时,SSL 和 TLS 指的是同一个协议,不做严格区分。

TLS 握手

在传输应用数据之前,客户端必须与服务端协商密钥、加密算法等信息,服务端还要把自己的证书发给客户端表明其身份,这些环节构成 TLS 握手过程,如下图所示:

tls-handshake

可以看到,假设服务端和客户端之间单次传输耗时 28ms,那么客户端需要等到 168ms 之后才能开始发送 HTTP 请求报文,这还没把客户端和服务端处理时间算进去。光是 TLS 握手就需要消耗两个 RTT(Round-Trip Time,往返时间),这就是造成 HTTPS 更慢的主要原因。当然,HTTPS 要求数据加密传输,加解密相比 HTTP 也会带来额外的开销,不过对称加密本来就很快,加上硬件性能越来越好,所以这部分开销还好。

详细的 TLS 握手过程这里就不介绍了,大家可以通过这两篇文章去了解:

通过 Wireshark 抓包可以清楚地看到完整 TLS 握手过程所需的两个 RTT,如下图:

tls-full-handshake

False Start

False Start 有抢跑的意思,意味着不按规则行事。TLS False Start 是指客户端在发送 Change Cipher Spec Finished 同时发送应用数据(如 HTTP 请求),服务端在 TLS 握手完成时直接返回应用数据(如 HTTP 响应)。这样,应用数据的发送实际上并未等到握手全部完成,故谓之抢跑。这个过程如下图所示:

tls-handshake-with-false-start

可以看到,启用 False Start 之后,TLS 阶段只需要一次 RTT 就可以开始传输应用数据。False Start 相当于客户端提前发送加密后的应用数据,不需要修改 TLS 协议,目前大部分浏览器默认都会启用,但也有一些前提条件:

  • 服务端必须在 Server Hello 握手中通过 NPN(Next Protocol Negotiation,下一代协议协商,Google 在 SPDY 协议中开发的 TLS 扩展,用于握手阶段协商应用协议)或 ALPN(Application Layer Protocol Negotiation,应用层协议协商,NPN 的官方修订版)表明自己支持的 HTTP 协议,例如:http/1.1、http/2;
  • 使用支持前向安全性(Forward Secrecy)的加密算法。False Start 在尚未完成握手时就发送了应用数据,Forward Secrecy 可以提高安全性;

通过 Wireshark 抓包可以清楚地看到 False Start 带来的好处(服务端的 ChangeCipherSpec 出现在 158 号包中,但在之前的 155 号包中,客户端已经发出了请求,相当于 TLS 握手只消耗了一个 RTT):

tls-false-start

Certificate

TLS 的身份认证是通过证书信任链完成的,浏览器从站点证书开始递归校验父证书,直至出现信任的根证书(根证书列表一般内置于操作系统,Firefox 自己维护)。站点证书是在 TLS 握手阶段,由服务端发送的。

Certificate-Chain

配置服务端证书链时,有两点需要注意:1)证书是在握手期间发送的,由于 TCP 初始拥塞窗口的存在,如果证书太长可能会产生额外的往返开销;2)如果证书没包含中间证书,大部分浏览器可以正常工作,但会暂停验证并根据子证书指定的父证书 URL 自己获取中间证书。这个过程会产生额外的 DNS 解析、建立 TCP 连接等开销,非常影响性能。

配置证书链的最佳实践是只包含站点证书和中间证书,不要包含根证书,也不要漏掉中间证书。大部分证书链都是「站点证书 - 中间证书 - 根证书」这样三级,这时服务端只需要发送前两个证书即可。但也有的证书链有四级,那就需要发送站点证书外加两个中间证书了。

通过 Wireshark 可以查看服务端发送的证书链情况,如下图。可以看到本站发送了两个证书,共 2270 字节,被分成 2 个 TCP 段来传输。这已经算小的了,理想的证书链应该控制在 3kb 以内。

tls-certificate

ECC Certificate

如果需要进一步减小证书大小,可以选择 ECC(Elliptic Curve Cryptography,椭圆曲线密码学)证书。256 位的 ECC Key 等同于 3072 位的 RSA Key,在确保安全性的同时,体积大幅减小。下面是一个对比:

对称加密 Key 长度 RSA Key 长度 ECC Key 长度
80 1024 160
112 2048 224
128 3072 256
192 7680 384
256 15360 521

如果证书提供商支持 ECC 证书,使用以下命令生成 CSR(Certificate Signing Request,证书签名请求)文件并提交给提供商,就可以获得 ECC 证书:

openssl ecparam -genkey -name secp256r1 | openssl ec -out ecc.key
openssl req -new -key ecc.key -out ecc.csr

以上命令中可以选择的算法有 secp256r1 和 secp384r1,secp521r1 已被 Chrome 和 Firefox 抛弃。

ECC 证书这么好,为什么没有普及呢?最主要的原因是它的支持情况并不好。例如 Windows XP 不支持,导致使用 ECC 证书的网站在 Windows XP 上只有 Firefox 能访问(Firefox 证书那一套完全自己实现,不依赖操作系统)。另外,Android 平台也只有 Android 4+ 才支持 ECC 证书。所以,确定使用 ECC 证书前需要明确用户系统分布情况。

Session Resumption

另外一个提高 TLS 握手效率的机制是会话复用。会话复用的原理很简单,将第一次握手辛辛苦苦算出来的对称密钥存起来,后续请求中直接使用。这样可以节省证书传送等环节,也可以将 TLS 握手所需 RTT 减少到一个,如下图所示:

tls-handshake-with-session-resumption

可以看到会话复用机制生效时,双方几乎不怎么交换数据就协商好密钥了,这是怎么做到的呢?

Session Identifier

Session Identifier(会话标识符),是 TLS 握手中生成的 Session ID。服务端可以将 Session ID 协商后的信息存起来,浏览器也可以保存 Session ID,并在后续的 ClientHello 握手中带上它,如果服务端能找到与之匹配的信息,就可以完成一次快速握手。

Session Ticket

Session Identifier 机制有一些弊端,例如:1)负载均衡中,多机之间往往没有同步 Session 信息,如果客户端两次请求没有落在同一台机器上就无法找到匹配的信息;2)服务端存储 Session ID 对应的信息不好控制失效时间,太短起不到作用,太长又占用服务端大量资源。

而 Session Ticket(会话记录单)可以解决这些问题,Session Ticket 是用只有服务端知道的安全密钥加密过的会话信息,最终保存在浏览器端。浏览器如果在 ClientHello 时带上了 Session Ticket,只要服务器能成功解密就可以完成快速握手。

配置 Session Ticket 策略后,通过 Wireshark 可以看到服务端发送 Ticket 的过程:

tls-new-session-ticket

以下是 Session Resumption 机制生效时的握手情况,可以看到没有发送证书等环节:

tls-session-ticket

OCSP Stapling

出于某些原因,证书颁发者有时候需要作废某些证书。那么证书使用者(例如浏览器)如何知道一个证书是否已被作废呢?通常有两种方式:CRL(Certificate Revocation List,证书撤销名单)和 OCSP(Online Certificate Status Protocol,在线证书状态协议)。

CRL 是由证书颁发机构定期更新的一个列表,包含了所有已被作废的证书,浏览器可以定期下载这个列表用于验证证书合法性。不难想象,CRL 会随着时间推移变得越来越大,而且实时性很难得到保证。OCSP 是一个在线查询接口,浏览器可以实时查询单个证书的合法性。在每个证书的详细信息中,都可以找到对应颁发机构的 CRL 和 OCSP 地址。

OCSP 的问题在于,某些客户端会在 TLS 握手阶段进一步协商时,实时查询 OCSP 接口,并在获得结果前阻塞后续流程,这对性能影响很大。而 OCSP Stapling(OCSP 封套),是指服务端在证书链中包含颁发机构对证书的 OCSP 查询结果,从而让浏览器跳过自己去验证的过程。服务端有更快的网络,获取 OCSP 响应更容易,也可以将 OCSP 响应缓存起来。

OCSP 响应本身是加密过的,无法伪造,所以 OCSP Stapling 技术既提高了握手效率,也不会影响安全性。启用这项技术后,也通过 Wireshark 来验证:

tls-ocsp-response

可以看到,服务端在发送完证书后,紧接着又发来了它的 OCSP 响应,从而避免了浏览器自己去验证证书造成阻塞。需要注意的是,OCSP Response 只能包含一个证书的验证结果,浏览器还是可能自己去验证中间证书。另外,OCSP Response 本身会占用几 kb 的大小。

OCSP Stapling 功能需要 Web Server 的支持,主流的 Nginx、Apache 和 H2O 都支持 —— 但同时还取决于使用的 SSL 库 —— 例如 BoringSSL 不支持 OCSP Stapling,使用 BoringSSL + Nginx 就无法开启 OCSP Stapling。

如何使用 Nginx 配置本文这些策略,可以参考我之前的文章:本博客 Nginx 配置之性能篇

最后,强烈推荐 Qualys SSL Labs 的SSL Server Test工具,可以帮你查出 HTTPS 很多配置上的问题。本博客的测试结果见这里

本文一部分内容来自于 Google 性能专家 Ilya Grigorik 写的《High Performance Browser Networking》第四章:Transport Layer Security (TLS)。这是一本可以免费在线阅读,一直都在更新的性能优化好书,本博客多次推荐。本书中文翻译由李松峰老师负责,已经出版,名为《WEB 性能权威指南》。

本文链接:https://imququ.com/post/optimize-tls-handshake.html参与讨论

推荐:领略前端技术 阅读奇舞周刊

09 Nov 00:57

一致性哈希算法(consistent hashing)

by studychen

一致性哈希算法(consistent hashing)

The post 一致性哈希算法(consistent hashing) appeared first on 头条 - 伯乐在线.

04 Nov 06:35

微软也有黑科技--Surface Book详细拆解

前几天我们已经拆解过Surface Pro 4,大家对新一代苏菲4的内部结构已经有一些了解,虽然Surface Pro 4已经代表了微软最新的科技,但更受人关注的还是装配了独立显卡的Surface Book,在如此小巧的机身里塞下一块独立显卡。该机采用了显卡集成在键盘中,而主板和处理器部分集成在屏幕中的分体式设计,可谓新意十足,颇具创新性。






04 Nov 00:43

中国技术力量:携程的技术演进之路

by 杜小芳

携程今年动作不断,继5月份收购艺龙后,前不久又宣布了与去哪儿合并,成为国内在线旅游领域当之无愧的霸主。那么一路走来,技术是如何支撑携程成长到今天的地位,我们基于过去三年携程在QCon会议中分享的十几篇技术主题内容,从一个独特视角来下分析下携程技术的演进之路。

在今年11月17日QCon旧金山中国技术开放日专场上,携程旅行网CTO叶亚明(Eric Ye)先生也将上台与大家分享携程的技术演化进程。

基础架构 

携程在多年的发展中不断进行架构优化,逐步采用分布式架构,解耦业务和架构,提高可扩展性,现在已逐步进化到大量采用开源技术的Java + .Net(.Net主要是为了兼容历史业务)混合技术栈。

携程从2013年开始使用OpenStack将基础设施标准化,实现快速部署,帮助减少运维成本。携程曾在QCon上分享过使用OpenStack打造携程私有云的经验,基于OpenStack进行二次开发,综合了KVM,VMware和Docker,网络虚拟化使用了Neutron OpenVSwitch + VLan以及VMware的Nova-VMware-Drive,打造了携程私有云。

携程目前拥有网页Online业务,呼叫中心Offline业务和移动Mobile业务。而在几年前,携程曾有相当比重业务集中在Offline,实施云架构后,以虚拟桌面云替代PC桌面。呼叫中心虚拟云桌面是一个独特的OpenStack应用场景。所有呼叫中心员工办公只需一个云客户端和一个显示器,桌面都运行在云端。虚拟桌面云的整个平台,包括后端对桌面、云终端运维管理、资源分配调度、动态伸缩等功能。经过这样的技术变革后,携程拥有了在线旅游业界规模最大的多呼叫中心。

为了保证后端业务处理的实时性,降低系统耦合度,增加吞吐量和提高可靠性,携程研发了新一代异步消息队列系统Hermes,可实现消息追踪和全面的监控治理。携程在2015年QCon上分享了异步消息队列系统的开发实践经验。

携程拥有多个数据中心,一方面支撑业务的高速成长,另一方面提高网站的高可用性,这些离不开高效的发布和监控体系。在线交易增长背后伴随着应用发布数量、服务器数量、网站流量的不断上升,而运维伴随着携程的发展成为一个新的挑战。携程构建了自己的自动化发布监控体系,不断在人员组织、发布监控工具和流程定义上进行探索和磨合,形成的监控体系能先于人工及时发现网站问题。

搜索技术

携程需要在大量的旅游产品线中快速帮用户找到合适的产品,携程利用后台大规模数据挖掘和实时索引功能不断完善自己的搜索引擎,在Lucene API的基础上,设计开发易扩展的搜索架构,快速支持例如全站搜索等全新产品线。在2014年QCon大会上携程介绍了他们在垂直搜索架构上所做的探索工作。 

安全

携程也会经常遇到一些常见的恶意请求和攻击,如抓数据、恶意扫描、发垃圾信息等等,对用户、服务器、带宽造成损失。携程通过自身摸索,建立了一系列基于大规模日志分析的规则引擎、实时计算的安全分析产品(2014年QCon会议分享)。

移动技术

目前携程无线App上的业务量在携程总业务量所占比重已超过70%,移动技术无疑是重点。携程2014年便在QCon上分享了他们的全新无线系统架构,介绍了H5/Hybrid/Native客户端和服务端(H5/Mobile Service)的架构调整和技术变迁,通过这些技术升级来满足业务发展需求并提高系统稳定性。

为了增强无线服务的稳定性,携程基于Netflix的开源项目Zuul开发了无线Gateway。无线解耦是携程一次里程碑式的技术和业务变迁,无线Gateway为这次变迁提供了重要的支撑。Gateway的职能是负责接收来自无线端的所有API请求,并将他们路由到正确的目标应用服务器,并且提供限流、隔离、熔断等功能,保证了无线服务的长期稳定运行,拥有的弹性容错机制也减少了日常运维工作。同时该Gateway提供了多维度的监控数据,并与报警系统对接,实时监控线上情况,达到运维自动化。

在网络方面,为了优化网络性能,实现连得上,连得快,传输时间短的目标,经过了一系列App端网络性能优化探索,采用了如优化DNS解析和缓存、提供网络服务优先级和依赖机制、优化海外网络性能等手段优化网络性能,并且基于Elastic Search开发了网络实时监控Portal,实时监控所有的网络服务。经过优化后,用户感知到的端到端网络服务成功率达到99.7%以上。

由于携程App下载量已达8亿多,涉及各类机型几十种,对于测试的要求相应提高,需要通过自动化测试提高效率。从2014年开始,携程便开始摸索自己的移动App自动化测试方案,覆盖主流机型,实现无线App持续集成和自动化测试。

移动产品方面,携程从2014年就成立团队研究如何通过可穿戴设备提供旅行相关服务,曾开发过基于Android Wear的Moto360智能手表应用。Apple Watch发布后,旅行作为与Apple Watch时间管理特性契合度较高的场景,携程很快对Apple Watch进行了适配开发,提供了诸如旅行日程、航班动态的通知、待入住酒店地图和导航、发现周边等功能,成为被苹果App Store多次推荐的应用。

总结 

携程从线下到线上的成功转变,离不开技术在背后的强力支撑。从携程的转型之路上我们可以看到,它的技术路线并不激进,在恰当的时机引入合适的技术,是携程的成功之道,携程的技术演进案例,对那些急于技术转型的企业也是很好的启示。

03 Nov 09:25

译文:理解Java中的弱引用

by importnewzz

不久之前,我面试了一些求职Java高级开发工程师的应聘者。我常常会面试他们说,“你能给我介绍一些Java中得弱引用吗?”,如果面试者这样说,“嗯,是不是垃圾回收有关的?”,我就会基本满意了,我并不期待回答是一篇诘究本末的论文描述。

然而事与愿违,我很吃惊的发现,在将近20多个有着平均5年开发经验和高学历背景的应聘者中,居然只有两个人知道弱引用的存在,但是在这两个人之中 只有一个人真正了解这方面的知识。在面试过程中,我还尝试提示一些东西,来看看有没有人突然说一声“原来是这个啊”,结果很是让我失望。我开始困惑,为什 么这块的知识如此不被重视,毕竟弱引用是一个很有用途的特性,况且这个特性已经在7年前 Java 1.2发布时便引入了。

好吧,这里我不期待你看完本文之后成为一个弱引用方面的专家,但是我认为至少你应该了解什么是弱引用,如何使用它们,并且什么场景使用。既然它们是一些不知名的概念,我简单就着前面的三个问题来说明一下。

强引用(Strong Reference)

强引用就是我们经常使用的引用,其写法如下

StringBuffer buffer = new StringBuffer();

上面创建了一个StringBuffer对象,并将这个对象的(强)引用存到变量buffer中。是的,就是这个小儿科的操作(请原谅我这样的说 法)。强引用最重要的就是它能够让引用变得强(Strong),这就决定了它和垃圾回收器的交互。具体来说,如果一个对象通过一串强引用链接可到达 (Strongly reachable),它是不会被回收的。如果你不想让你正在使用的对象被回收,这就正是你所需要的。

但是强引用如此之强

在一个程序里,将一个类设置成不可被扩展是有点不太常见的,当然这个完全可以通过类标记成final实现。或者也可以更加复杂一些,就是通过内部包 含了未知数量具体实现的工厂方法返回一个接口(Interface)。举个例子,我们想要使用一个叫做Widget的类,但是这个类不能被继承,所以无法 增加新的功能。

但是我们如果想追踪Widget对象的额外信息,我们该怎么办? 假设我们需要记录每个对象的序列号,但是由于Widget类并不包含这个属性,而且也不能扩展导致我们也不能增加这个属性。其实一点问题也没有,HashMap完全可以解决上述的问题。

serialNumberMap.put(widget, widgetSerialNumber);

这表面看上去没有问题,但是widget对象的强引用很有可能会引发问题。我们可以确信当一个widget序列号不需要时,我们应该将这个条目从 map中移除。如果我们没有移除的话,可能会导致内存泄露,亦或者我们手动移除时删除了我们正在使用的widgets,会导致有效数据的丢失。其实这些问 题很类似,这就是没有垃圾回收机制的语言管理内存时常遇到的问题。但是我们不用去担心这个问题,因为我们使用的时具有垃圾回收机制的Java语言。

另一个强引用可能带来的问题就是缓存,尤其是像图片这样的大文件的缓存。假设你有一个程序需要处理用户提供的图片,通常的做法就是做图片数据缓存,因为从磁盘加载图片代价很大,并且同时我们也想避免在内存中同时存在两份一样的图片数据。

缓存被设计的目的就是避免我们去再次加载哪些不需要的文件。你会很快发现在缓存中会一直包含一个到已经指向内存中图片数据的引用。使用强引用会强制 图片数据留在内存,这就需要你来决定什么时候图片数据不需要并且手动从缓存中移除,进而可以让垃圾回收器回收。因此你再一次被强制做垃圾回收器该做的工 作,并且人为决定是该清理到哪一个对象。

弱引用(Weak Reference)

弱引用简单来说就是将对象留在内存的能力不是那么强的引用。使用WeakReference,垃圾回收器会帮你来决定引用的对象何时回收并且将对象从内存移除。创建弱引用如下

WeakReference<Widget> weakWidget = new WeakReference<Widget>(widget);

使用weakWidget.get()就可以得到真实的Widget对象,因为弱引用不能阻挡垃圾回收器对其回收,你会发现(当没有任何强引用到widget对象时)使用get时突然返回null。

解决上述的widget序列数记录的问题,最简单的办法就是使用Java内置的WeakHashMap类。WeakHashMap和HashMap 几乎一样,唯一的区别就是它的键(不是值!!!)使用WeakReference引用。当WeakHashMap的键标记为垃圾的时候,这个键对应的条目 就会自动被移除。这就避免了上面不需要的Widget对象手动删除的问题。使用WeakHashMap可以很便捷地转为HashMap或者Map。

引用队列(Reference Queue)

一旦弱引用对象开始返回null,该弱引用指向的对象就被标记成了垃圾。而这个弱引用对象(非其指向的对象)就没有什么用了。通常这时候需要进行一些清理工作。比如WeakHashMap会在这时候移除没用的条目来避免保存无限制增长的没有意义的弱引用。

引用队列可以很容易地实现跟踪不需要的引用。当你在构造WeakReference时传入一个ReferenceQueue对象,当该引用指向的对 象被标记为垃圾的时候,这个引用对象会自动地加入到引用队列里面。接下来,你就可以在固定的周期,处理传入的引用队列,比如做一些清理工作来处理这些没有 用的引用对象。

四种引用

Java中实际上有四种强度不同的引用,从强到弱它们分别是,强引用,软引用,弱引用和虚引用。上面部分介绍了强引用和弱引用,下面介绍剩下的两个,软引用和虚引用。

软引用(Soft Reference)

软引用基本上和弱引用差不多,只是相比弱引用,它阻止垃圾回收期回收其指向的对象的能力强一些。如果一个对象是弱引用可到达,那么这个对象会被垃圾 回收器接下来的回收周期销毁。但是如果是软引用可以到达,那么这个对象会停留在内存更时间上长一些。当内存不足时垃圾回收器才会回收这些软引用可到达的对 象。

由于软引用可到达的对象比弱引用可达到的对象滞留内存时间会长一些,我们可以利用这个特性来做缓存。这样的话,你就可以节省了很多事情,垃圾回收器会关心当前哪种可到达类型以及内存的消耗程度来进行处理。

虚引用 (Phantom Reference)

与软引用,弱引用不同,虚引用指向的对象十分脆弱,我们不可以通过get方法来得到其指向的对象。它的唯一作用就是当其指向的对象被回收之后,自己被加入到引用队列,用作记录该引用指向的对象已被销毁。

当弱引用的指向对象变得弱引用可到达,该弱引用就会加入到引用队列。这一操作发生在对象析构或者垃圾回收真正发生之前。理论上,这个即将被回收的对 象是可以在一个不符合规范的析构方法里面重新复活。但是这个弱引用会销毁。虚引用只有在其指向的对象从内存中移除掉之后才会加入到引用队列中。其get方 法一直返回null就是为了阻止其指向的几乎被销毁的对象重新复活。

虚引用使用场景主要由两个。它允许你知道具体何时其引用的对象从内存中移除。而实际上这是Java中唯一的方式。这一点尤其表现在处理类似图片的大 文件的情况。当你确定一个图片数据对象应该被回收,你可以利用虚引用来判断这个对象回收之后在继续加载下一张图片。这样可以尽可能地避免可怕的内存溢出错 误。

第二点,虚引用可以避免很多析构时的问题。finalize方法可以通过创建强引用指向快被销毁的对象来让这些对象重新复活。然而,一个重写了 finalize方法的对象如果想要被回收掉,需要经历两个单独的垃圾收集周期。在第一个周期中,某个对象被标记为可回收,进而才能进行析构。但是因为在 析构过程中仍有微弱的可能这个对象会重新复活。这种情况下,在这个对象真实销毁之前,垃圾回收器需要再次运行。因为析构可能并不是很及时,所以在调用对象 的析构之前,需要经历数量不确定的垃圾收集周期。这就意味着在真正清理掉这个对象的时候可能发生很大的延迟。这就是为什么当大部分堆被标记成垃圾时还是会 出现烦人的内存溢出错误。

使用虚引用,上述情况将引刃而解,当一个虚引用加入到引用队列时,你绝对没有办法得到一个销毁了的对象。因为这时候,对象已经从内存中销毁了。因为虚引用不能被用作让其指向的对象重生,所以其对象会在垃圾回收的第一个周期就将被清理掉。

显而易见,finalize方法不建议被重写。因为虚引用明显地安全高效,去掉finalize方法可以虚拟机变得明显简单。当然你也可以去重写这个方法来实现更多。这完全看个人选择。

总结

我想看到这里,很多人开始发牢骚了,为什么你要讲一个过去十年的老古董API呢,好吧,以我的经验看,很多的Java程序员并不是很了解这个知识,我认为有一些深入的理解是很必要的,同时我希望大家能从本文中收获一些东西。

可能感兴趣的文章

03 Nov 00:55

Java 资源大全中文版

by 黄利民

Java 资源大全中文版。内容包括:构建工具、数据库、框架、模板、安全、代码分析、日志、第三方库、书籍、Java 站点等等。

The post Java 资源大全中文版 appeared first on 头条 - 伯乐在线.

02 Nov 07:40

男子朋友圈砍价千元买到山寨苹果6s

“0元购手机”“1元购鞋子”……近段时间来,在不少人的微信群以及朋友圈里,类似这样的帮好友砍价的事,层出不穷。面对好友的求助,不少网友毫无顾忌地伸以援手“大砍一刀”。结果真的如此嘛?事实是,微信“砍价”活动看起来很美,但很有可能是“水中月、镜中花”。稍有不慎还泄露了自己个人信息,甚至落入骗局。






01 Nov 01:37

利用 PhantomReference 替代 finalize

by Dozer Zone

问题来源

之前的一篇文章讲了利用 WeakReference 关闭守护线程,守护线程一旦发现守护的对象不在了,就把自己清理掉。

这次的问题更棘手一些,假如一个对象有一些资源需要被关闭,那怎么处理?

很多人会说,这个简单啊!用 Java 的finalize!

但在 Java 中的finalize真的设计得不好,一不小心就会引发很多问题。

 

Java 中的finalize有哪些问题?

  1. 影响 GC 性能,可能会引发OutOfMemoryException
  2. finalize方法中对异常处理不当会影响 GC
  3. 子类中未调用super.finalize会导致父类的finalize得不到执行

总结一下就是:实现finalize对代码的质量要求非常高,一旦使用不当,就容易引发各种问题。

 

PhantomReference

Java 中的各种引用的区别就不说了,网上一搜一大堆。 直接上代码吧。

假设我有这样一个类,内部有一个InputStream并且需要自动close掉它。你只需要这么用就行了:

public class CleanUpExample {
    private InputStream input;

    public CleanUpExample() {
        //todo:init input
        CleanUpHelper.register(this, new CleanUpImpl(input));
    }

    static class CleanUpImpl implements CleanUp {
        private final InputStream input;

        public CleanUpImpl(InputStream input) {
            this.input = input;
        }

        @Override
        public void cleanUp() {
            try {
                if (input != null) {
                    input.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            System.out.println("Success!");
        }
    }
}

看完了业务代码就来看看底层实现吧,先看一下最简单的CleanUp接口:

public interface CleanUp {
    void cleanUp();
}

然后看一下略复杂的CleanUpHelper

public final class CleanUpHelper {

    private CleanUpHelper(){}

    private static volatile boolean started = false;

    private static final int SLEEP_TIME = 10;

    private static final Thread CLEAN_UP_THREAD = new Thread(new Runnable() {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Reference target = REFERENCE_QUEUE.poll();
                    if (target != null) {
                        CleanUp cleanUp = MAPS.remove(target);
                        if (cleanUp != null) {
                            cleanUp.cleanUp();
                            continue;
                        }
                    }
                } catch (RuntimeException ignore) {
                    //add logs
                }

                try {
                    Thread.sleep(SLEEP_TIME);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    });

    private static final Map<Reference<Object>, CleanUp> MAPS = new ConcurrentHashMap<Reference<Object>, CleanUp>();

    private static final ReferenceQueue<Object> REFERENCE_QUEUE = new ReferenceQueue<Object>();

    public static void register(Object watcher, CleanUp cleanUp) {
        init();
        MAPS.put(new PhantomReference<Object>(watcher, REFERENCE_QUEUE), cleanUp);
    }

    private static void init() {
        if (!started) {
            synchronized (CleanUpHelper.class) {
                if (!started) {
                    CLEAN_UP_THREAD.setName("CleanUpThread");
                    CLEAN_UP_THREAD.setDaemon(true);
                    CLEAN_UP_THREAD.start();
                    started = true;
                }
            }
        }
    }
}

最后跑一下测试代码,看看是否能被清理掉:

CleanUpExample item = new CleanUpExample();
item = null;
System.gc();
Thread.sleep(2000);

 

使用过程中的一个坑

CleanUpExample在使用过程中只要实现一下CleanUp接口并且注册一下即可。

看似简单但这里有一个大坑,创建内部类的时候,一定要用静态内部类,而不要使用匿名内部类、成员内部类和局部内部类。

因为只有静态内部类才不会依赖外围类,其它的内部类在编译完成后会隐含地保存着一个引用,该引用是指向创建它的外围内。

这样你的代码又把CleanUpImpl注册到了CleanUpHelper中,最终导致CleanUpExample无法被 GC。

来一个错误的例子:

public class CleanUpExample {
    private InputStream input;

    public CleanUpExample() {
        //todo:init input
        
        CleanUpHelper.register(this, new CleanUp() {
            @Override
            public void cleanUp() {
                try {
                    if (input != null) {
                        input.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

                System.out.println("Success!");
            }
        });
    }
}

我第一次用自己写的CleanUpHelper就是这么写的,匿名内部类多简洁啊,但是,这样写后就无法生效了,一定要注意!

 

为什么不用close方法来解决。

之前提到的守护进程和这次的资源清理,其实只要加一个close方法,在销毁的时候调一下就行了。

但是我们现在做的都是给全公司用的 Java 中间件。用户是不爱看文档的,我以前用别人的中间件也不看;用户也很少回去完整地在finally中去调用close方法。我自己也不喜欢,懒癌发作。

01 Nov 01:37

在 Linux 上如何清除内存的 Cache、Buffer 和交换空间

by 鸟窝

英文原文:How to Clear RAM Memory Cache, Buffer and Swap Space on Linux,
中文翻译:在 Linux 上如何清除内存的 Cache、Buffer 和交换空间by strugglingyouth
像任何其他的操作系统一样,GNU/Linux 已经实现的内存管理不仅有效,而且更好。但是,如果有任何进程正在蚕食你的内存,而你想要清除它的话,Linux 提供了一个刷新或清除RAM缓存方法。

如何在 Linux 中清除缓存(Cache)?

每个 Linux 系统有三种选项来清除缓存而不需要中断任何进程或服务。
(LCTT 译注:Cache,译作“缓存”,指 CPU 和内存之间高速缓存。Buffer,译作“缓冲区”,指在写入磁盘前的存储再内存中的内容。在本文中,Buffer 和 Cache 有时候会通指。)

  1. 仅清除页面缓存(PageCache)
1
# sync; echo 1 > /proc/sys/vm/drop_caches
  1. 清除目录项和inode
1
# sync; echo 2 > /proc/sys/vm/drop_caches
  1. 清除页面缓存,目录项和inode
1
# sync; echo 3 > /proc/sys/vm/drop_caches

上述命令的说明:
sync将刷新文件系统缓冲区(buffer),命令通过“;”分隔,顺序执行,shell在执行序列中的下一个命令之前会等待命令的终止。正如内核文档中提到的,写入到drop_cache将清空缓存而不会杀死任何应用程序/服务,echo命令做写入文件的工作。

如果你必须清除磁盘高速缓存,第一个命令在企业和生产环境中是最安全,"...echo 1> ..."只会清除页面缓存。 在生产环境中不建议使用上面的第三个选项"...echo 3 > ..." ,除非你明确自己在做什么,因为它会清除缓存页,目录项和inodes。

在Linux上释放也许被内核所使用的缓冲区(Buffer)和缓存(Cache)是否是个好主意?

当你设置许多设定想要检查效果时,如果它实际上是专门针对 I/O 范围的基准测试,那么你可能需要清除缓冲区和缓存。你可以如上所示删除缓存,无需重新启动系统(即无需停机)。

Linux被设计成它在寻找磁盘之前到磁盘缓存寻找的方式。如果它发现该资源在缓存中,则该请求不会发送到磁盘。如果我们清理缓存,磁盘缓存就起不到作用了,系统会到磁盘上寻找资源。

此外,当清除缓存后它也将减慢系统运行速度,系统会将每一个被请求的资源再次加载到磁盘缓存中。

现在,我们将创建一个 shell 脚本,通过一个 cron 调度任务在每天下午2点自动清除RAM缓存。如下创建一个 shell 脚本clearcache.sh并在其中添加以下行:

123
#!/bin/bash# 注意,我们这里使用了 "echo 3",但是不推荐使用在产品环境中,应该使用 "echo 1"echo "echo 3 > /proc/sys/vm/drop_caches"

给clearcache.sh文件设置执行权限

1
# chmod 755 clearcache.sh

现在,当你需要清除内存缓存时只需要调用脚本。

现在设置一个每天下午2点的定时任务来清除RAM缓存,打开crontab进行编辑。

1
# crontab -e

添加以下行,保存并退出。

1
0 3 * * * /path/to/clearcache.sh

有关如何创建一个定时任务,更多细节你可以查看我们的文章11 个定时调度任务的例子

在生产环境的服务器上自动清除RAM是否是一个好主意?

不!它不是。想想一个情况,当你已经预定脚本在每天下午2点来清除内存缓存。那么其时该脚本会执行并刷新你的内存缓存。在某一天由于某些原因,可能您的网站的在线用户会超过预期地从你的服务器请求资源。

而在这时,按计划调度的脚本运行了,并清除了缓存中的一切。当所有的用户都从磁盘读取数据时,这将导致服务器崩溃并损坏数据库。因此,清除缓存仅在必要时并且在你的预料之中,否则你就是个呆瓜系统管理员。

如何清除Linux的交换空间?

如果你想清除掉Swap空间,你可以运行下面的命令:

1
# swapoff -a && swapon -a

此外,了解有关风险后,您可以将上面的命令添加到cron中。

现在,我们将上面两种命令结合成一个命令,写成正确的脚本来同时清除RAM缓存和交换空间。

1
# echo 3 > /proc/sys/vm/drop_caches && swapoff -a && swapon -a && printf '\n%s\n' 'Ram-cache and Swap Cleared'

1
su -c 'echo 3 > /proc/sys/vm/drop_caches' && swapoff -a && swapon -a && printf '\n%s\n' 'Ram-cache and Swap Cleared'

在测试上面的命令之前,我们在执行脚本前后运行“free -m” 来检查缓存。
就是这样,如果你喜欢这篇文章,不要忘记向我们提供您宝贵的意见,让我们知道,您认为在企业和生产环境中清除内存缓存和缓冲区是否是一个好主意?

30 Oct 00:31

计算机科学中的最严重错误,造成十亿美元损失

by SamLin

【2015-10-31 更新】补充插播一个改自 StackOverflow 帖子的段子:

杯具啊!我们公司有个职工姓 Null,当用他的姓氏做查询词时,把所有员工查询应用给弄崩溃了! 我该肿么办?

在 1965 年有人提出了这个计算机科学中最糟糕的错误,该错误比 Windows 的反斜线更加丑陋、比 === 更加怪异、比 PHP 更加常见、比 CORS 更加不幸、比 Java 泛型更加令人失望、比 XMLHttpRequest 更加反复无常、比 C 预处理器更加难以理解、比 MongoDB 更加容易出现碎片问题、比 UTF-16 更加令人遗憾。

“我把 Null 引用称为自己的十亿美元错误。它的发明是在1965 年,那时我用一个面向对象语言( ALGOL W )设计了第一个全面的引用类型系统。我的目的是确保所有引用的使用都是绝对安全的,编译器会自动进行检查。但是我未能抵御住诱惑,加入了Null引用,仅仅是因为实现起来非常容易。它导致了数不清的错误、漏洞和系统崩溃,可能在之后 40 年中造成了十亿美元的损失。近年来,大家开始使用各种程序分析程序,比如微软的 PREfix 和 PREfast 来检查引用,如果存在为非 Null 的风险时就提出警告。更新的程序设计语言比如 Spec# 已经引入了非 Null 引用的声明。这正是我在1965年拒绝的解决方案。” —— 《Null References: The Billion Dollar Mistake》托尼·霍尔(Tony Hoare),图灵奖得主

为纪念 Hoare 先生的 null 错误五十周年,这篇文章将会解释何为 null、为什么它这么可怕以及如何避免。

NULL 怎么了?

简单来说:NULL 是一个不是值的值。那么问题来了。

这个问题已经在有史以来最流行的语言中恶化,而它现在有很多名字:NULL、nil、null、None、Nothing、Nil 和 nullptr。每种语言都有自己的细微差别。

NULL 导致的问题,有一些只涉及某种特定的语言,而另一些则是普遍存在的;少量只是某个问题的不同方面。

NULL…

  1. 颠覆类型
  2. 是凌乱的
  3. 是一个特例
  4. 使 API 变得糟糕
  5. 使错误的语言决策更加恶化
  6. 难以调试
  7. 是不可组合的

1. NULL 颠覆类型

静态类型语言不需要实际去执行程序,就可以检查程序中类型的使用,并且提供一定的程序行为保证。

例如,在 Java 中,如果我编写 x.toUppercase(),编译器会检查 x 的类型。如果 x 是一个 String,那么类型检查成功;如果 x 是一个 Socket,那么类型检查失败。

在编写庞大的、复杂的软件时,静态类型检查是一个强大的工具。但是对于 Java,这些很棒的编译时检查存在一个致命缺陷:任何引用都可以是 null,而调用一个 null 对象的方法会产生一个 NullPointerException。所以,

  • toUppercase() 可以被任意 String 对象调用。除非 String 是 null。
  • read() 可以被任意 InputStream 对象调用。除非 InputStream 是 null。
  • toString() 可以被任意 Object 对象调用。除非 Object 是 null。

Java 不是唯一引起这个问题的语言;很多其它的类型系统也有同样的缺点,当然包括 AGOL W 语言。

在这些语言中,NULL 超出了类型检查的范围。它悄悄地越过类型检查,等待运行时,最后一下子释放出一大批错误。NULL 什么也不是,同时又什么都是。

2. NULL 是凌乱的

在很多情况下 null 是没有意义的。不幸的是,如果一种语言允许任何东西为 null,好吧,那么任何东西都可以是 null。

Java 程序员冒着患腕管综合症的风险写下

if (str == null || str.equals("")) {
}

而在 C# 中添加 String.IsNullOrEmpty 是一个常见的语法

if (string.IsNullOrEmpty(str)) {
}

真可恶!

每次你写代码,将 null 字符串和空字符串混为一谈时,Guava 团队都要哭了。– Google Guava

说得好。但是当你的类型系统(例如,Java 或者 C#)到处都允许 NULL 时,你就不能可靠地排除 NULL 的可能性,并且不可避免的会在某个地方混淆。

null 无处不在的可能性造成了这样一个问题,Java 8 添加了 @NonNull 标注,尝试着在它的类型系统中以追溯方式解决这个缺陷。

3. NULL 是一个特例

考虑到 NULL 不是一个值却又起到一个值的作用,NULL 自然地成为各种特别处理方法的课题。

指针

例如,请看下面的 C++ 代码:

char c = 'A';
char *myChar = &c;
std::cout << *myChar << std::endl;

myChar 是一个 char *,意味着它是一个指针——即,将一个内存地址保存到一个 char 中。编译器会对此进行检验。因此,下面的代码是无效的:

char *myChar = 123; // compile error
std::cout << *myChar << std::endl;

因为 123 不保证是一个 char 的地址,所以编译失败。无论如何,如果我们将数字改为 0(在 C++ 中 0 是 NULL),那么可以编译通过:

char *myChar = 0;
std::cout << *myChar << std::endl; // runtime error

123 一样,NULL 实际上不是一个 char 的地址。但是这次编译器还是允许它编译通过,因为 0(NULL)是一个特例。

字符串

还有另一个特例,即发生在 C 语言中以 NULL 结尾的字符串。这与其它的例子有点不同,因为这里没有指针或者引用。但是不是一个值却又起到一个值的作用这个思想还在,此处以不是一个 char 却起到一个 char 的形式存在。

一个 C 字符串是一连串的字节,并且以 NUL (0) 字节结尾。

C-string

因此,C 字符串的每个字符可以是 256 个字节中的任意一个,除了 0(即 NUL 字符)。这不仅使得字符串长度成为一个线性时间的运算;甚至更糟糕,它意味着 C 字符串不能用于 ASCII 或者扩展的 ASCII。相反,它们只能用于不常用的 ASCIIZ。

单个 NUL 字符的例外已经导致无数的错误:API 的怪异行为、安全漏洞和缓冲区溢出。

NULL 是 C 字符串中最糟糕的错误;更确切地说,以 NUL 结尾的字符串是最昂贵的一字节错误

4. NULL 使 API 变得糟糕

下一个例子,我们将会踏上旅程前往动态类型语言的王国,在那里 NULL 将再一次证明它是一个可怕的错误。

键值存储

假设我们创建一个 Ruby 类充当一个键值存储。这可能是一个缓存、一个用于键值数据库的接口等等。我们将会创建简单通用的 API:

class Store
    ##
    # associate key with value
    # 
    def set(key, value)
        ...
    end

    ##
    # get value associated with key, or return nil if there is no such key
    #
    def get(key)
        ...
    end
end

我们可以想象在很多语言中类似的类(Python、JavaScript、Java、C# 等)。

现在假设我们的程序有一个慢的或者占用大量资源的方法,来找到某个人的电话号码——可能通过连通一个网络服务。

为了提高性能,我们将会使用本地存储作为缓存,将一个人名映射到他的电话号码上。

store = Store.new()
store.set('Bob', '801-555-5555')
store.get('Bob') # returns '801-555-5555', which is Bob’s number
store.get('Alice') # returns nil, since it does not have Alice

然而,一些人没有电话号码(即他们的电话号码是 nil)。我们仍然会缓存那些信息,所以我们不需要在后面重新填充那些信息。

store = Store.new()
store.set('Ted', nil) # Ted has no phone number
store.get('Ted') # returns nil, since Ted does not have a phone number

但是现在意味着我们的结果模棱两可!它可能表示:

  1. 这个人不存在于缓存中(Alice)
  2. 这个人存在于缓存中,但是没有电话号码(Tom)

一种情形要求昂贵的重新计算,另一种需要即时的答复。但是我们的代码不够精密来区分这两种情况。

在实际的代码中,像这样的情况经常会以复杂且不易察觉的方式出现。因此,简单通用的 API 可以马上变成特例,迷惑了 null 凌乱行为的来源。

用一个 contains() 方法来修补 Store 类可能会有帮助。但是这引入重复的查找,导致降低性能和竞争条件。

双重麻烦

JavaScript 有相同的问题,但是发生在每个单一的对象

如果一个对象的属性不存在,JS 会返回一个值来表示该对象缺少属性。JavaScript 的设计人员已经选择了此值为 null。

然而他们担心的是当属性存在并且该属性被设为 null 的情况。“有才”的是,JavaScript 添加了 undefined 来区分值为 null 的属性和不存在的属性。

但是如果属性存在,并且它的值被设为 undefined,将会怎样?奇怪的是,JavaScript 在这里停住了,没有提供“超级 undefined”。

JavaScript 提出了不仅一种,而是两种形式的 NULL。

5. NULL 使错误的语言决策更加恶化

Java 默默地在引用和主要类型之间转换。加上 null,事情变得更加奇怪。

例如,下面的代码编译不过:

int x = null; // compile error

这段代码则编译通过:

Integer i = null;
int x = i; // runtime error

虽然当该代码运行时会报出 NullPointerException 的错误。

成员方法调用 null 是够糟糕的;当你从未见过该方法被调用时更糟糕。

6. NULL 难以调试

来解释 NULL 是多么的麻烦,C++ 是一个很好的例子。调用成员函数指向一个 NULL 指针不一定会导致程序崩溃。更糟糕的是:它可能会导致程序崩溃。

#include <iostream>
struct Foo {
    int x;
    void bar() {
        std::cout << "La la la" << std::endl;
    }
    void baz() {
        std::cout << x << std::endl;
    }
};
int main() {
    Foo *foo = NULL;
    foo->bar(); // okay
    foo->baz(); // crash
}

当我用 gcc 编译上述代码时,第一个调用是成功的;第二个则是失败的。

为什么?foo->bar() 在编译时是已知的,所以编译器避免一个运行时虚表查找,并将它转换成一个静态调用,类似 Foo_bar(foo),以此为第一个参数。由于 bar 没有间接引用 NULL 指针,所以它成功运行。但是 baz 有引用 NULL 指针,所以导致一个段错误。

但是解设我们将 bar 变成虚函数。这意味着它的实现可能会被一个子类重写。

    ...
    virtual void bar() {
    ...

作为一个虚函数,foo->bar()foo 的运行时类型做虚表查找,以防 bar() 被重写。由于 foo 是 NULL,现在的程序会在 foo->bar() 这句崩溃,这全都是因为我们把该函数变成虚函数了。

int main() {
    Foo *foo = NULL;
    foo->bar(); // crash
    foo->baz();
}

NULL 已经使得 main 函数的程序员调试这段代码变得非常困难和不直观。

的确,在 C++ 标准中没有定义引用 NULL,所以技术上我们不应该对发生的任何情况感到惊讶。还有,这是一个非病态的、常见的、十分简单的、真实的例子,这个例子是在实践中 NULL 变化无常的众多例子中的一个。

7. NULL 是不可组合的

程序语言是围绕着可组合性构建的:即将一个抽象应用到另一个抽象的能力。这可能是任何语言、库、框架、模型、API 或者设计模式的一个最重要的特征:正交地使用其它特征的能力。

实际上,可组合性确实是很多这类问题背后的基本问题。例如,Store API 返回 nil 给不存在的值与存储 nil 给不存在的电话号码之间不具有可组合性。

C# 用 Nullable 来处理一些关于 NULL 的问题。你可以在类型中包括可选性(为空性)。

int a = 1;     // integer
int? b = 2;    // optional integer that exists
int? c = null; // optional integer that does not exist

但是这造成一个严重的缺陷,那就是 Nullable 不能应用于任何的 T。它仅仅能应用于非空的 T。例如,它不会使 Store 的问题得到任何改善。

  1. 首先 string 可以是空的;你不能创建一个不可空的 string
  2. 即使 string 是不可空的,以此创建 string?可能吧,但是你仍然无法消除目前情况的歧义。没有 string??

解决方案

NULL 变得如此普遍以至于很多人认为它是有必要的。NULL 在很多低级和高级语言中已经出现很久了,它似乎是必不可少的,像整数运算或者 I/O 一样。

不是这样的!你可以拥有一个不带 NULL 的完整的程序语言。NULL 的问题是一个非数值的值、一个哨兵、一个集中到其它一切的特例。

相反,我们需要一个实体来包含一些信息,这些信息是关于(1)它是否包含一个值和(2)已包含的值,如果存在已包含的值的话。并且这个实体应该可以“包含”任意类型。这是 Haskell 的 Maybe、Java 的 Optional、Swift 的 Optional 等的思想。

例如,在 Scala 中,Some[T] 保存一个 T 类型的值。None 没有值。这两个都是 Option[T] 的子类型,这两个子类型可能保存了一个值,也可能没有值。

不熟悉 Maybes/Options 的读者可能会想我们已经把一种没有的形式(NULL)替代为另一种没有的形式(None)。但是这有一个不同点——不易察觉,但是至关重要。

在一种静态类型语言中,你不能通过替代 None 为任意值来绕过类型系统。None 只能用在我们期望一个 Option 出现的地方。可选性显式地表现于类型中。

而在动态类型语言中,你不能混淆 Maybes/Options 和已包含值的用法。

让我们回到先前的 Store,但是这次可能使用 ruby。如果存在一个值,则 Store 类返回带有值的 Some,否则反回 None。对于电话号码,Some 是一个电话号码,None 代表没有电话号码。因此有两级的存在/不存在:外部的 Maybe 表示存在于 Store 中;内部的 Maybe 表示那个名字对应的电话号码。我们已经成功组合了多个 Maybe,这是我们无法用 nil 做到的。

cache = Store.new()
cache.set('Bob', Some('801-555-5555'))
cache.set('Tom', None())

bob_phone = cache.get('Bob')
bob_phone.is_some # true, Bob is in cache
bob_phone.get.is_some # true, Bob has a phone number
bob_phone.get.get # '801-555-5555'

alice_phone = cache.get('Alice')
alice_phone.is_some # false, Alice is not in cache

tom_phone = cache.get('Tom')
tom_phone.is_some # true, Tom is in cache
tom_phone.get.is_some #false, Tom does not have a phone number

本质的区别是不再有 NULL 和其它任何类型之间的联合——静态地类型化或者动态地假设,不再有一个存在的值和不存在的值之间的联合。

使用 Maybes/Options

让我们继续讨论更多没有 NULL 的代码。假设在 Java 8+ 中,我们有一个整数,它可能存在,也可能不存在,并且如果它存在,我们就把它打印出来。

Optional<Integer> option = ...
if (option.isPresent()) {
   doubled = System.out.println(option.get());
}

这样很好。但是大多数的 Maybe/Optional 实现,包括 Java,支持一种更好的实用方法:

option.ifPresent(x -> System.out.println(x));
// or option.ifPresent(System.out::println)

不仅因为这种实用的方法更加简洁,而且它也更加安全。需要记住如果该值不存在,那么 option.get() 会产生一个错误。在早些时候的例子中,get() 受到一个 if 保护。在这个例子中,ifPresent() 完全省却了我们对 get() 的需要。它使得代码明显地没有 bug,而不是没有明显的 bug。

Options 可以被认为是一个最大值为 1 的集合。例如,如果存在值,那么我们可以将该值乘以 2,否则让它空着。

option.map(x -> 2 * x)

我们可以可选地执行一个运算,该运算返回一个可选的值,并且使结果趋于“扁平化”。

option.flatMap(x -> methodReturningOptional(x))

如果 none 存在,我们可以提供一个默认的值:

option.orElseGet(5)

总的来说,Maybe/Option 真正的价值是

  1. 降低关于值存在和不存在的不安全的假设
  2. 更容易安全地操作可选的数据
  3. 显式地声明任何不安全的存在假设(例如,.get() 方法)

不要 NULL!

NULL 是一个可怕的设计缺陷,一种持续不断地、不可估量的痛苦。只有很少语言设法避免它的可怕。

如果你确实选择了一种带 NULL 的语言,那么至少要有意识地在你自己的代码中避免这种不快,并使用等效的 Maybe/Option

常用语言中的 NULL:

“分数”是根据下面的标准来定的:

编辑

评分

对于上述表格的“评分”不要太认真。真正的问题是总结各种语言 NULL 的状态和介绍 NULL 的替代品,并不是为了把常用的语言分等级。

部分语言的信息已经被修正过。出于运行时兼容性的原因,一些语言会有某种 null 指针,但是它们对于语言自身并没有实际的用处。

  • 例子:Haskell 的 Foreign.Ptr.nullPtr 被用于 FFI(Foreign Function Interface),给 Haskell 编组值和从 Haskell 中编组值。
  • 例子:Swift 的 UnsafePointer 必须与 unsafeUnwrap 或者 ! 一起使用。
  • 反例:Scala,尽管习惯性地避免 null,仍然与 Java 一样对待 null,以增强互操作。val x: String = null

什么时候 NULL 是 OK 的?

值得说明的是,当减少 CPU 周期时,一个大小一致的特殊值,像 0 或者 NULL 可以很有用,用代码质量换取性能。当这真正重要的时候,它对于那些低级语言很方便,像 C,但是它真应该离开那里。

真正的问题

NULL 更加常见的问题是哨兵值:这些值与其它值一样,但是有着完全不同的含义。从 indexOf 返回一个整数的索引或者整数 -1 是一个很好的示例。以 NULL 结尾的字符串是另一个例子。这篇文章主要关注 NULL,给出它的普遍性和真实的影响,但是正如 Sauron 仅仅是 Morgoth 的仆人,NULL 也仅仅是基本的哨兵问题的一种形式。

计算机科学中的最严重错误,造成十亿美元损失,首发于博客 - 伯乐在线

29 Oct 00:30

我的后端开发书架 2015 2.0 版

by 伯乐

–小学生作文的开头:光阴似箭,日月如梭…..半年过去了,床底下又多了不少书,更新一个2.0版。

自从技术书的书架设定为”床底下“之后,又多了很多买书的空间。中国什么都贵,就是书便宜。

很多书没有全部看完,看一部分觉得值得推荐就放上来了,但在碎片化的阅读下难免错评,不定期更新修正。

书架主要针对Java后端开发。

更偏爱那些能用简短流畅的话,把少壮不努力的程序员所需的基础补回来的薄书,而有些教课书可能很著名,但干涩枯燥,喋喋不休的把你带回到大学课堂上昏昏欲睡,不录。

 

1. 操作系统与网络的书

《Linux内核设计与实现 – Linux Kernel Development 第3版》
Robert Love用最薄的篇幅,顺畅的文字将Linux内核主要的算法讲清楚了,比《深入理解Linux内核》《深入Linux内核架构》之类厚厚的全是代码的,不是专门的内核程序员看这本足够了。

《Linux系统编程 第2版》
继续是Robert Love,比起APUE也是以薄见长,专门针对重要的系统调用讲解。

《性能之巅》
操作系统的性能调优、监控、工具和方法论,看这本就够了,足够厚。还有本薄一点的,东抄西编格调没那么高的叫《Linux性能优化大师》

《TCP/IP详解 卷1:协议》
这么多年过去了,TCP的书好像还是只有这一本,有点旧了,看了也还是半懂不懂。后人在2011年写了第二版,看目录清晰明了与时俱进了很多,机械工业正在翻译。

《现代操作系统 第3版》
如果看LKD未尽兴,可以回头看看这本基础概念,感觉比那本枯燥的《操作系统概念》(恐龙书)读起来舒服。

PS. 《UNIX环境高级编程》《UNIX网络编程》,APUE和UNP更多作为一本超厚工具书存在。《Unix 编程艺术》,扯的都是闲篇,厚厚的一本其实略读一下就行。

 

2. 算法的书

《数据结构与算法分析-Java语言描述 第2版》
够薄,数据结构与算法分析的点基本都涵盖了,而且示例代码还是Java写的。

《算法 第4版》
可与上一本对比着读,厚一些,也多些图,但知识点没上面的全,也是Java的。

《算法设计与分析基础 第3版》
数学系偏爱无比枯燥很多公式的《算法导论》,计算机系喜欢这本实用主义的典型。翻开就看到一段很文艺很贴心的话:“效率能用数学的严密性进行精确定义,而简单性就像“美”一样,很大程度取决于审视者的眼光。简单的算法更容易理解和实现,因而相应的程序也往往更少的Bug。当然对于简单性的美学诉求也是让人无法抗拒的。”
PS. 《数学之美》《编程珠玑》,都是专栏文章,讲得并不系统,可以当兴趣读物来看。

 

3. 架构设计的书

《恰如其分的软件架构 – 风险驱动的设计方法》
由于人类与生俱来的惰性,计算机原本科学的、精准的设计方式,有了敏捷的借口之后就很难再维持了。本书就是在这种背景下,提出由风险来决定设计的度,当然,这个风险是广义的。除了开始的风险驱动部分,其余部分就是规规矩矩标标准准的架构师教科书。

《软件系统架构:使用视点和视角与利益相关者合作 第2版》
也是教科书,最难得的是,这本老书在十年后的去年升级了第二版。

《程序员必读之软件架构 – Software Architecture for Developers》
作者维护着codingthearchitecture.com 。不过中文书名叫“必读”有点过。薄书里的两部分内容:
1. 编码的架构师:一直是我的职业模板,我记的笔记
2. 架构的表达: 当年我觉得RUP的4+1 UML视图不足以表达系统时,Simon Brown的模板给了很好的过渡范例。

《发布!软件的设计与部署 – Release It!: Design and Deploy Production-Ready Software 》
关于高可靠性的软件,学校里不会教,出来社会却要面对的那部分,英文的原标题更清晰。

《大型网站技术架构:核心原理与案例分析》
淘宝出品,大型互联网站的科普入门书。

《大规模分布式存储系统》
继续淘宝出品,分布式文件系统与数据库的科普入门书。

《大数据日知录》
前几年参加各种技术会议,CAP,最终一致性,RWN,向量时钟,Paxos,一致性哈希,Gossip什么的能灌你一耳朵。而现在,你只要在家安安静静的看书就够了。不过这个领域发展太快,期望它可以持续出新版。

PS. 关于设计模式,我以前曾经有过很多很多本,GOF23啦,企业应用架构模式啦,EIP啦, POSA 5卷本啦,反模式啦,JavaEE/SOA/Restful的模式啦。但现在觉得对新人来说,一本Java写的《Head First 设计模式》,知道什么叫设计模式就够了。

 

4. 语言的书

《Java并发编程实战》
人手一本不用多说了。

《深入理解 Java 虚拟机 第2版》
深入理解虚拟机并不是那么难,Java程序员都该看看,很多知识其实是必须的。另外还有几本类似主题的,忽然一下子都出来了。

《Java性能优化权威指南》
虽然后面的章节好像用处不大,前面有些部分还是值得看,不过Gosling说圣经有点过了。

《写给大忙人看的Java SE 8》
事实上,为了保持兼容性,很多项目还保持在JDK5/6上,这本书一次过将JDK7/JDK8的更新讲了,虽然讲得还不全。

《函数式编程思维》
Java8终于有函数式的影子了,不要落后太多,开始尝试跟上节奏。这本书是TW的Neal Ford面向Java程序员写的薄书。

《七周七语言》
《七周七X》系列的开山之作,可能也是最好的一本。

PS. 《Effective Java》外界一致推崇,但有点太过誉了。另外《Thinking in Java》有点旧了,而且作者思路随意,译者语言晦涩,新程序员还是建议同时再看两卷《Java核心技术 - Core Java》

 

5. 具体技术的书

《Docker: 容器与容器云》
这本书叫Docker一本就够了,的确够了,在那些Docker操作指南书之上,不想着改Docker代码的看它就够了,别想着什么《Docker源码分析》。

《Redis设计与实现》
用Redis的工程师桌面必备吧,不用再多说了。

《从Paxos到Zookeeper》
Zookeeper的书,淘宝出品。

《Spark技术内幕》
深度与厚度之间,选了这本200页的薄书,一样有很多的原理与代码解释,但不会像有的书那样贴20行代码只写一行字。

《Netty权威指南 第2版》
虽然网上的吐槽较多,但Netty 快速入门也只有这一本了。

 

6. 程序员的自我修养

PS. 最近没买什么新书,随便说点旧书:

《程序员修炼之道-从小工到专家》,Pragmatic Programmer-注重实效的程序员开山之作,翻译的马达维文笔也和熊节一样好。

《代码整洁之道》和 《程序员的职业素养》,英文名是很相近的《Clean Code》和 《Clean Coder》,应该接替《代码大全2》成为必看的系列,因为后者太厚了,而且也有不少过时的东西,要自己去过滤。

《重构》很厚,但最有价值就是前面几章的洗脑篇,具体实作不如薄薄的《重构手册》

关于敏捷的书,最开始的那本《解析极限编程–拥抱变化》就很好,再随便找本Scrum的流程看看就够了,《敏捷开发的艺术》也不错。

《布道之道 – Driving Technical Change:Why People on Your Team Don’t Act on Good Ideas,and How to Convince Them They Should》,经常在组织里推行新技术的同学可以看下,七种怀疑论者模式,脑海中一幅幅熟悉的面孔。

PS. 温伯格的书网上很推崇,《成为技术领导者》之类的,但我觉得年代太远,读起来其实没多大意思,一两个鸡汤观点还要自己从书里慢慢淘,有那功夫不如看点别的。

 

7. 没有覆盖到的内容

数据库如MySQL,我们DBA太专业,自己没机会搞。

欢迎大家在评论里补充。

我的后端开发书架 2015 2.0 版,首发于博客 - 伯乐在线