Shared posts

24 Mar 03:17

Java中的字符串常量池

by androidyue

Java中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new String("droid");,这两种方式我们在代码编写时都经常使用,尤其是字面量的方式。然而这两种实现其实存在着一些性能和内存占用的差别。这一切都是源于JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池。

The post Java中的字符串常量池 appeared first on 头条 - 伯乐在线.

04 Jan 10:43

Java不同压缩算法的性能比较

本文将会对常用的几个压缩算法的性能作一下比较。结果表明,某些算法在极端苛刻的CPU限制下仍能正常工作。

文中进行比较的算有:

  • JDK GZIP ——这是一个压缩比高的慢速算法,压缩后的数据适合长期使用。JDK中的java.util.zip.GZIPInputStream / GZIPOutputStream便是这个算法的实现。
  • JDK deflate ——这是JDK中的又一个算法(zip文件用的就是这一算法)。它与gzip的不同之处在于,你可以指定算法的压缩级别,这样你可以在压缩时间和输出文件大小上进行平衡。可选的级别有0(不压缩),以及1(快速压缩)到9(慢速压缩)。它的实现是java.util.zip.DeflaterOutputStream / InflaterInputStream。
  • LZ4压缩算法Java实现——这是本文介绍的算法中压缩速度最快的一个,与最快速的deflate相比,它的压缩的结果要略微差一点。如果想搞清楚它的工作原理,我建议你读一下这篇文章。它是基于友好的Apache 2.0许可证发布的。
  • Snappy——这是Google开发的一个非常流行的压缩算法,它旨在提供速度与压缩比都相对较优的压缩算法。我用来测试的是这个实现。它也是遵循Apache 2.0许可证发布的。

压缩测试

要找出哪些既适合进行数据压缩测试又存在于大多数Java开发人员的电脑中(我可不希望你为了运行这个测试还得个几百兆的文件)的文件也着实费了我不少工夫。最后我想到,大多数人应该都会在本地安装有JDK的文档。因此我决定将javadoc的目录整个合并成一个文件——拼接所有文件。这个通过tar命令可以很容易完成,但并非所有人都是Linux用户,因此我写了个程序来生成这个文件:

public class InputGenerator {
    private static final String JAVADOC_PATH = "your_path_to_JDK/docs";
    public static final File FILE_PATH = new File( "your_output_file_path" );
 
    static
    {
        try {
            if ( !FILE_PATH.exists() )
                makeJavadocFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    private static void makeJavadocFile() throws IOException {
        try( OutputStream os = new BufferedOutputStream( new FileOutputStream( FILE_PATH ), 65536 ) )
        {
            appendDir(os, new File( JAVADOC_PATH ));
        }
        System.out.println( "Javadoc file created" );
    }
 
    private static void appendDir( final OutputStream os, final File root ) throws IOException {
        for ( File f : root.listFiles() )
        {
            if ( f.isDirectory() )
                appendDir( os, f );
            else
                Files.copy(f.toPath(), os);
        }
    }
}

在我的机器上整个文件的大小是354,509,602字节(338MB)。

测试

一开始我想把整个文件读进内存里,然后再进行压缩。不过结果表明这么做的话即便是4G的机器上也很容易把堆内存空间耗尽。

于是我决定使用操作系统的文件缓存。这里我们用的测试框架是JMH。这个文件在预热阶段会被操作系统加载到缓存中(在预热阶段会先压缩两次)。我会将内容压缩到ByteArrayOutputStream流中(我知道这并不是最快的方法,但是对于各个测试而言它的性能是比较稳定的,并且不需要花费时间将压缩后的数据写入到磁盘里),因此还需要一些内存空间来存储这个输出结果。

下面是测试类的基类。所有的测试不同的地方都只在于压缩的输出流的实现不同,因此可以复用这个测试基类,只需从StreamFactory实现中生成一个流就好了:

@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
@Fork(1)
@Warmup(iterations = 2)
@Measurement(iterations = 3)
@BenchmarkMode(Mode.SingleShotTime)
public class TestParent {
    protected Path m_inputFile;
 
    @Setup
    public void setup()
    {
        m_inputFile = InputGenerator.FILE_PATH.toPath();
    }
 
    interface StreamFactory
    {
        public OutputStream getStream( final OutputStream underlyingStream ) throws IOException;
    }
 
    public int baseBenchmark( final StreamFactory factory ) throws IOException
    {
        try ( ByteArrayOutputStream bos = new ByteArrayOutputStream((int) m_inputFile.toFile().length());
              OutputStream os = factory.getStream( bos ) )
        {
            Files.copy(m_inputFile, os);
            os.flush();
            return bos.size();
        }
    }
}

这些测试用例都非常相似(在文末有它们的源代码),这里只列出了其中的一个例子——JDK deflate的测试类;

public class JdkDeflateTest extends TestParent {
    @Param({"1", "2", "3", "4", "5", "6", "7", "8", "9"})
    public int m_lvl;
 
    @Benchmark
    public int deflate() throws IOException
    {
        return baseBenchmark(new StreamFactory() {
            @Override
            public OutputStream getStream(OutputStream underlyingStream) throws IOException {
                final Deflater deflater = new Deflater( m_lvl, true );
                return new DeflaterOutputStream( underlyingStream, deflater, 512 );
            }
        });
    }
}

测试结果

输出文件的大小

首先我们来看下输出文件的大小:

||实现||文件大小(字节)|| ||GZIP||64,200,201|| ||Snappy (normal)||138,250,196|| ||Snappy (framed)|| 101,470,113|| ||LZ4 (fast)|| 98,316,501|| ||LZ4 (high) ||82,076,909|| ||Deflate (lvl=1) ||78,369,711|| ||Deflate (lvl=2) ||75,261,711|| ||Deflate (lvl=3) ||73,240,781|| ||Deflate (lvl=4) ||68,090,059|| ||Deflate (lvl=5) ||65,699,810|| ||Deflate (lvl=6) ||64,200,191|| ||Deflate (lvl=7) ||64,013,638|| ||Deflate (lvl=8) ||63,845,758|| ||Deflate (lvl=9) ||63,839,200||

image

可以看出文件的大小相差悬殊(从60Mb到131Mb)。我们再来看下不同的压缩方法需要的时间是多少。

压缩时间

||实现||压缩时间(ms)|| ||Snappy.framedOutput ||2264.700|| ||Snappy.normalOutput ||2201.120|| ||Lz4.testFastNative ||1056.326|| ||Lz4.testFastUnsafe ||1346.835|| ||Lz4.testFastSafe ||1917.929|| ||Lz4.testHighNative ||7489.958|| ||Lz4.testHighUnsafe ||10306.973|| ||Lz4.testHighSafe ||14413.622|| ||deflate (lvl=1) ||4522.644|| ||deflate (lvl=2) ||4726.477|| ||deflate (lvl=3) ||5081.934|| ||deflate (lvl=4) ||6739.450|| ||deflate (lvl=5) ||7896.572|| ||deflate (lvl=6) ||9783.701|| ||deflate (lvl=7) ||10731.761|| ||deflate (lvl=8) ||14760.361|| ||deflate (lvl=9) ||14878.364|| ||GZIP ||10351.887||

image

我们再将压缩时间和文件大小合并到一个表中来统计下算法的吞吐量,看看能得出什么结论。

吞吐量及效率

||实现||时间(ms)||未压缩文件大小||吞吐量(Mb/秒)||压缩后文件大小(Mb)|| ||Snappy.normalOutput ||2201.12 ||338 ||153.5581885586 ||131.8454742432|| ||Snappy.framedOutput ||2264.7 ||338 ||149.2471409017 ||96.7693328857|| ||Lz4.testFastNative ||1056.326 ||338 ||319.9769768045 ||93.7557220459|| ||Lz4.testFastSafe ||1917.929 ||338 ||176.2317583185 ||93.7557220459|| ||Lz4.testFastUnsafe ||1346.835 ||338 ||250.9587291688 ||93.7557220459|| ||Lz4.testHighNative ||7489.958 ||338 ||45.1270888301 ||78.2680511475|| ||Lz4.testHighSafe ||14413.622 ||338 ||23.4500391366 ||78.2680511475|| ||Lz4.testHighUnsafe ||10306.973 ||338 ||32.7933332124 ||78.2680511475|| ||deflate (lvl=1) ||4522.644 ||338 ||74.7350443679 ||74.7394561768|| ||deflate (lvl=2) ||4726.477 ||338 ||71.5120374012 ||71.7735290527|| ||deflate (lvl=3) ||5081.934 ||338 ||66.5101120951 ||69.8471069336|| ||deflate (lvl=4) ||6739.45 ||338 ||50.1524605124 ||64.9452209473|| ||deflate (lvl=5) ||7896.572 ||338 ||42.8033835442 ||62.6564025879|| ||deflate (lvl=6) ||9783.701 ||338 ||34.5472536415 ||61.2258911133|| ||deflate (lvl=7) ||10731.761 ||338 ||31.4952969974 ||61.0446929932|| ||deflate (lvl=8) ||14760.361 ||338 ||22.8991689295 ||60.8825683594|| ||deflate (lvl=9) ||14878.364 ||338 ||22.7175514727 ||60.8730316162|| ||GZIP ||10351.887 ||338 ||32.651051929 ||61.2258911133||

image

可以看到,其中大多数实现的效率是非常低的:在Xeon E5-2650处理器上,高级别的deflate大约是23Mb/秒,即使是GZIP也就只有33Mb/秒,这大概很难令人满意。同时,最快的defalte算法大概能到75Mb/秒,Snappy是150Mb/秒,而LZ4(快速,JNI实现)能达到难以置信的320Mb/秒!

从表中可以清晰地看出目前有两种实现比较处于劣势:Snappy要慢于LZ4(快速压缩),并且压缩后的文件要更大。相反,LZ4(高压缩比)要慢于级别1到4的deflate,而输出文件的大小即便和级别1的deflate相比也要大上不少。

因此如果需要进行“实时压缩”的话我肯定会在LZ4(快速)的JNI实现或者是级别1的deflate中进行选择。当然如果你的公司不允许使用第三方库的话你也只能使用deflate了。你还要综合考虑有多少空闲的CPU资源以及压缩后的数据要存储到哪里。比方说,如果你要将压缩后的数据存储到HDD的话,那么上述100Mb/秒的性能对你而言是毫无帮助的(假设你的文件足够大的话)——HDD的速度会成为瓶颈。同样的文件如果输出到SSD硬盘的话——即便是LZ4在它面前也显得太慢了。如果你是要先压缩数据再发送到网络上的话,最好选择LZ4,因为deflate75Mb/秒的压缩性能跟网络125Mb/秒的吞吐量相比真是小巫见大巫了(当然,我知道网络流量还有包头,不过即使算上了它这个差距也是相当可观的)。

总结

  • 如果你认为数据压缩非常慢的话,可以考虑下LZ4(快速)实现,它进行文本压缩能达到大约320Mb/秒的速度——这样的压缩速度对大多数应用而言应该都感知不到。

  • 如果你受限于无法使用第三方库或者只希望有一个稍微好一点的压缩方案的话,可以考虑下使用JDK deflate(lvl=1)进行编解码——同样的文件它的压缩速度能达到75Mb/秒。

源代码

Java压缩测试源码

原创文章转载请注明出处:Java不同压缩算法的性能比较

英文原文链接

04 Jan 00:56

中国互联网工程师如何获Google的工作机会

by 投稿 (guest)

  Google在国内被封,其实有点像清朝时期的闭关锁国,闭关锁国严重影响了国内的经济发展,导致中国经济衰弱与其他国家。屏蔽谷歌对于中国的科研和学术具有重大负面影响,不利于中国的科技创新和发展,因此未来解封谷歌也是很有可能的,让我们静静的等待。

  本文是Mountain View Google总部的华人工程师朋友小T,以及Google中国的HR朋友Y小姐提供,截图出自 http://google.com/careers 。

  不知道答主是学生还是职场人士,那就分两块都说说吧:Google校招 实习-转正;Google社招 申请/内推。

  1. 学生—校招

  学生还可以通过「实习+转正」获得Google的工作机会,并且这个途径与直接申请全职相比过程要简单一些,录取几率也相对大一些。

  1、登陆官网 Google Careers ↑↑↑,查找职位↓↓↓,投递简历。

  2、一般一周左右之后收到回复,如果通过简历筛选,Recruitor 联系询问各种信息,来回几次后,安排电话面试时间。

  3、两轮电话面试—都是技术面。

  4、一轮小组match—电面通过之后进入candidate pool等team match.

  5、转正—加面两轮,加上组里的力推,最终获得offer.

  事实上,基本上大的IT公司都是这个路数,比如Facebook的套路就差不多一模一样。

  什么时候申请?要趁早

  其实,一年大部分时间http://google.com/careers 都有opening,但是暑假职位最多,一般技术实习生在前一年10月就可以开始申请了。一句话,僧多粥少,填坑要趁早,等不到match的host就吃亏了。

  简历怎么写?如实写

  很多人喜欢在简历里面夸张,说自己什么语言都精通,什么架构都熟悉。最好别这么干,要知道Google里面牛人众多,HR会针对你的简历去邀请精通的员工面试,你要是不熟悉还说精通,一问就fail了。

  还不知道怎么写?戳这里:程序员简历应该怎么写? - 周萌萌的回答

  电话面试的内容?直接考察coding skill

  技术面试不外乎考察算法和数据结构。形式很简单,面试官提问题,让你谈思路,如果他觉得思路对了会让你在google doc上写下来。至于问题和难度,要看运气和考官了,variety和随机性都很大。

  面试中要注意什么?keep talking

  电话面试中要注意:即便是思考的时候最好也keep talking,避免出现尴尬的冷场。

  也就是说面试官问你什么题目,你通常会马上想到一个最简单也最麻烦的方法,别犹豫,马上说出来,让面试官知道你的思路,然后再讨论怎么改进。如果你闷着头自己去想最优解,电话那头的面试官压根就不知道你在干嘛,说不定还以为你不知道怎么解呢。

  如果你不知道答案,也不要怕,先说自己的思路,让面试官知道你的思考过程。用小T的话说,「题目用尽量好的办法做对固然很重要,但是让别人知道你的思考方式,以及保持轻松愉快的谈话氛围,留下好的印象也很重要。」

  怎么准备面试?推荐两个网站

  1、Career Cup,有各大公司的最新面试题集锦,你懂的。

  2、mitbbs(买买提)—Job hunting版,有面经,也有解题交流。

  Google实习待遇怎样?临时工/合同工<实习生<正式员工

  通过面试后就开始愉快的Google实习生生活啦~

  在总部,可以说,实习生的待遇处于正式员工和临时工/合同工之间。除了没有股票,和一些正式员工才有的benefit外,其他的待遇和正式员工没有区别,还可以才参加每周一次的TGIF大会——这身临时工/合同工不能参加的。

  对了,海外实习生还有一笔一次性的relocation fee.

  具体待遇自己去glassdoor上查吧,挺准的,不细说了,怕被拍>< anyway,总部待遇比中国好很多很多…… ……

  如何实习转正?面试两轮+推荐

  实习转正就只需要再加面两轮,加上组里的力推就好。实习转正成功了,可以留在原来的组也可以选择其他的组,看个人意愿和match.

  比较一下,如果不是实习转正,那么一般的全职招聘需要电面2~3轮,onsite面试3~5轮,最后还有个committee根据所有的反馈和材料决定是否录用,过程比较辛苦。

  英语要求?不要求TOEFL成绩

  口语流利,沟通顺畅,不要求托福等成绩。另外,听力要好,最好能适应多种口音,尤其是印度英语,否则碰到个印度哥么听不懂题目就悲剧了。

  美国实习一定比国内实习好吗?不一定

  虽然美国实习福利多多,但是答主个人并不建议国内的同学们直接来美国实习。因为拿到实习offer去美国的签证是J1签证,而J1有个两年回国服务条款,意味着实习结束回国两年才能申请美国工作签证。

  唯一的例外,就是申请豁免waiver.

  但是申请豁免很难!!首先需要中国大使馆出具no object letter,最大的一条是申请时必须在美J1身份至少半年,并且还有半年有效期停留。这个过程有很多tricky的地方。为此我的朋友小T把半年的实习延长成一年,虽然最后豁免申请成功了,但是用他的话说「纯粹是运气好而已。」

  其实,国内谷歌实习后转正也可以让你选美国和中国,所以直接申请国内的实习也挺好,转正的机会是公平的。

  2. 在职场中的程序员-Google社招

  虽然在硅谷Google的offer已经没有airbnb/uber/dropbox等新贵有竞争力了,但是对中国的程序员来说Google的offer还是很有吸引力的,毕竟Google招的人多啊!

  ↓↓↓(Google IPO之后的雇员数量实在是节节攀升……)

  Google的社招流程是怎样的?

  社招和校招的流程基本一致,即:

  官网查找职位-申请/朋友内推-HR筛选简历-电话面试-技术面试-committee审核-发offer

  大家一般知道,Google是不用猎头的,候选人可以自己申请,HR也会主动去找符合要求的候选人。另外,朋友推荐的一般更能得到HR的重视。

  筛选简历的标准怎样?

  教育背景/工作背景/技术水平都是HR关注的部分,简历除了给HR看,也会给相应的技术负责人看。

  教育背景:首先,并不要求必须是名校,名校在建立筛选有加分,但是不是必要条件。其次,也不一定是计算机专业。事实上,Google中国的员工中不乏数学、物理、自动化等学历背景。

  工作背景:无论曾经服务的是大公司,还是小公司,HR更关注做过的项目和成长水平。毕竟,BAT的人太多了,如果仅仅是BAT工作经验,不一定能有面试机会,还要看做的项目,以及他的技术水平,在项目组中负责的事务相关。

  怎样面试?

  社招的面试流程很容易拉得比较长。首先一般是1~2的电面,然后最多不超过5轮面对面,所有的面试都是一对一。和前面说过的校招一样,电面着重考察的是候选人的沟通能力和技术水平。在面对面的面试中,一般还会考察领导力、团队协作等非技术能力。

  怎样发offer?

  通过面试的候选人同样可以挑选工作地点,无论是国内还是国外。虽然Google招的不是特别多,但是每年申请去海外工作的候选人挺多的,去海外工作也会有relocation fee和高薪。

  (看看Google北美的职位所在地 ↓↓↓ 貌似是一条技术移民的捷径啊…… ……)

  和BAT一样,Google也有针对技术人员的内部等级,薪资和等级也是相关的,不同国家和区域的薪资当然可能有不小的差距,但是Google的薪资都是在当地市场相对有竞争力的。

  最后,来个小结:

  任何的面试都需要充足的准备,你真的相信大神们真的是随便申申就通过了吗?

  无论是社招还是校招,为了通过面试你至少需要:

  1. 提前做好充足的准备。熟悉简历上的每一个项目,每一条经历;以及复习编程算法,巩固基础,适量刷面试题。

  2. 提高沟通能力,技术面试中最为重要的就是向面试官展示你的思考过程和分析解决问题的能力。

  3. 提高心理承受能力,在有限时间内写出bug free的解法真的需要一颗冷静的头脑和一颗坚强的心脏。

  来源:知乎周萌萌,原文链接

评论《中国互联网工程师如何获Google的工作机会》的内容...

相关文章:


微博:新浪微博 - 微信公众号:williamlonginfo
月光博客投稿信箱:williamlong.info(at)gmail.com
Created by William Long www.williamlong.info
04 Jan 00:42

如何更好地学习机器学习?

by Daetalus

Metacademy的创始人Colorado Reed发布过一篇名为“机器学习练级攻略”,文中回答了初学者经常问他的一个问题:如何才能更好地学习机器学习?这篇文章将总结Colorado的建议并分步讲解他文中的路线图。

如何更好地掌握机器学习

Colorado是伯克利大学的在读博士,同时也是Metacademy的创始人。Metacademy是一个优秀的开源平台,许多专业人员共同在这个平台上编写wiki文章。目前,这些文章主要围绕着机器学习和人工智能这两个主题。

在Colorado的建议中,更好地学习机器学习的方法就是不断的通过书本学习。他认为读书的目的就是让心中有书。

一个博士在读生给出这样的建议并不令人惊讶,以前本站可能还推荐过类似的建议。这个建议还可以,但我不认为适用每个人。如果你是个开发者,想实现机器学习的算法。下面列出的书籍是一个很好的参考,可以从中逐步学习。

机器学习路线图

他的关于机器学习的路线图分为5个级别,每个级别都对应一本书必须要掌握的书。这5个级别如下:

  • Level 0(新手):阅读《Data Smart: Using Data Science to Transform Information into Insight》。需要了解电子表格、和一些算法的高级数据流。
  • Level 1(学徒):阅读《Machine Learning with R》。学习在不同的情况下用R语言应用不同的机器学习算法。需要一点点基本的编程、线性代数、微积分和概率论知识。
  • Level 2(熟练工):阅读《Pattern Recognition and Machine Learning》。从数学角度理解机器学习算法的工作原理。理解并调试机器学习方法的输出结果,同时对机器学习的概念有更深的了解。需要有算法、较好的线性代数、一些向量积分、一些算法实现经验。
  • Level 3(大师):阅读《Probabilistic Graphical Models: Principles and Techniques》。深入了解一些高级主题,如凸优化、组合优化、概率论、微分几何,及其他数学知识。深入了解概率图模型,了解何时应该使用以及如何解释其输出结果。
  • Leval 4(宗师):随便去学吧,记得反馈社区。

Colorado针对每个级别中列出的书中章节阅读建议,并给出了建议去了解的相关顶级项目。

Colorado后来重新发布了一篇博客,其中对这个路线图做了一点修改。他移除了最后一个级别,并如下定义了新的级别:好奇者、新手、学徒、熟练工、大师。他说道,Level 0中的机器学习好奇者不应该阅读相关书籍,而是浏览观看与机器学习有关的顶级视频。

机器学习中被忽视的主题

Scott Locklin也阅读了Colorado的那篇博客,并从中受到了启发,写了一篇相应的文章,名为“机器学习中被忽视的想法”(文中有Boris Artzybasheff绘制的精美图片)。

Scott认为Colorado给出的建议并没有充分的介绍机器学习领域。他认为很少有书籍能做到这一点,不过他还是喜欢Peter Flach所著的《Machine Learning: The Art and Science of Algorithms that Make Sense of Data》这本书,因为书中也接触了一些隐晦的技术。

Scott列出了书本中过分忽视的内容。如下所示:

  • 实时学习:对流数据和大数据很重要,参见Vowpal Wabbit
  • 强化学习:在机器人方面有过讨论,但很少在机器学习方面讨论。
  • “压缩”序列预测技术:压缩数据发现学习模式。参见CompLearn
  • 面向时间序列的技术。
  • 一致性预测:为实时学习精确估计模型。
  • 噪声背景下的机器学习:如NLP和CV。
  • 特征工程:机器学习成功的关键。
  • 无监督和半监督学习。

这个列表很好的指出了机器学习中没有注意到的领域。

最后要说明的是,我自己也有一份关于机器学习的路线图。与Colorado一样,我的路线图仅限于分类/回归类型的监督机器学习,但还在完善中,需要进一步的调查和添加所有感兴趣的主题。与前面的“读这些书就可以了”不同,这个路线图将会给出详细的步骤。

如何更好地学习机器学习?,首发于博客 - 伯乐在线

02 Jan 09:13

Runtime-Compiled C++

31 Dec 06:41

Linus:为何对象引用计数必须是原子的

by Leo

(感谢网友  @我的上铺叫路遥  投稿)

Linus大神又在rant了!这次的吐槽对象是时下很火热的并行技术(parellism) ,并直截了当地表示并行计算是浪费所有人时间(“The whole “let’s parallelize” thing is a huge waste of everybody’s time.”)。大致意思是说乱序性能快、提高缓存容量、降功耗 。当然笔者不打算正面讨论并行的是是非非(过于宏伟的主题),因为Linus在另一则帖子中举了对象引用计数(reference counting) 的例子来说明并行的复杂性。

在Linus回复之前有人指出对象需要锁机制的情况下,引用计数的原子性问题:

Since it is being accessed in a multi-threaded way, via multiple access paths, generally it needs its own mutex — otherwise, reference counting would not be required to be atomic and a lock of a higher-level object would suffice.

由于(对象)通过多线程方式及多种获取渠道,一般而言它需要自身维护一个互斥锁——否则引用计数就不要求是原子的,一个更高层次的对象锁足矣。

而Linus不那么认为:

The problem with reference counts is that you often need to take them *before* you take the lock that protects the object data.

引用计数的问题在于你经常需要在对象数据上锁保护之前 完成它。

The thing is, you have two different cases:

问题有两种情况:

- object *reference* 对象引用

- object data 对象数据

and they have completely different locking.

它们锁机制是完全不一样的。

Object data locking is generally per-object. Well, unless you don’t have huge scalability issues, in which case you may have some external bigger lock (extreme case: one single global lock).

对象数据保护一般是一个对象拥有一个锁,假设你没有海量扩展性问题,不然你需要一些外部大一点的锁(极端的例子,一个对象一个全局锁)。

But object *referencing* is mostly about finding the object (and removing/freeing it). Is it on a hash chain? Is it in a tree? Linked list? When the reference count goes down to zero, it’s not the object data that you need to protect (the object is not used by anything else, so there’s nothing to protect!), it’s the ways to find the object you need to protect.

但对象引用主要关于对象的寻找(移除或释放),它是否在哈希链,一棵树或者链表上。当对象引用计数降为零,你要保护的不是对象数据,因为对象没有在其它地方使用,你要保护的是对象的寻找操作。

And the lock for the lookup operation cannot be in the object, because – by definition – you don’t know what the object is! You’re trying to look it up, after all.

而且查询操作的锁不可能在对象内部,因为根据定义,你还不知道这是什么对象,你在尝试寻找它。

So generally you have a lock that protects the lookup operation some way, and the reference count needs to be atomic with respect to that lock.

因此一般你要对查询操作上锁,而且引用计数相对那个锁 来说是原子的(译者注:查询锁不是引用计数所在的对象所有,不能保护对象引用计数,后面会解释为何引用计数变更时其所在对象不能上锁)。

And yes, that lock may well be sufficient, and now you’re back to non-atomic reference counts. But you usually don’t have just one way to look things up: you might have pointers from other objects (and that pointer is protected by the object locking of the other object), but there may be multiple such objects that point to this (which is why you have a reference count in the first place!)

当然这个锁是充分有效的,现在假设引用计数是非原子的,但你常常不仅仅使用一种方式来查询:你可能拥有其它对象的指针(这个指针又被其它对象的对象锁给保护起来),但同时还会有多个对象指向它(这就是为何你第一时间需要引用计数的理由)。

See what happens? There is no longer one single lock for lookup. Imagine walking a graph of objects, where objects have pointers to each other. Each pointer implies a reference to an object, but as you walk the graph, you have to release the lock from the source object, so you have to take a new reference to the object you are now entering.

看看会发生什么?查询不止存在一个锁保护。你可以想象走过一张对象流程图,其中对象存在指向其它对象的指针,每个指针暗含了一次对象引用,但当你走过这个流程图,你必须释放源对象的锁,而你进入新对象时又必须增加一次引用。

And in order to avoid deadlocks, you can not in the general case take the lock of the new object first – you have to release the lock on the source object, because otherwise (in a complex graph), how do you avoid simple ABBA deadlock?

而且为了避免死锁,你一般不能立即对新对象上锁——你必须释放源对象的锁,否则在一个复杂流程图里,你如何避免ABBA死锁 (译者注:假设两个线程,一个是A->B,另一个B->;A,当线程一给A上锁,线程二给B上锁,此时两者谁也无法释放对方的锁)?

So atomic reference counts fix that. They work because when you move from object A to object B, you can do this:

原子引用计数修正了这一点,当你从对象A到对象B,你会这样做:

(a) you have a reference count to A, and you can lock A

对象A增加一次引用计数,并上锁。

(b) once object A is locked, the pointer from A to B is stable, and you know you have a reference to B (because of that pointer from A to B)

对象A一旦上锁,A指向B的指针就是稳定的,于是你知道你引用了对象B。

(c) but you cannot take the object lock for B (ABBA deadlock) while holding the lock on A

但你不能在对象A上锁期间给B上锁(ABBA死锁)。

(d) increment the atomic reference count on B

对象B增加一次原子引用计数。

(e) now you can drop the lock on A (you’re “exiting” A)

现在你可以扔掉对象A的锁(退出对象A)。

(f) your reference count means that B cannot go away from under you despite unlocking A, so now you can lock B.

对象B的原子引用计数意味着即使给A解锁期间,B也不会失联,现在你可以给B上锁。

See? Atomic reference counts make this kind of situation possible. Yes, you want to avoid the overhead if at all possible (for example, maybe you have a strict ordering of objects, so you know you can walk from A to B, and never walk from B to A, so there is no ABBA deadlock, and you can just lock B while still holding the lock on A).

看见了吗?原子引用计数使这种情况成为可能。是的,你想尽一切办法避免这种代价,比如,你也许把对象写成严格顺序的,这样你可以从A到B,绝不会从B到A,如此就不存在ABBA死锁了,你也就可以在A上锁期间给B上锁了。

But if you don’t have some kind of forced ordering, and if you have multiple ways to reach an object (and again – why have reference counts in the first place if that isn’t true!) then atomic reference counts really are the simple and sane answer.

但如果你无法做到这种强迫序列,如果你有多种方式接触一个对象(再一次强调,这是第一时间使用引用计数的理由),这样,原子引用计数就是简单又理智的答案。

If you think atomic refcounts are unnecessary, that’s a big flag that you don’t actually understand the complexities of locking.

如果你认为原子引用计数是不必要的,这就大大说明你实际上不了解锁机制的复杂性。

Trust me, concurrency is hard. There’s a reason all the examples of “look how easy it is to parallelize things” tend to use simple arrays and don’t ever have allocations or freeing of the objects.

相信我,并发设计是困难的。 所有关于“并行化如此容易”的理由都倾向于使用简单数组操作做例子,甚至不包含对象的分配和释放。

People who think that the future is highly parallel are invariably completely unaware of just how hard concurrency really is. They’ve seen Linpack, they’ve seen all those wonderful examples of sorting an array in parallel, they’ve seen all these things that have absolutely no actual real complexity – and often very limited real usefulness.

那些认为未来是高度并行化的人一成不变地完全没有意识到并发设计是多么困难。他们只见过Linpack,他们只见过并行技术中关于数组排序的一切精妙例子,他们只见过一切绝不算真正复杂的事物——对真正的用处经常是非常有限的。

(译者注:当然,我无意借大神之口把技术宗教化。实际上Linus又在另一篇帖子中综合了对并行的评价。)

Oh, I agree. My example was the simple case. The really complex cases are much worse.

哦,我同意。我的例子还算简单,真正复杂的用例更糟糕。

I seriously don’t believe that the future is parallel. People who think you can solve it with compilers or programming languages (or better programmers) are so far out to lunch that it’s not even funny.

我严重不相信未来是并行的。有人认为你可以通过编译器,编程语言或者更好的程序员来解决问题,他们目前都是神志不清,没意识到这一点都不有趣。

Parallelism works well in simplified cases with fairly clear interfaces and models. You find parallelism in servers with independent queries, in HPC, in kernels, in databases. And even there, people work really hard to make it work at all, and tend to expressly limit their models to be more amenable to it (eg databases do some things much better than others, so DB admins make sure that they lay out their data in order to cater to the limitations).

并行计算可以在简化的用例以及具备清晰的接口和模型上正常工作。你发现并行在服务器上独立查询里,在高性能计算(High-performance computing)里,在内核里,在数据库里。即使如此,人们还得花很大力气才能使它工作,并且还要明确限制他们的模型来尽更多义务(例如数据库要想做得更好,数据库管理员得确保数据得到合理安排来迎合局限性)。

Of course, other programming models can work. Neural networks are inherently very parallel indeed. And you don’t need smarter programmers to program them either..

当然,其它编程模型倒能派上用场,神经网络(neural networking)天生就是非常并行化的,你不需要更聪明的程序员为之写代码。

参考资料

(全文完)

(转载本站文章请注明作者和出处酷 壳 – CoolShell.cn,请勿用于任何商业用途)

——===访问酷壳404页面寻找遗失儿童。===——
31 Dec 01:00

慕课分享:盘点2014年热门免费开发课程

by aoi

这篇文章来自慕课网团队的分享,文章从前端开发、后端开发、移动开发和OS 四个方面,分享了2014年度的热门免费课程。

前端开发

HTML+CSS基础课程

这门课程从最基本的概念开始讲起,步步深入,带领大家学习HTML、CSS样式基础知识,了解各种常用标签的意义以及基本用法,后半部分讲解CSS样式代码添加,为后面的案例课程打下基础。

HTML5之元素与标签结构

知识与实例相结合,本部分是HTML5课程的基础内容,主要讲解HTML5的标签结构,与传统的HTML4相比,新增和删去的标签及相关属性,并深入拓展了全局属性的相关知识。

HTML5离线应用实战演练

这门课程将探索FT Web App背后的神秘面纱,并且手把手教您如何利用AppCache,localStorage等HTML5技术打造一个真正提供离线阅读体验的RSS阅读器,你将掌握如何搭建一个离线的Web App的框架。

JavaScript入门篇

这门课程让您快速认识JavaScript,熟悉基本语法、窗口交互方法和通过DOM进行网页元素的操作,学会如何编写JS代码,如何运用JavaScript去操作HTML元素和CSS样式,为JavaScript深入学习打下基础。

JavaScript进阶篇

做为WEB攻城师必备技术JavaScript,这门课程从如何插入JS代码开始,学习JS基础语法、语法、函数、方法等,让你掌握JS编程思路、知识的使用等,实现运用JS语言为网页增加动态效果,达到与用户交互的目的。

jQuery基础课程

这门课程分为11个章节,前四个章节重点介绍jQuery中选择器的使用方法,第五、六章节介绍jQuery如何操作DOM元素,在第七、八章中,详细介绍jQuery中的动画特效和Ajax的调用过程,从第九章到最后,着重介绍与jQuery相关的常用插件和UI插件的使用方法。

jQuery源码解析(架构与依赖模块)

这门课程是目前真正意义上第一部完整的jQuery 2.1.1版本源码课程,课程分14章,先从整体入手理解架构,再逐个攻破每个依赖模块包括回调函数、异步加载、数据缓存、动画队列等。最后整合分析选择器、DOM处理、事件、AJAX、动画模块。

电商网站前端架构

通过一个垂直电子商务网站,介绍前端架构搭建和实现的步骤和方法,以及在这个过程中我们需要做什么才能帮助项目最终从设计走向实现。

手把手教你实现电商网站开发

电商网站基本制作流程,通过整站分步的教学让学员了解和掌握电商网站制作的流程和注意事项,运用网站内学过的知识点的连接掌握整站的开发过程,增加开发经验。

后端开发

Java

Java 入门第一季

Java语言已经成为当前软件开发行业中主流的开发语言。这门课程将介绍Java环境搭建、工具使用、基础语法。带领大家一步一步的踏入Java达人殿堂!Let’s go!

Java 入门第二季

这门课程是程序猿质变课程,理解面向对象的思想,掌握面向对象的基本原则以及 Java 面向对象编程基本实现原理,熟练使用封装、继承、多态面向对象三大特性,带你进一步探索 Java 世界的奥秘!

Java 入门第三季

这门课程将带领小伙伴们进一步探索 Java 的奥秘,将带来关于异常处理、集合框架、字符串的操作和常用类的使用的相关介绍。

反射——Java高级开发必须懂的

掌握了反射的知识,才能更好的学习Java高级课程,因此必须要学习——你懂的!本门课程主要介绍Class类的使用,方法和成员变量的反射,以及通过反射了解集合泛型的本质等知识。

深入浅出 Java 多线程

多线程是日常开发中的常用知识,也是难用知识。通过这门课程,你可以了解与多线程相关的基本概念,如何创建,启动和停止线程?什么是正确的多线程,怎样编写多线程程序。在掌握基础之后,将为你展望进阶路线,为进一步的学习提供方向。

Spring入门篇

Spring是为解决企业应用程序开发复杂性而创建的一个开源框架,应用非常广泛。业内非常流行的SSH架构中的其中一个”S”指的就是Spring。这门课程作为Spring的入门级课程,将结合实例为您带来依赖注入、IOC和AOP的基本概念及用法,为后续高级课程的学习打下基础。

PHP

在Ubuntu Server下搭建LAMP环境

这门课程通过详细的细分教学,让你理解LAMP环境各个组件之间的关系与作用,并能掌握从无到有的在Ubuntu Server操作系统下搭建LAMP环境、配置虚拟主机、数据库远程维护等常见的服务器搭建维护技能,为学习PHP铺平道路。

PHP入门篇

轻松学习PHP基础知识,了解PHP中的变量、变量的类型、常量等概念,认识PHP中的运算符,掌握PHP中顺序结构、条件结构、循环结构三种语言结构语句。

PHP进阶篇

通过PHP学习的进阶篇的学习,你可以对PHP的理论知识由浅入深有更深一步的掌握,这些知识能够使您更加全面的掌握PHP,从而助您在实际工作中使用PHP快速开发网站程序。

PHP面向对象编程

这门课程通过讲述面向对象的基本概念以及相关的案例实践,让小伙伴们对面向对象有一个基本的认识,能够掌握把实际问题抽象成为类对象用以解决实际问题的方法,掌握面向对象的最重要的核心能力。

大话PHP设计模式

通过实际的代码演示PHP的11种面向对象设计模式实现和使用,帮助PHPer具备使用设计模式解决工程中复杂逻辑的能力,并且对OOP中松耦合、依赖倒置、可替换性、配置化等哲学有一定了解。

MVC架构模式分析与设计

这门课程前部分通过学习MVC理论知识,引入MVC设计简单的框架的方法,并带领学生掌握对框架MVC三层框架简化的方法。后半部分通过视图层的介绍引入smarty的概念,详细介绍smarty的语法,通过实例再次加深对smarty的理解,并演示smarty各个内置函数与php、自定义函数的用法。

移动开发

Android

与Android Studio的第一次亲密接触

Android Studio是Google在I/O大会上发布的一个新的集成开发环境,可以让Android开发变的更简单。本课程会详细的向您介绍Android Studio的安装配置、使用技巧以及相对于Eclipse开发的优势,并通过实际的操作让大家快速熟悉Android Studio的使用,让您体验更强大的开发工具。

Android攻城狮的第一门课(入门篇)

这门课程涵盖全部Android应用开发的基础,根据技能点的作用分为5个篇章,包括环境篇、控件篇、布局篇、组件篇和通用篇,本课程的目标就是“看得懂、学得会、做得出”,为后续的学习打下夯实的基础。

Android中常用高级控件详解汇总

这门课程由浅入深地带您学会Android的常用控件的开发和使用,以知识概念为主导,实例代码为驱动,带您走入一个神奇的移动开发世界。

Android中的消息提示、菜单与动画

这门课程讲带你熟悉Android开发中常用的调试方式,各种对话框,各种提示菜单,各种动画效果等,来进一步充实你的Android知识

Android中的数据存储、组件与手势

这门课程深入的讲解了Android中的手势识别原理、数据存储方式、对组件的详细剖析以及对Service的深度讲解,每一个知识点都对应有实际案例。这门课程内容不仅使您牢牢掌握之前的基础知识点,学完之后也会有新的收获、新的体会。

iOS

征战Objective-C

这门课程作为零基础学习Objective-c语言的优秀教程,教学核心在于教会学生像计算机一样思考,并且使用符合OC编程哲学的方式写出如其他苹果产品一般优雅的程序,最终为作出优秀的IOS与Mac OS应用程序打好基础。

使用Swift开发iOS8 App实战

这个系列会从零开始学习iOS的开发,主要包括UIKit的使用,通过学习这个系列课程,同学们可以了解开发界面相关的应用以及自己动手开发一个真实的ios APP。

Swift Weather APP

这门课程将带领大家使用Swift语言开发一个完整的天气 iOS APP。同时大家能够学习到Interface Builder、CocoaPods、Core Location、AFNetworking的使用,以及如何通过Swift调用Objective-C组件,如何通过IBOutlets更新View。

一起来做价值百万的Apple Watch App:分歧终端机

这门课程展示了如何使用WatchKit,如何通过MVC的绑定来处理用户事件,如何制作动画的功能。

Linux

Linux Guide for Developers

Linux 的知识点浩如烟海,其实作为开发者,日常用到的只是里面的一小部分。但是初学者是很难去判别哪些知识是超范围的,那么就让这门课程成为你初入 Linux 世界的一个向导。

Linux达人养成计划 I

这门课程以通俗易懂的语言、风趣幽默的实例、清晰严谨的逻辑介绍了Linux的基础内容。课程以CentOS操作系统为例,为你带来Linux的简介、系统安装和常用命令等内容。在轻松的氛围中感受到Linux之美。

Linux 达人养成计划 II

这门课程介绍Linux系统下操作VI编辑器、创建文本文件、VI的三种操作模式、磁盘分区与格式化、用户及用户组权限的相关操作与管理等,让童鞋们对Linux系统有进一步的理解,对Linux服务器的维护操作更加得心应手。

Linux网络管理

Linux装好以后是不能和网络中的其他机器进行通信的,这门课程会为你解决Linux网络配置的问题。首先会介绍网络基础知识,然后进行IP地址的配置,并总结了在配置网络环境中经常遇到的问题,最后介绍了几种常用远程登录工具的使用,如XShell和SecureCRT。

慕课分享:盘点2014年热门免费开发课程,首发于博客 - 伯乐在线

31 Dec 00:59

Java中有关Null的9件事

by Calarence

对于Java程序员来说,null是令人头痛的东西。时常会受到空指针异常(NPE)的骚扰。连Java的发明者都承认这是他的一项巨大失误。Java为什么要保留null呢?null出现有一段时间了,并且我认为Java发明者知道null与它解决的问题相比带来了更多的麻烦,但是null仍然陪伴着Java。

我越发感到惊奇,因为java的设计原理是为了简化事情,那就是为什么没有浪费时间在指针、操作符重载、多继承实现的原因,null却与此正好相反。好吧,我真的不知道这个问题的答案,我知道的是不管null被Java开发者和开源社区如何批评,我们必须与null共同存在。与其为null的存在感到后悔,我们倒不如更好的学习null,确保正确使用null。

为什么在Java中需要学习null?因为如果你对null不注意,Java将使你遭受空指针异常的痛苦,并且你也会得到一个沉痛的教训。精力充沛的编程是一门艺术,你的团队、客户和用户将会更加欣赏你。以我的经验来看,导致空指针异常的一个最主要的原因是对Java中null的知识还不够。你们当中的很多已经对null很熟悉了,但是对那些不是很熟悉的来说,可以学到一些关于null老的和新的知识。让我们一起重新学习Java中null的一些重要知识吧。

Java中的Null是什么?

正如我说过的那样,null是Java中一个很重要的概念。null设计初衷是为了表示一些缺失的东西,例如缺失的用户、资源或其他东西。但是,一年后,令人头疼的空指针异常给Java程序员带来不少的骚扰。在这份材料中,我们将学习到Java中null关键字的基本细节,并且探索一些技术来尽可能的减少null的检查以及如何避免恶心的空指针异常。

1)首先,null是Java中的关键字,像public、static、final。它是大小写敏感的,你不能将null写成Null或NULL,编译器将不能识别它们然后报错。

Object obj = NULL; // Not Ok
Object obj1 = null  //Ok
使用其他语言的程序员可能会有这个问题,但是现在IDE的使用已经使得这个问题变得微不足道。现在,当你敲代码的时候,IDE像Eclipse、Netbeans可以纠正这个错误。但是使用其他工具像notepad、Vim、Emacs,这个问题却会浪费你宝贵时间的。

2)就像每种原始类型都有默认值一样,如int默认值为0,boolean的默认值为false,null是任何引用类型的默认值,不严格的说是所有object类型的默认值。就像你创建了一个布尔类型的变量,它将false作为自己的默认值,Java中的任何引用变量都将null作为默认值。这对所有变量都是适用的,如成员变量、局部变量、实例变量、静态变量(但当你使用一个没有初始化的局部变量,编译器会警告你)。为了证明这个事实,你可以通过创建一个变量然后打印它的值来观察这个引用变量,如下图代码所示:

private static Object myObj;
public static void main(String args[]){
    System.out.println("What is value of myObjc : " + myObj);
}
What is value of myObjc : null

这对静态和非静态的object来说都是正确的。就像你在这里看到的这样,我将myObj定义为静态引用,所以我可以在主方法里直接使用它。注意主方法是静态方法,不可使用非静态变量。

3)我们要澄清一些误解,null既不是对象也不是一种类型,它仅是一种特殊的值,你可以将其赋予任何引用类型,你也可以将null转化成任何类型,来看下面的代码:

String str = null; // null can be assigned to String
Integer itr = null; // you can assign null to Integer also
Double dbl = null;  // null can also be assigned to Double

String myStr = (String) null; // null can be type cast to String
Integer myItr = (Integer) null; // it can also be type casted to Integer
Double myDbl = (Double) null; // yes it's possible, no error
你可以看到在编译和运行时期,将null强制转换成任何引用类型都是可行的,在运行时期都不会抛出空指针异常。

4)null可以赋值给引用变量,你不能将null赋给基本类型变量,例如int、double、float、boolean。如果你那样做了,编译器将会报错,如下所示:

int i = null; // type mismatch : cannot convert from null to int
short s = null; //  type mismatch : cannot convert from null to short
byte b = null: // type mismatch : cannot convert from null to byte
double d = null; //type mismatch : cannot convert from null to double

Integer itr = null; // this is ok
int j = itr; // this is also ok, but NullPointerException at runtime
正如你看到的那样,当你直接将null赋值给基本类型,会出现编译错误。但是如果将null赋值给包装类object,然后将object赋给各自的基本类型,编译器不会报,但是你将会在运行时期遇到空指针异常。这是Java中的自动拆箱导致的,我们将在下一个要点看到它。

5) 任何含有null值的包装类在Java拆箱生成基本数据类型时候都会抛出一个空指针异常。一些程序员犯这样的错误,他们认为自动装箱会将null转换成各自基本类型的默认值,例如对于int转换成0,布尔类型转换成false,但是那是不正确的,如下面所示:

Integer iAmNull = null;
int i = iAmNull; // Remember - No Compilation Error
但是当你运行上面的代码片段的时候,你会在控制台上看到主线程抛出空指针异常。在使用HashMap和Integer键值的时候会发生很多这样的错误。当你运行下面代码的时候就会出现错误。
import java.util.HashMap;
import java.util.Map;

/**
 * An example of Autoboxing and NullPointerExcpetion
 * 
 * @author WINDOWS 8
 */
public class Test {
    public static void main(String args[]) throws InterruptedException {
      Map numberAndCount = new HashMap<>();
      int[] numbers = {3, 5, 7,9, 11, 13, 17, 19, 2, 3, 5, 33, 12, 5};

      for(int i : numbers){
         int count = numberAndCount.get(i);
         numberAndCount.put(i, count++); // NullPointerException here
      }       
    }
}

输出:

Exception in thread "main" java.lang.NullPointerException
 at Test.main(Test.java:25)

这段代码看起来非常简单并且没有错误。你所做的一切是找到一个数字在数组中出现了多少次,这是Java数组中典型的寻找重复的技术。开发者首先得到以前的数值,然后再加一,最后把值放回Map里。程序员可能会以为,调用put方法时,自动装箱会自己处理好将int装箱成Interger,但是他忘记了当一个数字没有计数值的时候,HashMap的get()方法将会返回null,而不是0,因为Integer的默认值是null而不是0。当把null值传递给一个int型变量的时候自动装箱将会返回空指针异常。设想一下,如果这段代码在一个if嵌套里,没有在QA环境下运行,但是你一旦放在生产环境里,BOOM:-)

6)如果使用了带有null值的引用类型变量,instanceof操作将会返回false:

Integer iAmNull = null;
if(iAmNull instanceof Integer){
   System.out.println("iAmNull is instance of Integer");                             

}else{
   System.out.println("iAmNull is NOT an instance of Integer");
}

输出:

i
AmNull is NOT an instance of Integer

这是instanceof操作一个很重要的特性,使得对类型强制转换检查很有用

7)你可能知道不能调用非静态方法来使用一个值为null的引用类型变量。它将会抛出空指针异常,但是你可能不知道,你可以使用静态方法来使用一个值为null的引用类型变量。因为静态方法使用静态绑定,不会抛出空指针异常。下面是一个例子:

public class Testing {             
   public static void main(String args[]){
      Testing myObject = null;
      myObject.iAmStaticMethod();
      myObject.iAmNonStaticMethod();                             
   }

   private static void iAmStaticMethod(){
        System.out.println("I am static method, can be called by null reference");
   }

   private void iAmNonStaticMethod(){
       System.out.println("I am NON static method, don't date to call me by null");
   }

输出:

I am static method, can be called by null reference
Exception in thread "main" java.lang.NullPointerException
               at Testing.main(Testing.java:11)

8)你可以将null传递给方法使用,这时方法可以接收任何引用类型,例如public void print(Object obj)可以这样调用print(null)。从编译角度来看这是可以的,但结果完全取决于方法。Null安全的方法,如在这个例子中的print方法,不会抛出空指针异常,只是优雅的退出。如果业务逻辑允许的话,推荐使用null安全的方法。

9)你可以使用==或者!=操作来比较null值,但是不能使用其他算法或者逻辑操作,例如小于或者大于。跟SQL不一样,在Java中null==null将返回true,如下所示:

public class Test {

    public static void main(String args[]) throws InterruptedException {

       String abc = null;
       String cde = null;

       if(abc == cde){
           System.out.println("null == null is true in Java");
       }

       if(null != null){
           System.out.println("null != null is false in Java"); 
       }

       // classical null check
       if(abc == null){
           // do something
       }

       // not ok, compile time error
       if(abc > null){

       }
    }
}

输出:

null == null is true in Java

这是关于Java中null的全部。通过Java编程的一些经验和使用简单的技巧来避免空指针异常,你可以使你的代码变得null安全。因为null经常作为空或者未初始化的值,它是困惑的源头。对于方法而言,记录下null作为参数时方法有什么样的行为也是非常重要的。总而言之,记住,null是任何一个引用类型变量的默认值,在java中你不能使用null引用来调用任何的instance方法或者instance变量。

相关文章

30 Dec 00:47

Java在现实生活中都用在哪些项目?

by Calarence

如果你是一个初学者,刚刚开始学习Java,你可能会想Java用在什么地方。你可能会想,除了《我的世界》这款游戏外其他游戏很少用Java开发,桌面工具如Adobe Acrobat、Microsoft Office也没有用Java开发,甚至操作系统Linux、Windows也没用。那么人们到底会在什么地方用到Java呢?

Java到底有没有现实世界的应用?好吧,其实并不是只有你一个人对这个问题感到困惑。很多程序员在开始Java之前或者在毕业时选择Java作为编程语言时也问过相同的问题。顺便说一下,你可以通过在你计算机上安装Java获得Java在哪些地方使用的相关线索。Oracle声称超三十亿的设备上运行着Java,那是一个巨大的数字,不是吗?大多数公司用一种或者其他方式使用Java。很多服务器端使用Java来处理每天数十百万的请求,高频率的交易应用同样也使用Java,例如LMAX交易程序,这套程序基于他们开创性的线程间通讯库——Disruptor。在这篇文章中,我们将会看到一些更精确的例子。什么种类的项目使用Java开发?在哪些领域内Java是占统治地位的?现实生活中Java到底用在什么地方?

现实生活中的Java应用

在现实生活中,很多地方都用到了Java,从商业上的电子商务网站到安卓App,从科学应用到经济应用,如电子交易系统,从游戏如《我的世界》到桌面应用,如Eclipse、Netbeans、Interllij,从开源类库到J2ME应用。让我们更详细地看一下它们。

1)安卓应用

如果你想弄清楚Java用在什么地方,你离这个目标不是太远。打开你的安卓手机和任何一款App,它们是使用Java语言、基于Google Android API(和JDK类似)开发的。数年的安卓支持已经有了很大提高,并且很多Java程序员已经成为了安卓app开发者。顺便提一下,像我们之前在how Android app works这篇文章中说到的那样,安卓使用不同的java虚拟机、不同的包,但是代码仍是用Java写的。

2)金融业服务器的应用

在金融服务中Java有很重要的应用。很多全球投资银行像高盛、花旗、巴克莱、标准渣打银行等其他银行使用Java来开发前台和后台电子交易系统,提供解决方案和确认系统以及数据处理项目等等。Java大多数用在开发服务器端的应用,几乎不用来开发前端,前端是从一个服务器接受数据,然后处理它并把它发送给其他进程。Java Swing对交易员来说在开发胖客户端的GUI方面还是很受欢迎的,但是现在C#很快地在这个领域获得了市场占有率,Java Swing在这方面被挤压喘不过气来。

3)Java Web应用

Java在电子商务和Web应用领域也是有很多的应用。现在有很多使用Spring MVC、Structs2.0和类似框架开发的RESTful风格的服务。甚至件一个简单的依赖Servlet、JSP和Structs开发的web应用在各种各样的政府项目中很受欢迎。政府的很多部门如卫生局、保险部门、国防部等部门都有他们使用Java开发的web应用。

4)软件工具

很多有用的软件和开发工具是用Java开发的,例如Eclipse、InetelliJ、Netbeans IDE。我认为大多数使用的桌面应用也是用Java开发的。所以有一段时间,Swing在开发胖客户端方面非常流行,尤其是在金融行业和投资银行。现在,Java FX正逐渐受到欢迎,但是仍不能成为Swing的替代品,而C#在金融领域内已经几乎代替了Swing。

5)交易应用

第三方应用交易应用,作为更大的金融服务业的一部分也使用Java开发。流行的交易应用像Murex也是用Java开发的,很多银行都使用它们来连接前后端。

6)J2ME应用

虽然iOS和Android的出现几乎抹杀了J2ME的市场,但是在低终端Nokia和使用J2ME的三星手机方面还是有很大的市场。有一段时间,安卓上可用的游戏、软件几乎全都是用MIDP、CLDC ,他们是J2ME平台的一部分。J2ME在一些产品如蓝光光碟、机顶盒等等。WhatsApp很受欢迎的一个原因是因为对所有Nokia手机的J2ME平台来说是可用的。

7)嵌入式领域

在嵌入式领域,Java也是有很大应用的。它展示了平台是多么的强大,你仅需130Kb就能使用Java技术(在智能卡或者传感器上)。起初,Java是为嵌入式设备而设计的。实际上,这是Java最初“一次编写,到处运行”初衷的一个部分,现在看起来获得了成功。

8)大数据技术

Hadoop和其他大数据技术以这样或那样的方式使用着Java,例如Apache依赖Java的HBse和Accumulo(开源)以及ElasticSearch。但是Java在该领域并不占统治地位,因为有其他技术如MongoDB是用C++开发的。如果Hadoop或者ElasticSearch发展壮大的话,Java在这个发展的领域有可能获得主要的占有率

9)高频率的交易领域

Java平台在现代JIT技术的帮助下,它的性能特性已经有了很大提升,传送性能已经到了C++水平。由于这个原因,Java在开发高性能系统方面还是很受欢迎的,因为性能与机器语言相比稍差一些,但是你可以安全地折衷——轻便、可维护带来更快地速度。对一个缺乏经验的C++程序员来说,只能使应用变得更慢和不可靠。

10)科学应用

现在,对于科学应用来说Java经常作为一个默认的选择,包括自然语言处理。这种现象的主要原因是Java更安全、轻便、易维护,并且与C++和其他语言相比有更好的高级并发工具。

在九十年代,Java由于Applet在因特网领域占有重要地位,但是几年后,由于Applet沙箱模式的各种安全问题,Applet失去了风头。现在,桌面Java和Applet几乎灭亡。Java作为软件开发公司默认的开发语言,在金融服务行业、投资银行和电子商务web应用领域获得了很大应用,任何学习Java的人员都会为自己赢得光明的未来。Java 8更加强了一个信念——在未来的几年,Java在软件开发领域继续占有重要地位。

相关文章

24 Dec 01:03

My favourite interview question (2006)

24 Dec 01:03

Software engineering interview questions

22 Dec 00:59

C++ 的五个普遍误解(2):垃圾回收

by Sheng Gordon

[编注:为了增加您冬天阅读的乐趣,我们很荣幸的奉上Bjarne Stroustrup大神的这个包含3个部分的系列文章。第一部分在这里第三部分将在下个周一发布,即在圣诞节之前完成这个系列。请欣赏。]

1. 简介

本系列包括 3 篇文章,我将向大家展示并澄清关于C++的五个普遍的误解:

  • 1. “要理解C++,你必须先学习C”
  • 2. “C++是一门面向对象的语言”
  • 3. “为了软件可靠性,你需要垃圾回收”
  • 4. “为了效率,你必须编写底层代码”
  • 5. “C++只适用于大型、复杂的程序”

如果你深信上述误解中的任何一个,或者有同事深信不疑,那么这篇短文正是为你而写。对某些人,某些任务,在某些时间,其中一些误解曾经只是正确的。然而,在如今的C++,应用广泛使用的最先进的ISO C++ 2011编译器和工具,它们只是误解。

我认为这些误解是“普遍的”,是因为我经常听到。偶尔,它们有原因来支持,但是它们经常地被作为明显的、不需要理由的支持地表达出来。有时,它们成为某些场景下不考虑使用C++的理由。

每一个误解,都需要一大篇文章,甚至一本书来澄清,但是这里我的目标很简单,就是抛出问题,并简明地陈述我的原因。

前两个误解在我的第一篇文中呈现。

4. 误解3:“对可靠的软件,你需要垃圾回收”

在回收不再使用的内存上,垃圾回收做的很好,但是并不完美。它并非灵丹妙药。因为内存可以被间接地引用,并且很多资源并不是普通内存。考虑:

class Filter { // take input from file iname and produce output on file oname
public:
  Filter(const string& iname, const string& oname); // constructor
  ~Filter();                                        // destructor
  // ...
private:
  ifstream is;
  ofstream os;
  // ...
};

Filter的构造函数打开了两个文件。之后,Filter从它的输入文件读取数据,执行一些任务,然后输出到输出文件。任务与Filter直接有关,可能通过一个lambda提供,或者通过一个函数返回重载了虚方法的继承类来提供;这些细节在资源管理的讨论中并不重要。我们可以这样创建Filter:

void user()
{
  Filter flt {“books”,”authors”};
  Filter* p = new Filter{“novels”,”favorites”};
  // use flt and *p
  delete p;
}

从资源管理的观点来看,这里的问题在于如何保证关闭被打开的文件,以及回收这两个流对象的相关资源,以供后续重复使用。

对于依赖垃圾回收的语言和系统,常规的解决方法是消除delete(它很容易被遗忘,导致泄漏)和析构函数(因为支持垃圾回收的语言很少有析构函数,而最好避免使用“finalizers”,因为它在逻辑上容易被取巧,并经常损坏性能)。内存回收器能够回收所有内存,但是我们需要用户手动(代码)关闭文件,以及释放与流相关的非内存资源(如锁)。因此,内存是自动(此例中很完美)回收的,但是需要手动管理其他资源,从而存在错误和泄露的可能性。

C++中常用和推荐的方法是使用析构函数,来保证资源被回收。典型的,在此例和通用技术中,这类资源在构造器中申请,并遵循有着笨拙名字的“资源申请即初始化”(RAII)原则。在user()中,flt的析构函数隐式地调用了流is和os的析构函数。这些析构函数依次关闭文件并释放流相关的资源。delete对*p做同样的操作。

有经验的现代C++11用户会注意到,user()相当笨拙并容易出错。这样会更好一些:

void user2()
{
  Filter flt {“books”,”authors”};
  unique_ptr<Filter> p {new Filter{“novels”,”favorites”}};
  // use flt and *p
}

现在当user()退出时,*p将被隐式地释放。程序员不会忘记这么做。unique_ptr是标准库类,它被设计用来在没有运行时(RTTI)或者空间开销的前提下,增强内置“裸“指针的资源释放。

然而,我们仍然可以看到new,这个解决方案有点啰嗦(Filter类型重复了两次),并且将普通指针构造(通过new)和智能指针(这里是unique_ptr)分离开阻止了一些有效的优化。我们可以使用C++14中的辅助函数make_unique来改进,它构造一个指定类型的对象,并返回一个unique_ptr:

void user3()
{
  Filter flt {“books”,”authors”};
  auto p = make_unique<Filter>(“novels”,”favorites”);
  // use flt and *p
}

Unless we really needed the second Filter to have pointer semantics (which is unlikely) this would be better still:
除非我们在语法上真正地需要第二个Filter指针(这不太可能),否则这样会更好:

void user3()
{
  Filter flt {“books”,”authors”};
  Filter flt2 {“novels”,”favorites”};
  // use flt and flt2
}

最后一个版本比最初的代码更简短,更简单,更清晰,更快。

但是Filter的析构函数做什么?它释放Filter拥有的资源;即,它关闭文件(通过触发它们的析构函数)。实际上,这是隐式完成的,因此除非有其他需要,我们可以忽略Filter析构函数的显式声明,让编译器来处理它。因此,我需要编写的只有:

class Filter { // take input from file iname and produce output on file oname
public:
  Filter(const string& iname, const string& oname);
  // ...
private:
  ifstream is;
  ofstream os;
  // ...
};

void user3()
{
  Filter flt {“books”,”authors”};
  Filter flt2 {“novels”,”favorites”};
  // use flt and flt2
}

这比你在多数垃圾回收语言(如Java或C#)中写的代码更简单;并且对那些健忘的程序员,它不会导致泄漏。它也比其他方案(不需要使用free/dynamic,也不需要运行垃圾回收器)更快。典型的,相对与手动方式,RAII也缩短了资源的生命周期。

这是我理想的资源管理方式。它不单单处理内存,同时也处理通用(非内存)资源,例如文件句柄,线程句柄和锁。但是这就够了吗?怎么处理需要从一个函数传递到另一个函数的对象?那些没有明显单独拥有者的对象呢?

4.1传递拥有关系:move

让我们先来看一看把对象从一个代码块传递到另一个代码块的问题。关键问题是,在不复制或者错误使用指针导致严重性能问题的前提下,如何从一个代码块中得到大量信息。使用指针的传统方式是:

X* make_X()
{
  X* p = new X:
  // ... fill X ..
  return p;
}

void user()
{
  X* q = make_X();
  // ... use *q ...
  delete q;
}

现在,谁有责任来释放对象呢?在这个简单的例子里,明显是make_X()的调用者,但是通常情况下答案并不是显而易见的。假如make_X()为了最小化申请负荷而保存了对象的缓存呢?假如user()把指针传递给了其他如other_user()函数呢?潜在的可能性很多,在这类程序中的泄露并非罕见。

我可能会使用一个shared_ptr或者unique_ptr,来明确表明对创建对象的拥有关系。例如:

unique_ptr<X> make_X();

但是为什么要使用一个指针(不管是否智能)呢?通常,我不想使用指针;并且,指针会导致从对象的常规使用中分心。例如,一个矩阵求和函数,根据两个参数创建了一个新的对象(求和结果),但是返回一个指针会导致非常奇怪的代码:

unique_prt<Matrix> operator+(const Matrix& a, const Matrix& b);
Matrix res = *(a+b);

这里需要使用*操作符来得到求和结果,否则得到的是指向结果的指针。在很多情况下,我真正需要的是一个对象,而不是指向对象的指针。很多时候,我可以容易地做到。尤其是,复制一个小的对象很快,我不想使用指针:

double sqrt(double); // a square root function
double s2 = sqrt(2); // get the square root of 2

从另一方面来说,一个包含了很多数据的对象,一般会处理这么多的数据。考虑istream,string,vector,list和thread。它们都只包含了少数几个字节的数据,来保证潜在的大量数据访问。再次考虑矩阵求和。我们需要的是

Matrix operator+(const Matrix& a, const Matrix& b); // return the sum of a and b
Matrix r = x+y;

我们可以轻松的做到。

Matrix operator+(const Matrix& a, const Matrix& b)
{
  Matrix res;
  // ... fill res with element sums ...
  return res;
}

默认情况下,它将res的元素复制给r,但是因为res即将被销毁,保存元素的内存即将被释放,因此这里没有必要复制:我们可以“窃取”元素。自从C++诞生以来,任何人都可能这么做,并且很多人确实这么做了。但是这是代码实现的技巧,而且这项技术并不好理解。C++11直接支持“窃取表示法(stealing the representation)”,通过move操作传递一个句柄的拥有关系。考虑一个简单的2维double类型的矩阵:

class Matrix {
  double* elem; // pointer to elements
  int nrow;     // number of rows
  int ncol;     // number of columns
public:
  Matrix(int nr, int nc)                  // constructor: allocate elements
    :elem{double[nr*nc]}, nrow{nr}, ncol{nc}
  {
    for(int i=0; i<nr*nc; ++i) elem[i]=0; // initialize elements
  }

  Matrix(const Matrix&);                  // copy constructor
  Matrix operator=(const Matrix&);        // copy assignment

  Matrix(Matrix&&);                       // move constructor
  Matrix operator=(Matrix&&);             // move assignment

  ~Matrix() { delete[] elem; }            // destructor: free the elements

// …
};

通过引用参数(&),可以识别一个复制操作。类似地,通过右值引用(&&)参数,可以识别一个move操作。move操作的目的是“窃取”对象表现,并留下一个“空对象”。对Matrix,意味着这样的情形:

Matrix::Matrix(Matrix&& a)                   // move constructor
  :nrow{a.nrow}, ncol{a.ncol}, elem{a.elem}  // “steal” the representation
{
  a.elem = nullptr;                          // leave “nothing” behind
}

就是这样!当编译器看到返回值res,它意识到res即将被销毁。即,在函数返回后res将不再被使用。因此它使用了一个move构造函数来传递返回值,而不是复制构造函数。特殊的,对于

Matrix r = a+b;

在operator+()内部的res变成了空——析构函数将空执行一次——然后r拥有了res的元素。我们成功地从函数的结果中取得了元素——可能是数M字节的内存——并存入调用函数的变量中。我们用最小的代价实现了(可能是4个字的赋值)。

老练的C++用户指出,一个好的编译器能够完全消除返回值复制操作(这个例子中是,消除掉4个字的赋值和析构函数调用)。然而,这是依赖于实现的,我不喜欢我的基本编程技术的性能依赖于独立编译器的聪明程度。更进一步,一个能够消除复制的编译器,也能够轻易的消除move。这里我们所拥有的,是一个简单、可靠和通用的方式,能够消除从一个代码块移动大量信息到另一个块的复杂度和代价。

通常,我们甚至不需要定义所有这些赋值和移动操作。如果一个类由拥有特定表现的成员组成,我们可以简单地依赖编译器自动生成的默认操作。考虑:

class Matrix {
    vector<double> elem; // elements
    int nrow;            // number of rows
    int ncol;            // number of columns
public:
    Matrix(int nr, int nc)    // constructor: allocate elements
      :elem(nr*nc), nrow{nr}, ncol{nc}
    { }

	    // ...
};

这个版本的Matrix和之前版本的表现相同,除了它处理错误稍好一些,以及稍大一些(一个vector通常是3个字)。

不是句柄的对象怎么处理呢?如果它们很小,像int,或者complex,不用担心。否则,把它们改成句柄,或者使用“智能”指针返回,如unique_ptr和shared_ptr。不要和“裸”操作new和delete混用。

不幸的是,类似我上面例子中的Matrix类不是ISO C++标准库的一部分,但是还是可以找到的(开源或者商业)。例如,在网上搜索“Origin Matrix Sutton”,阅读我The C++ Programming Language (Fourth Edition)的第29章,里面有如何设计类似矩阵类的讨论。

4.2 共享拥有关系:shared_ptr

在关于垃圾回收的讨论中,通常会注意到一个现象,即不是每一个对象都有唯一的拥有者。这意味着,我们必须确保当最后一个引用消除后,才能销毁/释放这个对象。在这个模型中,我们必须有一个机制,来保证当对象的最后一个拥有者销毁时,销毁这个对象。即,我们需要一种共享的拥有关系形式。假设我们有一个同步的队列,sync_queue,用作任务之间的通信。生产者和消费者都拥有一个指向sync_queue的指针:

void startup()
{
  sync_queue* p  = new sync_queue{200};  // trouble ahead!
  thread t1 {task1,iqueue,p};  // task1 reads from *iqueue and writes to *p
  thread t2 {task2,p,oqueue};  // task2 reads from *p and writes to *oqueue
  t1.detach();
  t2.detach();
}

我假定task1,task2,iqueue和oqueue已经在其他地方定义好了;很抱歉让线程的生存周期比创建线程的域更长(使用detatch())。你可能会想到多任务处理中的管道和同步队列。然而,这里我只对一个问题感兴趣:“谁来释放startup()中创建的sync_queue?”。如前面所写,只有一个正确答案:“最后使用sync_queue的那个线程”。这是一个刺激产生垃圾回收的经典情形。垃圾回收的最初形式是引用计数:保持对象被使用的计数,当计数降为0时,释放对象。今天很多语言都依赖于这种想法,而C++11通过shared_ptr的形式支持它。例子变成这样:

void startup()
{
  auto p = make_shared<sync_queue>(200);  // make a sync_queue and return a stared_ptr to it
  thread t1 {task1,iqueue,p};  // task1 reads from *iqueue and writes to *p
  thread t2 {task2,p,oqueue};  // task2 reads from *p and writes to *oqueue
  t1.detach();
  t2.detach();
}

这样当task1和task2析构时,会销毁它们的shared_ptr(在良好的设计中会隐式地调用),并且最后一个析构的任务会销毁sync_queue。

它很简单并高效。它并不包含需要复杂运行时系统的垃圾回收器。更重要的是,它不仅仅回收sync_queue关联的内存资源,它同时回收内置在sync_queue中管理两个任务线程同步的对象(互斥,锁,或其他)。我们这里做到的,仍然不仅仅是内存管理,而是通用资源管理。“隐藏的”同步对象也被处理了,和前面例子中处理文件句柄和流缓冲区一样。

在围绕任务的某些范围内,我们可以尝试引入一个唯一的拥有者,从而不使用shared_ptr;但是这样做通常不简单。因此C++11同时提供了unique_ptr(对唯一拥有关系)和shared_ptr(对共享拥有关系)。

4.3 类型安全

我刚刚只提到了和资源管理有关联的垃圾回收。它还在类型安全中扮演一个角色。只要我们有显式的delete操作,它就可能被错误使用。例如:

X* p = new X;
X* q = p;
delete p;
// ...
q->do_something();  // the memory that held *p may have been re-used

不要这样做。裸露的delete非常危险——而且在常用的代码中是不必要的。把delete放到资源管理类的内部,例如string,ostream,thread,unique_ptr和shared_ptr。这样,delete就会和new正确对应,不会出错。

4.4 总结:资源管理理念

对于资源管理,我认为垃圾回收是最后的选择,而不是“解决方案”或者理念:
1. 运用适当的抽象,递归和显式地处理自己拥有的资源。限定对象的作用域会更好。
2. 当你需要使用指针/引用语义时,使用诸如unique_ptr和shared_ptr的“智能指针”,来表明拥有关系。
3. 如果其他都行不通(如,你的代码是一个程序的一部分,而程序中使用了大量不满足语言资源管理和错误处理策略的指针),尝试“手动”处理非内存资源,并内嵌一个保守的垃圾回收器,用它来处理那些几乎不可避免的内存泄露。

这个策略完美吗?不,但它是通用的,并且简单。传统的基于垃圾回收的策略也不完美,并且它们不能直接处理非内存资源。

附言

系列的第一篇在这里

  • 误解1:“要理解C++,你必须先学习C”
  • 误解2:“C++是一门面向对象的语言”

在下一篇中我将讲解

  • 误解4:“为了效率,你必须编写底层代码”
  • 误解5:“C++只适用于大型、复杂的程序”

C++ 的五个普遍误解(2):垃圾回收,首发于博客 - 伯乐在线

22 Dec 00:58

C++之父:C++ 的五个普遍误解(1)

by Sheng Gordon

[编注:为了增加您冬天阅读的乐趣,我们很荣幸的奉上Bjarne Stroustrup大神的这个包含3个部分的系列文章。这是第一部分;第二和第三部分将在接下来的两个周一发布,即在圣诞节之前完成这个系列。请欣赏。——Ed]

1. 简介

本系列包括 3 篇文章,我将向大家展示并澄清关于C++的五个普遍的误解:

  • 1. “要理解C++,你必须先学习C”
  • 2. “C++是一门面向对象的语言”
  • 3. “为了软件可靠性,你需要垃圾回收”
  • 4. “为了效率,你必须编写底层代码”
  • 5. “C++只适用于大型、复杂的程序”

如果你深信上述误解中的任何一个,或者有同事深信不疑,那么这篇短文正是为你而写。对某些人,某些任务,在某些时间,其中一些误解曾经只是正确的。然而,在如今的C++,应用广泛使用的最先进的ISO C++ 2011编译器和工具,它们只是误解。

我认为这些误解是“普遍的”,是因为我经常听到。偶尔,它们有原因来支持,但是它们经常地被作为明显的、不需要理由的支持地表达出来。有时,它们成为某些场景下不考虑使用C++的理由。

每一个误解,都需要一大篇文章,甚至一本书来澄清,但是这里我的目标很简单,就是抛出问题,并简明地陈述我的原因。

2. 误解1:“要理解C++,你必须先学习C”

不。学习C++基础编程比学习C要容易地多。

C几乎是C++的一个子集,但是它不是最先要学习的最好的子集,因为C缺少计数支持,类型安全,和易用的标准库,而C++为简单任务提供了这些。考虑一个拼接email地址的简单函数:

string compose(const string& name, const string& domain)
{
  return name+'@'+domain;
}

它可能被这样使用

string addr = compose("gre","research.att.com");

而C语言版本需要显式地字符复制和显式地内存管理:

char* compose(const char* name, const char* domain)
{
  char* res = malloc(strlen(name)+strlen(domain)+2); // space for strings, '@', and 0
  char* p = strcpy(res,name);
  p += strlen(name);
  *p = '@';
  strcpy(p+1,domain);
  return res;
}

它可能被这样使用

char* addr = compose("gre","research.att.com");
// …
free(addr); // release memory when done

哪个版本你更愿意教?哪个版本更容易使用呢?刚才我的C版本代码写对了吗?你确定?为什么?

最后,哪个版本可能更有效率呢?是的,是C++版本。因为它不需要数参数中字符的个数,也不需要释放参数中短字符串的空间(动态内存)。

2.1 学习C++

这不是一个奇怪的孤立例子。我认为它是典型的。那为什么有那么多老师坚持“先学习C”的观点?
• 因为多年来他们一直这么做。
• 因为这是课程所要求的。
• 因为老师们年轻时就是这么学习的。
• 因为C比C++小,就认为C比C++简单。
• 因为学生们迟早要学习C(或者C++的C子集)。

然而,C并不是最先学习C++的最容易或者最常用的子集。更进一步,一旦你知道了C++的合理数量,C子集很容易学习。先学习C,会导致不断忍受错误,以及学习如何避免这些错误,而在C++中很容易避免这些错误。

对于用现代的方法教学C++,看我的书Programming: Principles and Practice Using C++。它甚至在结尾有一章,专门讲如何使用C。它在多个大学中数以万计的初学者中成功应用。为了灵活学习,这本书的第二版中使用了C++11和C++14工具。

在C++11[11-12]中,C++对初学者更友好。例如,标准库中使用元素序列初始化一个vector:

vector<int> v = {1,2,3,5,8,13};

在C++98中,我们只能使用列表来初始化数组。而在C++11中,我们可以定义一个构造函数,接收可以是任意需要类型的{}初始化列表。

我们能够使用range-for循环来遍历vector:

for(int x : v) test(x);

它将对v中的每一个元素,调用一次test()。

range-for循环可以遍历任意序列,因此我们可以直接使用初始化列表来简化上面的例子:

for (int x : {1,2,3,5,8,13}) test(x);

C++11的一个目标就是,让简单的事情简单化。自然地,它在没有增加性能负担的前提下实现了。

3. 误解2:“C++是一门面向对象的语言”

不。C++支持OOP和其他编程风格,但它并不局限于狭隘的“面向对象”。它综合地支持了包括面向对象和泛型编程技术。通常,一个问题的最优解决方案,包含不止一种风格(范例)。“最优”,我指的是最短、最易于理解、最有效率和最易于维护等。

“C++是一门面向对象的语言”使人们认为C++不是必要的(当与C做比较时),除非你需要一个巨大的类继承层次以及很多须函数(运行时多态)——对很多人和很多问题,这样应用并不合适。相信这个误区导致C++因为不是纯面向对象而遭到谴责;毕竟,如果你把“好”和“面向对象”等同起来,那么C++明显包含了很多不是面向对象的东西,一定会被认为是“不好”。不管是哪种情形,这个误解为不学习C++提供了一个很好的接口。

考虑一个例子:

void rotate_and_draw(vector<Shape*>& vs, int r)
{
  for_each(vs.begin(),vs.end(), [](Shape* p) { p->rotate(r); });  // rotate all elements of vs
  for (Shape* p : vs) p->draw();                                  // draw all elements of vs
}

它是面向对象的吗?当然是;它关键依赖类的虚函数机制。它是泛型吗?当然是;它关键依赖于一个参数化容器(vector)和泛型函数for_each。它是函数式吗?某种程度上;它使用了lambda表达式([]构造器)。那么它到底是什么?它是现代C++:C++11。

我使用了range-for和标准库算法for_each,就是为了炫一下特性。在真实的代码中,不管我使用哪种方式,我只会使用一种循环。

3.1 泛型编程

你想让这段代码更通用吗?毕竟,它只在指向Shapes的指针向量中使用。如果使用列表和内置数组会怎么样?“智能指针”(资源管理指针)呢,例如shared_ptr和unique_ptr?对于那些你可以调用draw()和rotate()方法,但是类名称不是Shape的对象呢?考虑:

template<typename Iter>
void rotate_and_draw(Iter first, Iter last, int r)
{
  for_each(first,last,[](auto p) { p->rotate(r); });  // rotate all elements of [first:last)
  for (auto p = first; p!=last; ++p) p->draw();       // draw all elements of [first:last)
}

对于任意你需要从first遍历到last的序列,它能够运行。这就是C++标准库算法的风格。我使用了auto,来避免不得不对“类似shape的对象”的接口命名。这是一个C++11特性,即“使用表达式的类型初始化”,因此对于for循环,p的类型由第一次赋值的类型决定。使用auto来表示lambda参数类型是C++14的一个特性,但是已经在使用了。

考虑:

void user(list<unique_ptr<Shape>>& lus, Container<Blob>& vb)
{
  rotate_and_draw(lst.begin(),lst.end());
  rotate_and_draw(begin(vb),end(vb));
}

这里,我假设Blob是一个包含draw()和rotate()操作的图形类型,而Container是某种容器类型。标准库列表(std::list)有成员函数begin()和end(),来帮助用户遍历它序列的元素。这是经典的面向对象编程。但是如果Container不支持C++标准算法库遍历半开序列[b:e]的约定呢?如果不包含begin()和end()成员呢?好了,我还从来没有见过哪些容器类是不能被遍历的,因此我们可以定义适当语义的独立begin()和end()函数。这个标准库支持C样式的数组,因此如果Container是C样式的数组,问题能够解决——并且C样式的数组仍然很常见。

3.2 适应性

考虑一个更难的情况:如果容器包含对象指针,而且有着不同的访问和遍历模式呢?例如,假设你需要这样访问一个Container:

for (auto p = c.first(); p!=nullptr; p=c.next()) { /* do something with *p */}

这种风格并非不常见。我们可以把它转化成一个[b:e)序列:

template<typename T> struct Iter {
  T* current;
  Container<T>& c;
};

template<typename T> Iter<T> begin(Container<T>& c) { return Iter<T>{c.first(),c}; }
template<typename T> Iter<T> end(Container<T>& c)   { return Iter<T>{nullptr}; }
template<typename T> Iter<T> operator++(Iter<T> p)  { p.current = c.next(); return this; }
template<typename T> T*      operator*(Iter<T> p)   { return p.current; }

注意这种修改方式是非侵入式的:我不需要修改Container或者它的继承类,来把Container映射到支持C++标准算法的遍历模型。这是一种形式的适应,而不是重构。

我选择这个例子,是为了展示泛型编程技术并不局限于标准算法库(标准算法中它很常见)。同样,对大多数常见的“面向对象”定义,它们不是面向对象的。

C++代码必须是面向对象的想法(即到处使用类继承和虚方法),可能严重损坏效率。如果你需要一组类型的运行时解决方案,面向对象编程的观点是伟大的。我就经常使用它。然而,它是相对严格的(并不是每个类型都适应类继承),并且虚方法调用抑制了内链函数(它可以在一些简单而重要的场景中降低你50倍的速度)。

附言

在我的下一部分,我将讲解“为了软件可靠性,你需要垃圾回收”。

C++之父:C++ 的五个普遍误解(1),首发于博客 - 伯乐在线

20 Dec 02:03

MapReduce实例浅析

by Leo

在文章《MapReduce原理与设计思想》中,详细剖析了MapReduce的原理,这篇文章则通过实例重点剖析MapReduce

1.MapReduce概述

Hadoop Map/Reduce是一个使用简易的软件框架,基于它写出来的应用程序能够运行在由上千个商用机器组成的大型集群上,并以一种可靠容错的方式并行处理上T级别的数据集。

一个Map/Reduce 作业(job) 通常会把输入的数据集切分为若干独立的数据块,由 map任务(task)以完全并行的方式处理它们。框架会对map的输出先进行排序, 然后把结果输入给reduce任务。通常作业的输入和输出都会被存储在文件系统中。 整个框架负责任务的调度和监控,以及重新执行已经失败的任务。

通常,Map/Reduce框架和分布式文件系统是运行在一组相同的节点上的,也就是说,计算节点和存储节点通常在一起。这种配置允许框架在那些已经存好数据的节点上高效地调度任务,这可以使整个集群的网络带宽被非常高效地利用。

Map/Reduce框架由一个单独的master JobTracker 和每个集群节点一个slave TaskTracker共同组成。master负责调度构成一个作业的所有任务,这些任务分布在不同的slave上,master监控它们的执行,重新执行已经失败的任务。而slave仅负责执行由master指派的任务。

应用程序至少应该指明输入/输出的位置(路径),并通过实现合适的接口或抽象类提供map和reduce函数。再加上其他作业的参数,就构成了作业配置(job configuration)。然后,Hadoop的 job client提交作业(jar包/可执行程序等)和配置信息给JobTracker,后者负责分发这些软件和配置信息给slave、调度任务并监控它们的执行,同时提供状态和诊断信息给job-client。

虽然Hadoop框架是用Java实现的,但Map/Reduce应用程序则不一定要用 Java来写 。

2.样例分析:单词计数

1、WordCount源码分析

单词计数是最简单也是最能体现MapReduce思想的程序之一,该程序完整的代码可以在Hadoop安装包的src/examples目录下找到

单词计数主要完成的功能是:统计一系列文本文件中每个单词出现的次数,如图所示:

(1)Map过程

Map过程需要继承org.apache.hadoop.mapreduce包中的Mapper类,并重写map方法

通过在map方法中添加两句把key值和value值输出到控制台的代码,可以发现map方法中的value值存储的是文本文件中的一行(以回车符作为行结束标记),而key值为该行的首字符相对于文本文件的首地址的偏移量。然后StringTokenizer类将每一行拆分成一个个的单词,并将<word,1>作为map方法的结果输出,其余的工作都交由MapReduce框架处理。其中IntWritable和Text类是Hadoop对int和string类的封装,这些类能够被串行化,以方便在分布式环境中进行数据交换。

TokenizerMapper的实现代码如下:

public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable>{
    private final static IntWritable one = new IntWritable(1);
    private Text word = new Text();
      
    public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
        System.out.println("key = " + key.toString());//添加查看key值
        System.out.println("value = " + value.toString());//添加查看value值
        StringTokenizer itr = new StringTokenizer(value.toString());
        while (itr.hasMoreTokens()) {
            word.set(itr.nextToken());
            context.write(word, one);
        }
    }
}

(2)Reduce过程

Reduce过程需要继承org.apache.hadoop.mapreduce包中的Reducer类,并重写reduce方法

reduce方法的输入参数key为单个单词,而values是由各Mapper上对应单词的计数值所组成的列表,所以只要遍历values并求和,即可得到某个单词的出现总次数

IntSumReduce类的实现代码如下:

public static class IntSumReducer extends Reducer<Text,IntWritable,Text,IntWritable> {
    private IntWritable result = new IntWritable();

    public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
      int sum = 0;
      for (IntWritable val : values) {
          sum += val.get();
      }
      result.set(sum);
      context.write(key, result);
   }
}

(3)执行MapReduce任务

在MapReduce中,由Job对象负责管理和运行一个计算任务,并通过Job的一些方法对任务的参数进行相关的设置。此处设置了使用TokenizerMapper完成Map过程和使用的IntSumReduce完成Combine和Reduce过程。还设置了Map过程和Reduce过程的输出类型:key的类型为Text,value的类型为IntWritable。任务的输入和输出路径则由命令行参数指定,并由FileInputFormat和FileOutputFormat分别设定。完成相应任务的参数设定后,即可调用job.waitForCompletion()方法执行任务,主函数实现如下:

public static void main(String[] args) throws Exception {
    Configuration conf = new Configuration();
    String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
    if (otherArgs.length != 2) {
      System.err.println("Usage: wordcount <in> <out>");
      System.exit(2);
    }
    Job job = new Job(conf, "word count");
    job.setJarByClass(wordCount.class);
    job.setMapperClass(TokenizerMapper.class);
    job.setCombinerClass(IntSumReducer.class);
    job.setReducerClass(IntSumReducer.class);
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
    FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
    System.exit(job.waitForCompletion(true) ? 0 : 1);
  }
}

运行结果如下:

14/12/17 05:53:26 INFO jvm.JvmMetrics: Initializing JVM Metrics with processName=JobTracker, sessionId=
14/12/17 05:53:26 INFO input.FileInputFormat: Total input paths to process : 2
14/12/17 05:53:26 INFO mapred.JobClient: Running job: job_local_0001
14/12/17 05:53:26 INFO input.FileInputFormat: Total input paths to process : 2
14/12/17 05:53:26 INFO mapred.MapTask: io.sort.mb = 100
14/12/17 05:53:27 INFO mapred.MapTask: data buffer = 79691776/99614720
14/12/17 05:53:27 INFO mapred.MapTask: record buffer = 262144/327680
key = 0
value = Hello World
key = 12
value = Bye World
14/12/17 05:53:27 INFO mapred.MapTask: Starting flush of map output
14/12/17 05:53:27 INFO mapred.MapTask: Finished spill 0
14/12/17 05:53:27 INFO mapred.TaskRunner: Task:attempt_local_0001_m_000000_0 is done. And is in the process of commiting
14/12/17 05:53:27 INFO mapred.LocalJobRunner: 
14/12/17 05:53:27 INFO mapred.TaskRunner: Task ‘attempt_local_0001_m_000000_0′ done.
14/12/17 05:53:27 INFO mapred.MapTask: io.sort.mb = 100
14/12/17 05:53:27 INFO mapred.MapTask: data buffer = 79691776/99614720
14/12/17 05:53:27 INFO mapred.MapTask: record buffer = 262144/327680
14/12/17 05:53:27 INFO mapred.MapTask: Starting flush of map output
key = 0
value = Hello Hadoop
key = 13
value = Bye Hadoop
14/12/17 05:53:27 INFO mapred.MapTask: Finished spill 0
14/12/17 05:53:27 INFO mapred.TaskRunner: Task:attempt_local_0001_m_000001_0 is done. And is in the process of commiting
14/12/17 05:53:27 INFO mapred.LocalJobRunner: 
14/12/17 05:53:27 INFO mapred.TaskRunner: Task ‘attempt_local_0001_m_000001_0′ done.
14/12/17 05:53:27 INFO mapred.LocalJobRunner: 
14/12/17 05:53:27 INFO mapred.Merger: Merging 2 sorted segments
14/12/17 05:53:27 INFO mapred.Merger: Down to the last merge-pass, with 2 segments left of total size: 73 bytes
14/12/17 05:53:27 INFO mapred.LocalJobRunner: 
14/12/17 05:53:27 INFO mapred.TaskRunner: Task:attempt_local_0001_r_000000_0 is done. And is in the process of commiting
14/12/17 05:53:27 INFO mapred.LocalJobRunner: 
14/12/17 05:53:27 INFO mapred.TaskRunner: Task attempt_local_0001_r_000000_0 is allowed to commit now
14/12/17 05:53:27 INFO output.FileOutputCommitter: Saved output of task ‘attempt_local_0001_r_000000_0′ to out
14/12/17 05:53:27 INFO mapred.LocalJobRunner: reduce > reduce
14/12/17 05:53:27 INFO mapred.TaskRunner: Task ‘attempt_local_0001_r_000000_0′ done.
14/12/17 05:53:27 INFO mapred.JobClient: map 100% reduce 100%
14/12/17 05:53:27 INFO mapred.JobClient: Job complete: job_local_0001
14/12/17 05:53:27 INFO mapred.JobClient: Counters: 14
14/12/17 05:53:27 INFO mapred.JobClient: FileSystemCounters
14/12/17 05:53:27 INFO mapred.JobClient: FILE_BYTES_READ=17886
14/12/17 05:53:27 INFO mapred.JobClient: HDFS_BYTES_READ=52932
14/12/17 05:53:27 INFO mapred.JobClient: FILE_BYTES_WRITTEN=54239
14/12/17 05:53:27 INFO mapred.JobClient: HDFS_BYTES_WRITTEN=71431
14/12/17 05:53:27 INFO mapred.JobClient: Map-Reduce Framework
14/12/17 05:53:27 INFO mapred.JobClient: Reduce input groups=4
14/12/17 05:53:27 INFO mapred.JobClient: Combine output records=6
14/12/17 05:53:27 INFO mapred.JobClient: Map input records=4
14/12/17 05:53:27 INFO mapred.JobClient: Reduce shuffle bytes=0
14/12/17 05:53:27 INFO mapred.JobClient: Reduce output records=4
14/12/17 05:53:27 INFO mapred.JobClient: Spilled Records=12
14/12/17 05:53:27 INFO mapred.JobClient: Map output bytes=78
14/12/17 05:53:27 INFO mapred.JobClient: Combine input records=8
14/12/17 05:53:27 INFO mapred.JobClient: Map output records=8
14/12/17 05:53:27 INFO mapred.JobClient: Reduce input records=6

2、WordCount处理过程

上面给出了WordCount的设计思路和源码,但是没有深入细节,下面对WordCount进行更加详细的分析:

(1)将文件拆分成splits,由于测试用的文件较小,所以每一个文件为一个split,并将文件按行分割成<key, value>对,如图,这一步由Mapreduce框架自动完成,其中偏移量包括了回车所占的字符

(2)将分割好的<key, value>对交给用户定义的map方法进行处理,生成新的<key, value>对

(3)得到map方法输出的<key, value>对后,Mapper会将它们按照key值进行排序,并执行Combine过程,将key值相同的value值累加,得到Mapper的最终输出结果,如图:

(4)Reduce先对从Mapper接收的数据进行排序,再交由用户自定义的reduce方法进行处理,得到新的<key, value>对,并作为WordCount的输出结果,如图:

3.MapReduce,你够了解吗?

MapReduce框架在幕后默默地完成了很多的事情,如果不重写map和reduce方法,会出现什么情况呢?

下面来实现一个简化的MapReduce,新建一个LazyMapReduce,该类只对任务进行必要的初始化及输入/输出路径的设置,其余的参数均保持默认

代码如下:

public class LazyMapReduce {
    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub
        Configuration conf = new Configuration();
        String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
        if(otherArgs.length != 2) {
            System.err.println("Usage:wordcount<in><out>");
            System.exit(2);
        }
        Job job = new Job(conf, "LazyMapReduce");
        FileInputFormat.addInputPath(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));
        System.exit(job.waitForCompletion(true)? 0:1);
    }
}

运行结果为:

14/12/17 23:04:13 INFO jvm.JvmMetrics: Initializing JVM Metrics with processName=JobTracker, sessionId=
14/12/17 23:04:14 INFO input.FileInputFormat: Total input paths to process : 2
14/12/17 23:04:14 INFO mapred.JobClient: Running job: job_local_0001
14/12/17 23:04:14 INFO input.FileInputFormat: Total input paths to process : 2
14/12/17 23:04:14 INFO mapred.MapTask: io.sort.mb = 100
14/12/17 23:04:15 INFO mapred.JobClient: map 0% reduce 0%
14/12/17 23:04:18 INFO mapred.MapTask: data buffer = 79691776/99614720
14/12/17 23:04:18 INFO mapred.MapTask: record buffer = 262144/327680
14/12/17 23:04:18 INFO mapred.MapTask: Starting flush of map output
14/12/17 23:04:19 INFO mapred.MapTask: Finished spill 0
14/12/17 23:04:19 INFO mapred.TaskRunner: Task:attempt_local_0001_m_000000_0 is done. And is in the process of commiting
14/12/17 23:04:19 INFO mapred.LocalJobRunner: 
14/12/17 23:04:19 INFO mapred.TaskRunner: Task ‘attempt_local_0001_m_000000_0′ done.
14/12/17 23:04:20 INFO mapred.MapTask: io.sort.mb = 100
14/12/17 23:04:20 INFO mapred.MapTask: data buffer = 79691776/99614720
14/12/17 23:04:20 INFO mapred.MapTask: record buffer = 262144/327680
14/12/17 23:04:20 INFO mapred.MapTask: Starting flush of map output
14/12/17 23:04:20 INFO mapred.MapTask: Finished spill 0
14/12/17 23:04:20 INFO mapred.TaskRunner: Task:attempt_local_0001_m_000001_0 is done. And is in the process of commiting
14/12/17 23:04:20 INFO mapred.LocalJobRunner: 
14/12/17 23:04:20 INFO mapred.TaskRunner: Task ‘attempt_local_0001_m_000001_0′ done.
14/12/17 23:04:20 INFO mapred.LocalJobRunner: 
14/12/17 23:04:20 INFO mapred.Merger: Merging 2 sorted segments
14/12/17 23:04:20 INFO mapred.Merger: Down to the last merge-pass, with 2 segments left of total size: 90 bytes
14/12/17 23:04:20 INFO mapred.LocalJobRunner: 
14/12/17 23:04:20 INFO mapred.TaskRunner: Task:attempt_local_0001_r_000000_0 is done. And is in the process of commiting
14/12/17 23:04:20 INFO mapred.LocalJobRunner: 
14/12/17 23:04:20 INFO mapred.TaskRunner: Task attempt_local_0001_r_000000_0 is allowed to commit now
14/12/17 23:04:20 INFO output.FileOutputCommitter: Saved output of task ‘attempt_local_0001_r_000000_0′ to out
14/12/17 23:04:20 INFO mapred.LocalJobRunner: reduce > reduce
14/12/17 23:04:20 INFO mapred.TaskRunner: Task ‘attempt_local_0001_r_000000_0′ done.
14/12/17 23:04:20 INFO mapred.JobClient: map 100% reduce 100%
14/12/17 23:04:20 INFO mapred.JobClient: Job complete: job_local_0001
14/12/17 23:04:20 INFO mapred.JobClient: Counters: 14
14/12/17 23:04:20 INFO mapred.JobClient: FileSystemCounters
14/12/17 23:04:20 INFO mapred.JobClient: FILE_BYTES_READ=46040
14/12/17 23:04:20 INFO mapred.JobClient: HDFS_BYTES_READ=51471
14/12/17 23:04:20 INFO mapred.JobClient: FILE_BYTES_WRITTEN=52808
14/12/17 23:04:20 INFO mapred.JobClient: HDFS_BYTES_WRITTEN=98132
14/12/17 23:04:20 INFO mapred.JobClient: Map-Reduce Framework
14/12/17 23:04:20 INFO mapred.JobClient: Reduce input groups=3
14/12/17 23:04:20 INFO mapred.JobClient: Combine output records=0
14/12/17 23:04:20 INFO mapred.JobClient: Map input records=4
14/12/17 23:04:20 INFO mapred.JobClient: Reduce shuffle bytes=0
14/12/17 23:04:20 INFO mapred.JobClient: Reduce output records=4
14/12/17 23:04:20 INFO mapred.JobClient: Spilled Records=8
14/12/17 23:04:20 INFO mapred.JobClient: Map output bytes=78
14/12/17 23:04:20 INFO mapred.JobClient: Combine input records=0
14/12/17 23:04:20 INFO mapred.JobClient: Map output records=4
14/12/17 23:04:20 INFO mapred.JobClient: Reduce input records=4

可见在默认情况下,MapReduce原封不动地将输入<key, value>写到输出

下面介绍MapReduce的部分参数及其默认设置:

(1)InputFormat类

该类的作用是将输入的数据分割成一个个的split,并将split进一步拆分成<key, value>对作为map函数的输入

(2)Mapper类

实现map函数,根据输入的<key, value>对生产中间结果

(3)Combiner

实现combine函数,合并中间结果中具有相同key值的键值对。

(4)Partitioner类

实现getPartition函数,用于在Shuffle过程按照key值将中间数据分成R份,每一份由一个Reduce负责

(5)Reducer类

实现reduce函数,将中间结果合并,得到最终的结果

(6)OutputFormat类

该类负责输出最终的结果

上面的代码可以改写为:

public class LazyMapReduce {
    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub
        Configuration conf = new Configuration();
        String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
        if(otherArgs.length != 2) {
            System.err.println("Usage:wordcount<in><out>");
            System.exit(2);
        }
        Job job = new Job(conf, "LazyMapReduce");
        job.setInputFormatClass(TextInputFormat.class);
        job.setMapperClass(Mapper.class);
        
        job.setMapOutputKeyClass(LongWritable.class);
        job.setMapOutputValueClass(Text.class);
        job.setPartitionerClass(HashPartitioner.class);
        job.setReducerClass(Reducer.class);
        
        job.setOutputKeyClass(LongWritable.class);
        job.setOutputValueClass(Text.class);
        job.setOutputFormatClass(FileOutputFormat.class);
        
        FileInputFormat.addInputPath(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));
        System.exit(job.waitForCompletion(true)? 0:1);
    }
}

不过由于版本问题,显示有些类已经过时

参考资料

《实战Hadop:开启通向云计算的捷径.刘鹏》

http://hadoop.apache.org/docs/r1.0.4/cn/mapred_tutorial.html

MapReduce实例浅析,首发于博客 - 伯乐在线

20 Dec 02:03

监控系统资源的6个Linux工具

by Leo

系统管理员需要对服务器进行监控以确保其正常运行,通过这种方式管理员能够提前发现可能存在的问题并恢复系统,以避免麻烦的出现。

Linux上有很多命令来监控不同的系统资源,如CPU使用率、内存使用情况、网络及磁盘使用情况等等。目前比较受欢迎的工具有top、htop、iostat、nethogs等等。

在本文里,我们将谈论一些简单的命令行工具,它们可以以实时和交互的方式监控多个系统资源,并在一个简单的屏幕中以不断更新的方式呈现大量统计信息。

1. Top


Top是用于检查CPU和内存利用率的最受欢迎的命令行工具。它显示了一个进程的排序列表,其中耗费最多系统资源的进程位于最上面。

进程列表的后面是CPU和内存的使用情况。当程序运行时点击“h”可以显示帮助页面。

2.Htop


这是最能得到你喜爱的命令行工具。它的功能和top相似,但是更加精致并且多一个漂亮的系统负载界面。它的安装并不是默认的,但是在Ubuntu和Fedora这样的发行版套件上则默认可用。

这里有一些用于htop交互性输出的快捷键设置:

<span style="color: #888888;">M: 按照内存的使用量对进程排序
P: 按照CPU的使用量对进程排序 
?: 访问帮助信息
k: 关闭目前/标记的进程 
F2:设置htop. 你可以在这里选择显示选项。 
/: 搜索进程。</span>

3.Atop


Atop是一个监控系统资源和进程的工具。它通过CPU使用率来对列表中的进程进行降序排列,而每一个进程则包含了CPU、内存、磁盘和网络状态等信息。它的功能与top和htop类似。

4.Nmon


Nmon是一个非常容易使用,能够在一个屏幕上监视CPU、内存、网络、磁盘使用状况和进程列表的工具。除了无法管理进程和修改报告显示,Nmon与那些只用于报告的报告工具完全一样。另外,它可以将数据保存到电子表格文件。

5.Glances


Glance是一个由python编写的,与Nmon功能类似的报告工具,它能够报告统计cpu、内存、网络、磁盘和进程。除了报告统计,Glances不支持任何其他特性或功能。当程序运行时点击“h”可以显示帮助页面。

6.Saidar


Saidar是所有命令行工具中最简单的,它的输出包括CPU、进程、负载、内存、交换、网络I/O、磁盘I/O和文件系统信息的统计。它的输出不包括现在运行中的进程。

监控系统资源的6个Linux工具,首发于博客 - 伯乐在线

19 Dec 01:16

How to Get a Job in Algorithmic Trading

19 Dec 01:11

Java I/O底层是如何工作的?

by liken

本博文主要讨论I/O在底层是如何工作的。本文服务的读者,迫切希望了解Java I/O操作是在机器层面如何进行映射,以及应用运行时硬件都做了什么。假定你熟悉基本的I/O操作,比如通过Java I/O API读写文件。这些内容不在本文的讨论范围。

目录

缓存处理和内核vs用户空间

缓冲与缓冲的处理方式,是所有I/O操作的基础。术语“输入、输出”只对数据移入和移出缓存有意义。任何时候都要把它记在心中。通常,进程执行操作系统的I/O请求包括数据从缓冲区排出(写操作)和数据填充缓冲区(读操作)。这就是I/O的整体概念。在操作系统内部执行这些传输操作的机制可以非常复杂,但从概念上讲非常简单。我们将在文中用一小部分来讨论它。

上图显示了一个简化的“逻辑”图,它表示块数据如何从外部源,例如一个磁盘,移动到进程的存储区域(例如RAM)中。首先,进程要求其缓冲通过read()系统调用填满。这个系统调用导致内核向磁盘控 制硬件发出一条命令要从磁盘获取数据。磁盘控制器通过DMA直接将数据写入内核的内存缓冲区,不需要主CPU进一步帮助。当请求read()操作时,一旦磁盘控制器完成了缓存的填 写,内核从内核空间的临时缓存拷贝数据到进程指定的缓存中。

有一点需要注意,在内核试图缓存及预取数据时,内核空间中进程请求的数据可能已经就绪了。如果这样,进程请求的数据会被拷贝出来。如果数据不可用,则进程被挂起。内核将把数据读入内存。

虚拟内存

你可能已经多次听说过虚拟内存了。让我再介绍一下。

所有现代操作系统都使用虚拟内存。虚拟内存意味着人工或者虚拟地址代替物理(硬件RAM)内存地址。虚拟地址有两个重要优势:

  1. 多个虚拟地址可以映射到相同的物理地址。
  2. 一个虚拟地址空间可以大于实际可用硬件内存。

在上面介绍中,从内核空间拷贝到最终用户缓存看起来增加了额外的工作。为什么不告诉磁盘控制器直接发送数据到用户空间的缓存呢?好吧,这是由虚拟内存实现的。用到了上面的优势1。

通过将内核空间地址映射到相同的物理地址作为一个用户空间的虚拟地址,DMA硬件(只能访问物理内存地址)可以填充缓存。这个缓存同时对内核和用户空间进程可见。

这就消除了内核和用户空间之间的拷贝,但是需要内核和用户缓冲区使用相同的页面对齐方式。缓冲区必须使用的块大小的倍数磁盘控制器(通常是512字节的磁盘扇区)。操作系统将其内存地址空间划分为页面,这是固定大小的字节组。这些内存页总是磁盘块大小的倍数和通常为2倍(简化寻址)。典型的内存页面大小是1024、2048和4096字节。虚拟和物理内存页面大小总是相同的。

内存分页

为了支持虚拟内存的第2个优势(拥有大于物理内 存的可寻址空间)需要进行虚拟内存分页(通常称为页交换)。这种机制凭借虚拟内存空间的页可以持久保存在外部磁盘存储,从而为其他虚拟页放入物理内存提供了空间。本质上讲,物理内存担当了分页区域的缓存。分页区是磁盘上的空间,内存页的内容被强迫交换出物理内存时会保存到这里。

调整内存页面大小为磁盘块大小的倍数,让内核可以直接发送指令到磁盘控制器硬件,将内存页写到磁盘或者在需要时重新加载。事实证明,所有的磁盘I/O操作都是在页面级别上完成的。这是数据在现代分页操作系统上在磁盘与物理内存之间移动的唯一方式。

现代CPU包含一个名为内存管理单元(MMU)的子系统。这 个设备逻辑上位于CPU与物理内存之间。它包含从虚拟地址向物理内存地址转化的映射信息。当CPU引用一个内存位置时,MMU决定哪些页需要驻留(通常通过移位或屏蔽地址的某些位)以及转化虚拟页号到物理页号(由硬件实现,速度奇快)。

面向文件I/O

文件I/O总是发生在文件系统的上下文切换中。文件系统跟磁盘是完全不同的事物。磁盘按段存储数据,每段512字节。它是硬件设备,对保存的文件语义一无所知。它们只是提供了一定数量的可以保存数据的插槽。从这方面来说,一个磁盘的段与 内存分页类似。它们都有统一的大小并且是个可寻址的大数组。

另一方面,文件系统是更高层抽象。文件系统是安排和翻译保存磁盘(或其它可随机访问,面向块的设备)数据的一种特殊方法。你写的代码几乎总是与文件系统交互,而不与磁盘直接交互。文件系统定义了文件名、路径、文件、文件属性等抽象。

一个文件系统组织(在硬盘中)了一系列均匀大小的数据块。有些块保存元信息,如空闲块的映射、目录、索引等。其它块包含实际的文件数据。单个文件的元信息描述哪些块包含文件数据、数据结束位置、最后更新时间等。当用户进程发送请求来读取文件数据时,文件系统实现准确定位数据在磁盘上的位置。然后采取行动将这些磁盘扇区放入内存中。

文件系统也有页的概念,它的大小可能与一个基本内存页面大小相同或者是它的倍数。典型的文件系统页面大小范围从2048到8192字节,并且总是一个基本内存页面大小的倍数。

分页文件系统执行I/O可以归结为以下逻辑步骤:

  1. 确定请求跨越了哪些文件系统分页(磁盘段的集合)。磁盘上的文件内容及元数据可能分布在多个文件系统页面上,这些页面可能是不连续的。
  2. 分配足够多的内核空间内存页面来保存相同的文件系统页面。
  3. 建立这些内存分页与磁盘上文件系统分页的映射。
  4. 对每一个内存分页产生分页错误。
  5. 虚拟内存系统陷入分页错误并且调度pagins(页面调入),通过从磁盘读取内容来验证这些页面。
  6. 一旦pageins完成,文件系统分解原始数据来提取请求的文件内容或属性信息。

需要注意的是,这个文件系统数据将像其它内存页一样被缓存起来。在随后的I/O请求中,一些数据或所有文件数据仍然保存在物理内存中,可以直接重用不需要从磁盘重读。

文件锁定

文件加锁是一种机制,一个进程可以阻止其它进程访问一个文件或限制其它进程访问该文件。虽然名为“文件锁定”,意味着锁定整个文件(经常做的)。锁定通常可以在一个更细粒度的水平。随着粒度下降到字节级,文件的区域通常会被锁定。锁与特定文件相关联,起始于文件的指定字节位置并运行到指定的字节范围。这一点很重要,因为它允许多个进程协作访问文件的特定区域而不妨碍别的进程在文件其它位置操作。

文件锁有两种形式:共享独占。多个共享锁可以同时在相同的文件区域有效。另一方面,独占锁要求没有其它锁对请求的区域有效。

I/O

并非所有的I/O是面向块的。还有流I/O,它是管道的原型,必须顺序访问I/O数据流的字节。常见的数据流有TTY(控制台)设备、打印端口和网络连接。

数据流通常但不一定比块设备慢,提供间歇性输入。大多数操作系统允许在非阻塞模式下工作。允许一个进程检查数据流的输入是否可用,不必在不可用时发生阻塞。这种管理允许进程在输入到达时进行处理,在输入流空闲时可以执行其他功能。

比非阻塞模式更进一步的是有条件的选择(readiness selection)。它类似于非阻塞模式(并且通常建立在非阻塞模式基础上),但是减轻了操作系统检查流是否就绪准的负担。操作系统可以被告知观察流集合,并向进程返回哪个流准备好的指令。这种能力允许进程通过利用操作系统返回 的准备信息,使用通用代码和单个线程复用多个活动流。这种方式被广泛用于网络服务器,以便处理大量的网络连接。准备选择对于大容量扩展是至关重要的。

到此为止,对这个非常复杂的话题有一大堆技术术语。

如果你有想法和疑问,请给给我发评论。

学习快乐!!

相关文章

15 Dec 00:29

正则表达式“派别”简述

相信大家对于正则表达式都不陌生,在文本处理中或多或少的都会使用到它。但是,我们在使用linux下的文本处理工具如awk、sed等时,正则表达式的语法貌似还不一样,在awk中能正常工作的正则,在sed中总是不起作用,这是为什么呢?

这个问题产生的缘由是因为正则表达式不断演变的结果,为了弄清楚这些工具使用的正则语法的不同,我们有必要去简单了解下正则的演变过程,做到知己知彼。当然这个过程本身也是很精彩的,我这里抛砖引玉,希望对大家正确使用正则表达式有所帮助。

诞生期

正则表示式这一概念最早可以追溯到20世纪40年代的两个神经物理学家Warren McCulloch与Walter Pitts,他们将神经系统中的神经元描述成小而简单的自动控制元;紧接着,在50年代,数学家1950年代,数学家Stephen Kleene利用称之为“正则集合”的数学符号来描述此模型,并且建议使用一个简单的概念来表示,于是regular expressions就正式登上历史舞台了。插播一下,这个Kleene可不是凡人,大家都知道图灵是现代人工智能之父,那图灵的博导是Alonzo Church,提出了lambda表达式,而Church的老师,就是Kleene了。关于lambda,之前也写过一篇文章,大家可以参考编程语言的基石——Lambda calculus

在接下来的时间里,一直到60年代的这二十年里,正则表示式在理论数学领略得到了长足的发展,Robert Constable为数学发烧友们写了一篇总结性文章The Role of Finite Automata in the Development of Modern Computing Theory,由于版权问题,我在网上没找到这篇文章,大家有兴趣的可以参考Basics of Automata Theory

Ken Thompson大牛在1968年发表了Regular Expression Search Algorithm论文,紧接着Thompson根据这篇论文的算法实现了qed,qed是unix上编辑器ed的前身。ed所支持的正则表示式并不比qed的高级,但是ed是第一个在非技术圈广泛传播的工具,ed有一个命令可以展示文本中符合给定正则表达式的行,这个命令是g/Regular Expression/p,在英文中读作“Global Regular Expression Print”,由于这个命令非常实用,所以后来有了grep、egrep这两个命令。

成长期

相比egrep,grep只支持很少的元符号,是支持的(但不能用于分组中),但是+|?是不支持的;而且,分组时需要加上反斜线转义,像\( ...\)这样才行,由于grep的缺陷性日渐明显,AT&T的Alfred Aho实在受不了了,于是egrep诞生了,这里的e表示extended,加强版的意思,支持了+|?这三个元符号,并且可以在分组中使用*,分组可以直接写成(...),同时用\1,\2...来引用分组。

在grep、egrep发展的同时,awk、lex、sed等程序也开始发展起来,而且每个程序所支持的正则表达式都或多或少的和其他的不一样,这应该算是正则表达式发展的混乱期,因为这些程序在不断的发展过程中,有时新增加的功能因为bug原因,在后期的版本中取消了该功能,例如,如果让grep支持元符号+的话,那么grep就不能表示字符+了,而且grep的老用户会对这很反感。

成熟期

这种混乱度情况一直持续到了1986年。在1986年,POSIX(Portable Operating System Interface)标准公诸于世,POSIX制定了不同的操作系统都需要遵守的一套规则,当然,正则表达式也包括其中。

当然,除了POSIX标准外,还有一个Perl分支,也就是我们现在熟知的PCRE,随着Perl语言的发展,Perl语言中的正则表达式功能越来越强悍,为了把Perl语言中正则的功能移植到其他语言中,PCRE就诞生了。现在的编程语言中的正则表达式,大部分都属于PCRE这个分支。

下面分别所说这两个分支。

POSIX标准

POSIX把正则表达式分为两种(favor):BRE(Basic Regular Expressions)与ERE(Extended Regular Expressions )。所有的POSIX程序可以选择支持其中的一种。具体规范如下表:
 posix-regexp-favor

从上图可以看出,有三个空白栏,那么是不是就意味这无法使用该功能了呢?答案是否定的,因为我们现在使用的linux发行版,都是集成GNU套件的,GNU是Gnu’s Not Unix的缩写,GNU在实现了POXIS标准的同时,做了一定的扩展,所以上面空白栏中的功能也能使用。下面一一讲解:

  1. BRE如何使用+?呢?需要用\+\?
  2. BRE如何使用|呢?需要用\|
  3. ERE如何使用\1\2\9这样的反引用?和BRE一样,就是\1\2\9

通过上面总结,可以发现:GNU中的ERE与BRE的功能相同,只是语法不同(BRE需要用\进行转义,才能表示特殊含义)。例如a{1,2},在ERE表示的是aaa,在BRE中表示的是a{1,2}这个字符串。为了能够在Linux下熟练使用文本处理工具,我们必须知道这些命令支持那种正则表达式。现对常见的命令总结如下:

- 使用BRE语法的命令有:grep、ed、sed、vim
- 使用ERE语法的命令有:egrep、awk、emacs

当然,这也不是绝对的,比如 sed 通过-r选项就可以使用ERE了,大家到时自己man一下就可以了。

还值得一提的是POSIX还定义了一些shorthand,具体如下:

  • [:alnum:]
  • [:alpha:]
  • [:cntrl:]
  • [:digit:]
  • [:graph:]
  • [:lower:]
  • [:print:]
  • [:punct:]
  • [:space:]
  • [:upper:]
  • [:xdigit:]

在使用这些shorthand时有一个约束:必须在[]中使用,也就是说如果像匹配0-9的数字,需要这么写[[:alnum:]],取反就是[^[:alnum:]]。shorhand 在BRE与EBE中的用法相同。

如果你对sed、awk比较熟悉,你会发现我们平常在变成语言中用的\d\w在这些命令中不能用,原因很简单,因为POSIX规范根本没有定义这些shorthand,这些是由下面将要说的PCRE中定义的。

PCRE标准

Perl语言第一版是由Larry Wall发布于1987年12月,Perl在发布之初,就因其强大的功能而一票走红,Perl的定位目标就是“天天要使用的工具”。Perl比较显诸特征之一是与sed与awk兼容,这造就了Perl成为第一个通用性脚本语言。

随着Perl的不断发展,其支持的正则表达式的功能也越来越强大。其中影响较大的是于1994年10月发布的Perl 5,其增加了很多特性,比如non-capturing parentheses、lazy quantifiers、look-ahead、元符号\G等等。

正好这时也是WWW兴起的时候,而Perl就是为了文本处理而发明的,所以Perl基本上成了web开发的首选语言。Perl语言应用是如此广泛,以至于其他语言开始移植Perl,最终Perl compatible(兼容)的PCRE诞生了,这其中包括了Tcl, Python, Microsoft’s .NET , Ruby, PHP, C/C++, Java等等。

前面说了shorthand在POSIX与PCRE是不同的,PCRE中我们常用的有如下这些:

  • \w 表示[a-zA-Z]
  • \W 表示[^a-zA-Z]
  • \s 表示[ \t\r\n\f]
  • \S 表示[^ \t\r\n\f]
  • \d 表示[1-9]
  • \D 表示[^1-9]
  • \< 表示一个单词的起始
  • \> 表示一个单词的结尾

关于shorthand在两种标准的比较,更多可参考Wikipedia

总结

我相信大家最初接触正则表达式(RE)这东西,都是在某个语言中,像 Java、Python等,其实这些语言的正则表达式都是基于PCRE标准的。
而Linux下使用各种处理文本的命令,是继承自POSIX标准,不过是由GNU扩展后的而已。

大家如果对 sed、awk命令不熟悉,可以参考耗子叔下面的两篇文章:

参考

10 Dec 01:16

一键修改所有密码,Dashlane的新功能让1Password压力很大

by tips+u1410762720@36kr.com(饭遥)

密码管理服务提供商Dashlane最近推出了旗舰性的功能:一键修改所有账户密码,包括设有双重验证的账号。目前Dashlane的这个功能已经支持了包括Apple, Facebook, Google, LinkedIn, Pinterest, Twitter等普通用户高频使用的网站。

打开Dashlane,点击“一键改密码”的绿色按钮,用户就可以将自己账户的密码改为随机生成的新密码。对于有双重验证的账户,在Dashlane站内用户直接回答自己之前设置过密码验证问题即可。“一键改密码”的功能目前主要支持PC端和Mac端,密码修改可以在设备间同步,移动端的功能还在开发之中。

不过因为使用了Dashlane的服务而让自己不成为下一个“詹妮弗·劳伦斯”需要感谢的不是Dashlane本身,整个“一键改密码”核心技术的开发由纽约的创业公司PassOmatic完成,Dashlane收购了他们。

确实,这个功能解决了困扰用户已久的因为密码泄露危及隐私安全的问题。这款功能的负责人同时也是Dashlane联合创始人认为“一键改密码”用户从脆弱的OpenSSL安全协议还有著名的网络安全Bug Heartbleed的阴影中解救出来了。

“定期换密码让你真正减小了隐私被曝光的概率。”

但设备间密码可同步意味着Dashlane会储存用户的密码数据,在被问执法或情报机构是否能够访问用户在Dashlane上生成的密码时,Dashlane CEO Emmanuel Schalit表示这些机构只能在获得传票的情况下访问,同时Dashlane的“编码的加密等级让用户的加密文档很难攻击。”

坦白来讲,对于大多数用户,在1Password拿到苹果年度最佳App的时候还不知道Dashlane的存在。不过这项功能推出可以让Dashlane好好的打一场翻身仗了,Dashlane对单一设备免费,设备间同步每年收费39.99美元(Lastpass每年12美元),1Password在移动端免费,对Mac和Windows用户一次性收费50美元。照此看来,“一键改密码”这个功能足以让用户移动端用户从1Passport,Lastpass立马换到Dashlane,付费增值业务的价位也不是完全不能接受。

所以和你现在在想的一样,1Password这样的同类产品肯定不会坐以待毙,相信他们也会迅速推出“一键改密码”的功能。这一功能也必将成为同类产品的标配。不过同类产品跟进的速度很大程度上决定了他们能够挽回的用户数量。(跟进的技术难点欢迎在评论中讨论:))

Dashlane成立于2009年,目前在巴黎和纽约都设有办公室。在今年5月获得了2200万美元的B轮融资。

除非注明,本站文章均为原创或编译,转载请注明: 文章来自 36氪

36氪官方iOS应用正式上线,支持『一键下载36氪报道的移动App』和『离线阅读』 立即下载!

09 Dec 06:54

国外程序员收集整理的 DevOps 工具和资源

by Leo

DevOps是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。在DevOps的整个流程中,使用一些开源工具可以促进开发与运维之间的沟通,有利于项目的管理,甚至可以达到事半功倍的效果。

本文作者Richard Kraaijenhagen是Owlin创始人,全栈工程师,数据科学家。他收集了DevOps开发可能用到的所有工具,并且把它们按照职责进行分类,本文摘取了部分工具分享给大家,这些工具也可以用于日常软件方面的开发,所以,大家直接Mark吧!

包&产品管理工具

  • Chocolatey:Chocolatey是Windows下一款开源的命令行包管理软件 ,简单说这就是Windows的apt-get;
  • FPM:全称是Effing package management,该死的软件包管理器,极大的缓解了多个平台构建软件包(deb,rpm,等)的痛苦;
  • Herd:是一个基于Twitter Murder的文件分布系统;
  • Vagrant Cachier:Vagrant的一个插件,用于缓存包方面的管理;
  • WiX Toolset:提供一组最强大的工具集来帮助你创建Windows安装包。该工具集从XML源代码构建你的Windows安装程序包,可以无缝集成到构建过程;
  • Boxstarter:利用Chocolatey包管理工具来自动化安装软件和创建可重复、脚本化的Windows环境;
  • Elita:Elita是一个利用git和salt进行持续部署(部署作为服务)和API-driven基础设施的引擎/框架;
  • Fig:主要用来跟Docker一起来实现的快速隔离的开发环境;
  • Pulp:Pulp是一个用来管理软件库以及相关内容的平台;
  • Veewee:Veewee是一个开源工具,用来创建和配置轻量级、可再生、便捷式虚拟机环境。

日志&监控

  • AmonOne:现代化的自托管服务器监控工具;
  • Anthracite:一个事件/日志改变/管理应用程序;
  • collectd3:是一个可视化的collectd系统性能统计工具;
  • collectd:是一个守护(daemon)进程,用来收集系统性能和提供各种存储方式来存储不同值的机制;
  • Diamond:是一个基于Python的守护程序,主要用来收集系统指标,并且把它们发布到Graphite(或其它)工具中;
  • Errbit:是一个用于收集和管理程序错误的开源工具;
  • Sensu:一个开源的监控框架;
  • Logstash:是一个应用程序日志、事件的传输、处理、管理和搜索的平台。你可以用它来统一对应用程序日志进行收集管理,提供Web接口用于查询和统计;
  • log.io:一个实时的开源日志监控工具;
  • FnordMetric:是一个基于redis/ruby的实时事件跟踪应用,是个收集和可视化时间序列数据的框架,用户可以在几分钟内创建漂亮的实时分析仪表盘;
  • Logster:是一个工具,读取日志文件然后创建Graphite 或 Ganglia可用的指标数据。比如你可能使用logster来图形化在你的Web Server日志中的HTTP响应发生数量;
  • Kibana:是一个为Logstash和ElasticSearch提供的日志分析的Web接口。可使用它对日志进行高效的搜索、可视化、分析等各种操作;
  • Monit:是一款功能非常丰富的进程、文件、目录和设备的监测软件,用于Unix平台。 它可以自动修复那些已经停止运作的程序,适合处理那些由于多种原因导致的软件错误;
  • Metrics:这并不是Java库,而是基于Go的一个轻量级的检测器;
  • Graphite:是一个用于采集网站实时信息并进行统计的开源项目,可用于采集多种网站服务运行状态信息;
  • Ganglia:Ganglia是一个跨平台可扩展的、高性能计算系统下的分布式监控系统,如集群和网格;
  • Server Density:一个跨平台的监控系统;
  • Folsom:Folsom是一款受 Coda Hale’s metrics启发的、基于Erlang的度量系统;
  • CMB (Cloud Message Bus):是一个高可用、横向扩展的队列和通知服务,兼容AWS SQS和SNS;
  • Glances:是一款用于Linux、BSD的开源命令行系统监视工具,它使用Python语言开发,能够监视CPU、负载、内存、磁盘I/O、网络流量、文件系统、系统温度等信息。
  • Uptime:使用Node.js、MongoDB和Twitter Bootstrap开发的远程监控系统;
  • Icinga:Nagios的扩展版本;
  • Packetbeat: 是开源应用监控和包跟踪系统;
  • Zipkin:是Twitter的一个开源项目,允许开发者收集Twitter各个服务上的监控数据,并提供查询接口;
  • Dead Man’s Snitch:是一款监控Heroku Scheduler、计划的监视工具;
  • Statsd:是一个Node.js的daemon程序,简单,轻巧。使用的UDP协议,可以和Graphite图片渲染应用结合;
  • Riemann:一个网络监控系统;
  • Puppet Dashboard:Puppet Dashboard是一个Web接口,为Puppet提供节点分类和报告功能,是一个开源的配置管理工具;
  • jmxtrans:jmxtrans是一款非常强大的工具,使用它可以轻易生成基于json的配置文章,然后再以你想要的格式输出;
  • Scales:跟踪服务器状态和统计指标,使你全面掌握服务器状态,还可以发送指标到Graphite来图像呈现或者向文件写入崩溃信息;
  • Zabbix:是一个基于Web界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案;
  • Graylog 2:Graylog2是一个用来将系统日志syslog保存到MongoDB中的工具。

进程管理

  • Bouncy:可以作为HTTP路由主机;
  • Supervisor:是一个客户端服务器系统,允许用户监控和控制类Unix操作系统上的进程数;
  • God:由Ruby实现的进程监控框架。

服务发现

  • Consul:简化了分布式环境中的服务的注册和发现流程,通过HTTP或者DNS接口发现。支持外部SaaS 提供者等;
  • etcd:是一个高可用的Key/Value存储系统,主要用于分享配置和服务发现;
  • Apache ZooKeeper:是Apache Hadoop的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题;
  • Weave:创建一个虚拟网络并连接到部署在多个主机上的Docker容器。

持续集成和交付

  • Buildbot:是一个系统的自动化编译/测试周期最需要的软件,以验证代码的变化。通过自动重建和测试每次发生了变化的东西,在建设迅速查明之前,减少不必要的失败;
  • Cabot:是一个开源,自我托管的监控工具;
  • Jenkins:是基于Java开发的一种持续集成工具,用于监控持续重复的工作;
  • Hubot:基于脚本具有很高的灵活性,任何人都可以编写自己的脚本来扩展基本功能;
  • Hudson:是一个可扩展的持续集成引擎,主要用于:持续、自动地构建/测试软件项目、监控一些定时执行的任务;
  • CruiseControl.rb:是一个持续集成服务器,它可以让团队里的每个人随时了解项目的健康状况和进度;
  • OpsBot:是一个开源的、可插入的改善通信的机器人。

希望这些工具能够给开发者带来实实在在的帮助,想要查看更多工具,大家可以 访问原文,原文中的工具列表会持续更新。

最后,再跟大家分享一个 DevOps BookMarks,这里面涉及了DevOps方方面面的工具和内容,有兴趣的同学可以前去学习。

国外程序员收集整理的 DevOps 工具和资源,首发于博客 - 伯乐在线

07 Dec 01:24

关于 Zsh,我最喜欢的那些特性

by ashiontang

Zsh已经被追求交互式shell体验的开发者广为接受。虽然我很晚才成为这群开发者中的一员,但希望你也能加入到行列之中。

自从我第一次登录shell,我就开始学习bash配置文件。我整理自动补全,别名和函数,这些有助于快速完成重复性工作。当Zsh开始流行时,博客和评论的焦点是bash配置文件中已经配置的特性。我始终不为所动。直到读到oh-my-zsh相关的内容。该项目为Zsh配置了默认属性,并且有实用的在线引导。作为后来者,我以说服自己立刻转换shell为出发点写下这篇文章。

背景

如果你很熟悉Bash,担心会失去已经积累的知识。那么,不必担心,99%的Bash操作仍然适用于Zsh。你可以改变shell而不必失去积累的知识。

本指南使用了截至本文为止最新5.0.2版本的Zsh和oh-my-zsh。从基本安装开始,我没有改便任何东西。

当你在例子中看到<TAB>字符串,它表示按下tab键,而不是输入该字符串。

‘cd’命令的自动补全

让我们从一项常用的任务开始。不起眼的目录切换指令。

当你在Bash中按下<TAB>键,你会看到当前目录的文件列表。

此时,cd命令并不是特别有用,因为你只能进入目录。Zsh知道这一点并且会给你显示出可能的有效目标。

它不仅能帮你匹配,而且还允许你使用键盘导航到你想要的目录下。

TAB自动补全和使用键盘调节选项的结合是Zsh易于使用的核心所在。

‘ls’命令自动补全

你不必输入整个目录名称,只需输入初始几个可以唯一区别与其他目录的字母,Zsh会自动匹配出剩余部分。

变戏法一样!

聪明的历史记录

你可能熟悉 <CTRL>+R 递归查找历史命令的使用。在Bash和Zsh中,这是命令重用很好的方法。

Zsh要更胜一筹。你可以输入命令的一部分并按下<UP>键。

它会找到历史记录中最后一条以’ls’开头的指令。如果需要,我们可以继续点击向上键循环查找。

命令历史共享

在Bash中,每一个shell都有自己的历史。Zsh与所有现行的shell共享命令历史。这意味着你不必记住你在哪里输入过命令。

环境变量展开

在shells中可以访问用的环境变量。有时,我们想要改变这些变量值或者只是查看它们。

在Zsh中,你可以按下<TAB>键来展开这些值。

进程关闭命令的自动补全

通常,我会在用ps命令查看进程值后使用kill命令,或者如果明确可以使用pkill,Zsh给了你另外一个选择。

敲击tab键,你将会得到一个可用键盘导航的命令列表,该列表以你在kill命令后输入的字符开始。

那个选项是什么?

为了弄明白一条命令如何工作,通常你会尝试执行它,或者不带参数,带-help参数或者查看帮助页面。Zsh给了你另外一个选择。

输入选项的开始,然后点击<TAB>。

上述操作会列举出有内联说明的选项名称。你也可以选择使用键盘导航。

这不只限于ls命令。同样适用于大量像netstatgitchmod这样的令人困惑的命令。

Git的改善

每个人都有自己最喜欢的git命令的别名。oh-my-zsh也有大量的别名。

当进入git控制的目录时,它还能改善给你的提示。

你可以看到我所在的分支名,那个小的闪电符号是在提示我当前目录下有未提交的更改。

上一条指令执行失败

你可能已经注意到截图中提示开头的绿色箭头。这表明上一条命令被正确执行。


如果命令失败,那么在下一条指令被正确执行前该提示符会变成红色。

文件名生成

在Bash中,我经常使用find命令和xargs一起进行查找,使用它们完成任务。Zsh内建的globbing支持涵盖了大部分的应用场景。

假设我们需要验收项目,只需要找出今天发生变更的文件。

我们可以给ls命令的搜索部分添加限制参数来筛选出我们想要的。

另一个方便的技巧是使用**,这是一种递归的搜索。在当前目录下什么位置可以找到Rakefile实例?

让我们再使用修饰符来递归查找超过20mb的文件。

不仅仅局限于ls指令的使用。在该工程中使用了多少行Clojure代码?


假设我们想要递归删除该工程中的大部分Clojure文件。

嗯。我非常确信这一点。所以,让我们按下tab键来详细列出所有文件。

你的意思是说这不是吗?

有时我们会忘记文件名,或者会误输文件名的中间部分而不是从头开始。Zsh知道我们输错了,而且会自动更正。

Zsh自动更正。

有用的别名

和git的别名一样,oh-my-zsh也有很多有用的常用别名。

我非常喜欢使用…来跳转两级目录。

运行进程设置标签名

当你想要知道服务器运行在什么环境下,将标签名改成和运行进程一样非常有用。

在Vim中编辑长命令

使用<CTRL>+K删除字符这样的快捷键,你可以在Bash和Zsh中编辑长命令。但是,有些时候你想要功能更强大的编辑器来编辑命令。

无论何时遇到这种情况,点击 <CTRL>+X <CTRL>+E将当前命令输入到 $EDITOR中。

熟悉的$EDITOR。

我们可以编辑命令,保存退出后返回到终端中。

你最喜欢的工具插件

oh-my-zsh有一张你可能使用的综合工具插件列表。我使用了rake插件, 它允许你从Rakefile中输入可用的rake命令。

双击<TAB>进入键盘导航列表。

是不是很棒?

直到最近我才意识到Zsh是多么有用的交互式shell。不得不更换shell的理由如下:

  • 各种Tab补全和导航
  • 充满活力的插件社区为我们的dotfile提供合理的基准。
  • 你在Bash中学到的99%仍然适用。
  • Tab complete and navigate all the things
  • A vibrant plugin community providing a sensible baseline for our dotfiles
  • 99% of what you know from Bash still works

你准备好使用chsh命令修改shell配置了吗?

关于 Zsh,我最喜欢的那些特性,首发于博客 - 伯乐在线

06 Dec 09:24

The SSD Endurance Experiment: Two petabytes

04 Dec 00:34

专家否认“地铁雾霾超地上16倍”:最高不超12倍

“昨天,关于“地铁雾霾”的说法在网络上迅速传播。有报道援引国内一家环境研究所的数据称,地铁内PM2.5是室外的16倍。对此,该机构负责人称,确实测过地铁内PM2.5的数据,但从没提过是室外16倍的说法。专家表示,一日的测量数据得出地铁内空气比较差也是不科学的。”






02 Dec 07:36

计算机实际上是如何工作的

by AbandonZHANG

【伯乐在线导读】:网友 r00nk 写的一篇文章(分 8 节),还专门建了一个网站。他为什么会写这个?因为他之前在 Reddit 上看到一个帖子,有人问“计算机实际上是如何工作的?比如,它如何传送、阅读、展示数据”。他之前的回答是最佳回复,后来继续补充丰富内容了,便有了下文。


二进制

让我们马上进入数据的梦幻王国。

五的符号是什么?5

十的符号是什么?10

等等,这不是符号“1和0”吗?

是的,在我们的编码系统中,当我们要表示”十”的时候,我们会写1和0。这里没有单独表示十的符号,我们简单地循环利用已有的一些符号(0~9)。所以我们把这样的编码系统叫做“base-ten”或者“decimal”(十进制)。

“1和0”,“真和假”,“开或关”这些东西你可能已经听过,它们指的都是一个不同的编码系统。在我们的十进制系统中,我们写“10”来表示十,但是在binary(二进制)中,我们写“10”来表示二。在二进制中没有单独表示二的符号,正像十进制中没有单独表示十的符号一样。“开”或“关”在二进制中就是指“1”或“0”。

为保证你已经理解了二进制,试着点击下面的二进制计数器:

(原图可交互,在原文可查看。)

与非

现在来讲些完全不同,但是相关的东西。

在计算机理论中有个叫“逻辑门”的东西,它是个有两个输入和一个输出的器件,只接收“开”或“关”这样的输入,输出也是,“开”或“关”。你可能已经看出它和二进制的关系。

一个逻辑门的输出是基于它的输入的。一个逻辑门的实例是NAND gate(与非门),试着点击左边的开关:

(原图可交互,在原文可查看。)

当与非门的两个输入都是“开”的时候,输出是“关”;其他情况输出都是“开”。

还有一些更多种类的逻辑门,不过我们现在只需要关心与非门。如果你想看看与非门在现实中什么样子,点这里

 

内存

想知道计算机怎么存储数据吗?

下面这个器件叫“D型锁存器”(D-Lauch),它存储1个二进制位。

(原图可交互,在原文可查看。)

上面的开关是要存储的值(0 或 1),底下的开关控制这个器件是否存储。

八个这样的器件就可以用来在存储器中存储一个字节了。

 

更多逻辑门

在我们可以继续之前,我需要给你看看更多的逻辑门。

(原图可交互,在原文可查看。)

加法器

但是我们可以拿这些存储的数字做什么呢?

我们可以给它们做数学运算。

下面的器件叫做加法器(adder),它可以把两个位加起来。

(原图可交互,在原文可查看。)

在十进制中,如果要计算5+5,你就要追加一个1来生成10。当我们在二进制中计算1+1时,相同的事情就发生了,我们需要追加(输出)一个二进制1。

如果我们有多个位需要相加,我们可以把一个加法器的追加输入与另一个加法器的追加输入相连,这样来组合起这些器件。

 

编码

酷~我们现在有一个基本的计算器了。我们怎么样才能最大程度的发挥它并组成一台计算机呢?

编码。

现在你知道了数据是什么。编码也很好解释,它就是数据。至于为什么它和其他的数据不同,当然是因为CPU把它解释为指令。

比如我们想让这个计算机做数学运算,我们可以使用像下面的系统:

指令 编码
“一个数加上另一个数” 00000001
“一个数减去另一个数” 00000010

有了它,我们可以根据数据的形式安排逻辑门来怎么使用。

现在,真的很快,内存在计算机中由内存地址组织起来,这样就允许CPU在确切的位置来请求内存。一般来说地址大小以字节为单位,即8位。所以如果我们想要存取第5个内存位置或诸如此类,我们需要把它存为“00000101”。

回过头来加入更多的指令到我们的表中:

“移动这个数据到其他位置” 00000011

酷。现在我们可以实现这样的东西:

“将第5个内存中的数和第7个内存中的数加起来”

把它分解:

(add) (memory address #5) (memory address #7)

实际上就是:

00000001 00000101 00000111

很可爱是吧?

 

指令指针

但是CPU怎么知道去哪里取指令呢?

在CPU中,有一小块存储。它可以做各种各样的事,比如可以保存某个叫“指令指针寄存器”的东西。指令指针寄存器保存下一个指令的地址,并且可以在每一个指令结束后自增。所以,CPU读取指令指针寄存器,取出下一个地址,执行它,指令指针寄存器自增,然后回到第一步。

指令指针寄存器的值可以存到一个专门的存储单元中。你有没有想过计算机中的一个无限循环是怎样的?就是当一个指令指针寄存器指向的指令让这个指令指针寄存器再次指向同一个指令的时候。

 

CPU

这是一个相对简单的可以给你玩玩的CPU:

(原图可交互,在原文可查看。)

如果要修改内存,在CPU开关关着的时候点击相应位即可。你也可以滚动查看内存。如果要让CPU开始执行指令的话,就打开开关。

这个CPU有3个不同的指令

指令 编码
指令指针寄存器指向地址的下一位地址的数加上下下一位地址的数 00000001
指令指针寄存器指向地址的下下一位地址的数减去下一位地址的数 00000010
指令指针寄存器指向地址的下一位地址的数移动到其下下一位地址 00000011

所有的指令都有3个字节(包括操作数)。这意味着在每一个指令结束后指令指针寄存器将增加3。

这个指令指针寄存器可以通过存储地址00000000访问。

计算机实际上是如何工作的,首发于博客 - 伯乐在线

02 Dec 07:35

人工智能和机器学习领域有哪些有趣的开源项目?

by Leo

本文简要介绍了10款   Quora上网友推荐的 人工智能和机器学习领域方面的开源项目。


GraphLab

GraphLab是一种新的面向机器学习的并行框架。GraphLab提供了一个完整的平台,让机构可以使用可扩展的机器学习系统建立大数据以分析产品,该公司客户包括Zillow、Adobe、Zynga、Pandora、Bosch、ExxonMobil等,它们从别的应用程序或者服务中抓取数据,通过推荐系统、欺诈监测系统、情感及社交网络分析系统等系统模式将大数据理念转换为生产环境下可以使用的预测应用程序。( 详情

项目主页: http://graphlab.org/

Vowpal Wabbit

Vowpal Wabbit(Fast Online Learning)最初是由雅虎研究院建设的一个机器学习平台,目前该项目在微软研究院。它是由John Langford启动并主导的项目。

项目地址:  http://hunch.net/~vw/

scikits.learn

scikit-learn是一个开源的、构建在SciPy之上用于机器学习的 Python 模块。它包括简单而高效的工具,可用于数据挖掘和数据分析,适合于任何人,可在各种情况下重复使用、构建在 NumPy、SciPy和 matplotlib 之上,遵循BSD 协议。(详情

项目地址: http://scikit-learn.org/stable

Theano

Theano是一个python库,用来定义、优化和模拟数学表达式计算,用于高效的解决多维数组的计算问题。它使得写深度学习模型更加容易,同时也给出了一些关于在GPU上训练它们的选项。( 详情

项目地址: http://deeplearning.net/software/theano/

Mahout

Mahout 是 Apache Software Foundation(ASF) 旗下的一个开源项目,提供一些可扩展的机器学习领域经典算法的实现,旨在帮助开发人员更加方便快捷地创建智能应用程序。Mahout包含许多实现,包括聚类、分类、推荐过滤、频繁子项挖掘。此外,通过使用 Apache Hadoop 库,Mahout 可以有效地扩展到云中。

项目主页: http://mahout.apache.org/

pybrain

pybrain是Python的一个机器学习模块,它的目标是为机器学习任务提供灵活、易应、强大的机器学习算法。pybrain包括神经网络、强化学习(及二者结合)、无监督学习、进化算法。以神经网络为核心,所有的训练方法都以神经网络为一个实例。

项目主页: http://pybrain.org/

OpenCV

OpenCV是一个基于(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。( 详情

项目主页: http://opencv.org/

Orange

Orange 是一个基于组件的数据挖掘和机器学习软件套装,它的功能即友好,又很强大,快速而又多功能的可视化编程前端,以便浏览数据分析和可视化,基绑定了 Python以进行脚本开发。它包含了完整的一系列的组件以进行数据预处理,并提供了数据帐目,过渡,建模,模式评估和勘探的功能。

项目主页: http://orange.biolab.si/

NLTK

NLTK(natural language toolkit)是python的自然语言处理工具包。2001年推出,至今发展非常活跃。它的主要作用是为了教学,至今已经在20多个国家60多所高校使用,里面包括了大量的词料库,以及自然语言处理方面的算法实现:分词, 词根计算, 分类, 语义分析等。

项目主页: http://nltk.org/

Nupic

Nupic是一个开源的人工智能平台。该项目由Grok(原名 Numenta)公司开发,其中包括了公司的算法和软件架构。 NuPIC 的运作接近于人脑,“当模式变化的时候,它会忘掉旧模式,记忆新模式”。如人脑一样,CLA 算法能够适应新的变化。( 详情

项目主页: http://numenta.org/nupic.html


以上是小编整理的10款人工智能和机器学习领域的开源项目。更多项目可参看这个列表:http://deeplearning.net/software_links/,或查看 Quora中更多网友的精彩回答

人工智能和机器学习领域有哪些有趣的开源项目?,首发于博客 - 伯乐在线

01 Dec 10:36

从把3000行代码重构成15行代码谈起

by Leo

如果你认为这是一个标题党,那么我真诚的恳请你耐心的把文章的第一部分读完,然后再下结论。如果你认为能够戳中您的G点,那么请随手点个赞。

把三千行代码重构为15行

那年我刚毕业,进了现在这个公司。公司是搞数据中心环境监控的,里面充斥着嵌入式、精密空调、总线、RFID的概念,我一个都不懂。还好,公司之前用Delphi写的老客户端因为太慢,然后就搞了个Webform的替代,恰好我对Asp.Net还算了解,我对业务的不了解并不妨碍我称成为这个公司的一个程序员。小公司也有小公司的好,人少,进去很快负责代码开发。我当然也就搞这个数据中心智能管理系统啦。

这个系统非常的庞大,尤其牛逼的是支持客户端组态,然后动态生成网页,数据还能通过Socket实时监控(那时我还真就不懂网络编程)。这个对于当时的我来说,真真是高、大、上呐!!当时跟着了解整个系统大半个月才算能够调试,写一些简单的页面。

在维护系统的过程中,时不时要扩展一些功能,也就接触了下面这个类:

看到没有,就是当年最最流行的三层架构的产物,对于刚出茅庐的毛头小子来说,这是多么专业的文件头注释,还有反射也就算了,这构造函数还能静态的,还能私有的?那时刚接触这么高大上的代码的我,瞬间给跪了!

但是,类写多了,我就感觉越来越别扭,就是下面这段代码:

每增加一个表,除了要改接口、要改DAL、要改BLL之外,还得在这个工厂类添加一个方法,真真是累到手抽筋,即使有当时公司了的G工给我推荐的神器——动软代码生成器,这粘贴复制的几遍,也是让我感觉到异常繁琐,有时候打键盘稍微累了点,还把复制出来代码改错了,你妹的,难道这就是程序员该干的事情,不,绝对不是!我想起了一句至理名言:当你觉得代码重复出现在程序中的时候,就应该重构了。是的,在这句话的指导下,我开始了折腾,决定挑战这个高大上的代码,事实证明,思想的力量是无穷的。

那么,怎么修改呢,仔细观察之后,发现其中className的生成跟返回的类型非常类似,只是一个是类名,一个是字符串,这两者之间应该能够关联起来。于是google了一下(当时GFW还没猖獗起来哈),隐隐约约就找到了“反射”这两个字,深入了解之后,确定可以完成。

接下来,就是返回的类型了,返回的类型并不固定,但是似乎很有规律……这个似乎好像在哪里见过,对了,模板,C++课程上有讲过的,于是再次google,了解到了C#中使用了泛型代替了C++中的模板。在学习完泛型和反射之后,并参考了网上的一些文章,我捣鼓出了下面的代码:

没错,就是它了,三层架构年代最流行的工厂类……

看着原来滚十几屏幕的代码,变成了十多行的代码,真是爽到了骨子里去了,太干净了!唯一让我担忧的是,我进公司的时候,帮忙整理公司申请软件著作权都是需要代码量的,根据代码多少行来评估软件的大小,万一老板知道了我非但没有帮公司增加代码量,还减少了,会不会立即把我开掉?我没敢给我们老板展示我优秀的成果,所幸,这段代码非但没有出过任何问题,还避免了以前同事老是在新增一个类之后,把代码复制过来,但是没有正确修改的问题,大大提高了效率。虽然,我没敢大事宣布我的劳动成果,但是这次成功的修改,则彻底让我走上了代码重构的不归路。

看到这里,大家应该知道这个案例是否真实的了吧。我相信,从08年开始的码农们,看到这种类似的代码绝对不比我少。那么,我想告诉你们的是什么呢?

  • 要在编程过程中多思考
  • 编程的思想很重要,请多看点经典的书
  • 从小处着眼,慢慢重构,尤其在应对一个大型的系统
  • 当重复出现的时候,你应该考虑重构了
  • 粘贴复制的代码越少,你的系统越稳定

少用代码生成器

我们来分析一下,为什么我之前的前辈会写出上面的代码。我归结起来有以下几点:

  • 因为使用了动软代码生成器,生成代码方便,就没多想了。
  • 三层架构的概念倒是了解了,但是没有去深入思考就拿来应用
  • 遇到重复的代码,没有重构的概念,这是思想的问题——思想比你的能力重要

至今为止,还是很多人使用代码生成器,那么我们应该怎么对待这个问题呢。我认为,代码生成器确实可以减少你不少工作,但是少用,那些重复性的工作,除了部分确实是没有办法的,其他大部分都是可以通过框架解决的,举例来说,像三层架构,真正需要用到代码生成器的,也就是Model类而已,其他的完全可以在框架中完成。因此你要竭尽全力的思考怎么在框架中来减少你的重复性工作,而不是依赖于代码生成器

另外,如果你还是在用相关的代码生成工具,请重新定义“动软代码生成器”的代码模板,自己写一个模板;或者使用CodeSmith来完全制定自己的代码生成,因为动软给的代码模板真心乱,比如下面这段代码:

for (int n = 0; n < rowsCount; n++)
{
	model = new DBAccess.Model.eventweek();
	if(dt.Rows[n]["GroupNo"].ToString()!="")
	{
		model.GroupNo=int.Parse(dt.Rows[n]["GroupNo"].ToString());
	}
	if(dt.Rows[n]["Week0"].ToString()!="")
	{
		model.Week0=int.Parse(dt.Rows[n]["Week0"].ToString());
	}
	if(dt.Rows[n]["Week1"].ToString()!="")
	{
		model.Week1=int.Parse(dt.Rows[n]["Week1"].ToString());
	}
}

首先,你就不能用 var row=dt.Rows[n] 替代吗?其次,直接用int.Parse效率多低?再次,dt.Rows[n]["Week0"]NULL怎么办?

不要重复发明轮子

我们再来看看其他的一些代码:

public List<string> GetDevices(string dev){
	List<string> devs=new List<string>();

	int start=0;
	for(int i=0;i<dev.Length;i++){
		if(dev[i]=='^'){
			devs.Add(dev.SubString(start,i));
			start=i+1;
		}
	}

	return devs;
}

有没有很眼熟,没错,这就是对String.Split()函数的简单实现。我的前辈应该是从c++程序员转过来的,习惯了各种功能自己实现一遍,但是他忽略了C#的很多东西。我们不去评判这段代码的优劣,而实际上他在很长一段时间都运行得很好。我们来看看使用这一段代码有什么不好的地方:

  • 重复发明轮子。花费了额外的时间,函数的健壮性和很差
  • 可读性差。其实是一个很简单的功能,但是用上了这么一段函数,起初我还以为有什么特别的功能。

那么,我们应该怎样去避免重复发明轮子呢?我从个人的经历来提出以下几点,希望能够对各位有所帮助:

  • 了解你所学的编程语言的特性。你可以看一本基础的入门书籍,把所有的特性浏览一遍,或者上MSDN,把相关的内容过一遍。
  • 在你决定动手发明一个轮子之前,先搜索一下现成的解决方案。你还可以到CodeProject、GitHub之类的网站搜索一下。在知乎上有很多大牛其实都在批评,为什么你提问之前,不能首先去搜一下是否有现成的答案,反而指责没有回答他的问题。
  • 你有一定的基础之后,还应该去读一下相关的经典书籍,深入了解其中的原理。比如,你觉得你有一定的基础了,我建议你去吧《CLR Via C#》多读几遍,你了解原理越多,你越是能够利用这编程语言的特性,从而来实现原本那些你认为要靠自己写代码的功能。

这里我再举一个我自己的例子。在我现有的程序中,我发现我需要越来越多的线程来执行一些简单的任务,比如在每天检测一下硬盘是否达到90%了,每天9点要控制一下空调的开启而在网上6点的时候把空调关掉。线程使用越来越多,我越是觉得浪费,因为这些现场仅仅只需完成一次或者有限的几次,大部分时间都是没有意义的,那么怎么办呢?我决定自己写一个任务类,来完成相关的事情。说干就干,我很快把这个类写出来了。

public abstract class MissionBase : IMission
{
    private DateTime _nextExecuteTime;
    protected virtual DateTime[] ExecuteTimePoints { get; private set; }
    protected virtual int IntervalSeconds { get; private set; }
    protected IEngine Engine { get; private set; }

    public bool IsCanceled{get{……}}
    public bool IsExecuting{get{……}}
    public bool IsTimeToExecute{get{……}}

    public abstract bool Enable { get; }
    public abstract string Name { get; }

    protected MissionBase(IEngine engine)
    {
        ExecuteTimePoints = null;//默认采用间隔的方式
        IntervalSeconds = 60 * 60;//默认的间隔为1个小时

        Engine = engine;
    }

    /// 任务的执行方法
    public void Done()
    {
        if (Interlocked.CompareExchange(ref _isExecuting, 1, 0) == 1) return;

        try
        {
			……
        }
        finally
        {
            Interlocked.CompareExchange(ref _isExecuting, 0, 1);
        }
    }
	
	///实际方法的执行
    protected abstract void DoneReal();
}

但是,实际上这个任务方法,并不好用,要写的代码不少,而且可靠性还没有保障。当然,我可以继续完善这个类,但是我决定搜索一下是否还有其他的方法。直到有一天,我再次阅读《CLR Via C#》,看到线程这一章,讲到了System.Threading.Timer以及ThreadPool类时,我就知道了,使用Timer类完全可以解决我的这个用尽量少的线程完成定时任务的问题

因为从原理上来说,Timer类无论你声明了多少个,其实就只有一个线程在执行。当你到了执行时间时,这个管理线程会用ThreadPool来执行Timer中的函数,因为使用的ThreadPool,执行完成之后,线程就马上回收了,这个其实就完全实现了我所需要的功能。

等你无法重构的时候再考虑重写

我带过很多优秀的程序员,也与很多优秀的程序员共事过。有一大部分的程序员在看到一套系统不是那么满意,或者存在某些明显的问题,就总是忍不住要把整套系统按自己觉得可以优化的方向来重写,结果,重写结构往往并不令人满意。系统中确实存在很多不合理的地方,但是有不少的这种代码,恰恰是为了解决一些特定场景下的问题的。也就是说,所有的规范以及编程的原则,其实也是有条件限制的,他可能在大部分的时候是正确的,能够指导你完成你的任务,但是,并不是在所有地方都是适用的。比如数据库范式,但实际中我们的设计往往会考虑冗余,这是违背范式的,但是为什么还有那么多人趋之若鹜呢?因为我们可能需要用空间换时间。

如果我们一开始就考虑重写,那么你可能会陷入以下的困境:

  • 需要花更大的精力来完成一些看似简单的BUG
    你要知道,有一部分看似错误或者非常不优美的代码,其实恰恰是为了解决一些非常刁钻的问题的。
  • 再也无法兼容老的系统了
    你急于把原有系统重写,却往往忽略了对原有系统的兼容,那么你新的系统的推进则会十分缓慢。而老系统的维护,又会陷入及其尴尬的情况。
  • 过度设计,导致重写计划迟迟无法完成
    有重写冲动的程序员往往是在架构设计上有一些读到的见解,他们善于利用所学的各种设计模式和架构技巧来建立系统,但是越是想尽可能的利用设计模式,越是陷入过度设计的困局,导致重写的计划迟迟都无法完成。
  • 无法有效利用现有系统已经完成并测试的代码
    如果你确实有必要进行重写,我还是建议你把代码尽可能的重构。因为重构之后的系统,能够让你更轻易的重写,又最大限度了保留以前可用的业务代码

我举个例子,说明如何通过重构更好的利用现有代码的。

我有一个非常庞大的系统,其中有一块功能是用于数据采集、存储、告警管理以及电话、短信等告警通知。大致的结构如下:

class MainEngine:IEngine{
	public MainEngine(ConfigSettings config){
		
	}

	public void Start();
	public void Stop();
}

需要增加新的业务功能时,程序员写的代码往往是这样的:首先时修改配置类

class ConfigSettings{
	public bool NewFuncEnable{get;private set;}
	public ConfigSettings(){
		NewFuncEnable=xx;//从配置文件读取
	}
}

接着修改主程序:

class MainEngine:IEngine{
	private NewFuncClass newCls=new NewFuncClass();
	public MainEngine(ConfigSettings config){
	}

	public void Start(){
		if(config.NewFuncEnable)
			newCls.Start();
	}
	public void Stop(){
		if(config.NewFuncEnable)
			newCls.Stop();
	}
}

在修改的过程中,往往是根据配置文件来判断新功能是否启用。上面代码会造成什么问题呢:

  • 主程序代码和扩展功能耦合性太强,每增加一个功能都要修改主程序代码,这里非常非常容易出错。尤其是新的人进度开发组,很容易就忘主程序中增加了一些致命性的代码。比如上述的扩展功能,可能是在特定的项目中才会有这个扩展功能,但是,写代码的人忘记增加是否启用的配置选项了,导致所有的项目都应用了这个功能,而这个功能需要特定的表,这样就悲剧了。即使是你增加了配置,也是非常的不美观,因为在通用的版本中使用了这个配置,往往会让定制项目以外的人员感到困惑
  • 增加扩展功能的人还需对整个MainEngine代码有一定的熟悉,否则,他根本就不知道在Start方法和Stop方法进行newClas的对应方法的调用
  • 如果你打算对这段代码进行重写,那么,你会感到非常的困难,因为你分不清楚newCls这个新实例的作用,要么你花大精力去把所有代码理清楚,要么直接就把这段新增的业务代码去掉了。

那么我们如何对这段代码进行重构呢。首先,我们把新功能注册的代码抽取出来,通过反射来实现新的功能的注册。

 private void RegisterTaskHandlerBundles()
    {
        var bundles = xxx.BLL.Caches.ServiceBundleCache.Instance.GetBundles("TaskHandlerBundle");
        if (bundles != null && bundles.Count > 0)
        {
            var asmCache = new Dictionary<string, Assembly>();
            foreach (var bundle in bundles)
            {
                try
                {
                    if (!asmCache.ContainsKey(bundle.Category)) asmCache.Add(bundle.Category, Assembly.Load(bundle.AssemblyName));
                    var handler = (ITaskHandler)asmCache[bundle.Category].CreateInstance(bundle.ClassName, false, BindingFlags.Default, null,
                        new object[] { this, bundle }, null, null);
                    _taskHandlerBundles.Add(bundle, handler);
                }
                catch (Exception e)
                {
                    NLogHelper.Instance.Error("加载bundle[Name:{0},Assembly:{1}:Class:{2}]异常:{3}", bundle.Name, bundle.AssemblyName, bundle.ClassName, e.Message);
                }
            }
        }
    }

修改MainEngine代码

class MainEngine:IEngine{
	private NewFuncClass newCls=new NewFuncClass();
	public MainEngine(ConfigSettings config){
		RegisterTaskHandlerBundles();
	}

	public void Start(){
		_taskHandlerBundles.Start();
	}
	public void Stop(){
		_taskHandlerBundles.Stop();
	}
}

OK,现在我们再来看看怎么实现原来的新增功能:你只需按规范新建一个类,继承ITaskHandler接口,并实现接口的方法。最后在XTGL_ServiceBundle表中新增一条记录即可。我们再来看看这么做有什么好处:

  • 新增的类只需按规范写即可,完全对MainEngine代码没有任何影响。你甚至可以把这个MainEngine代码写在一个新建的Dll中。
  • 新增功能的这个业务类跟原来的代码解耦,非常方便进行新功能的业务测试,而无需考虑原有框架的影响
  • 新增功能的业务类与架构完全分离,我们在重写代码中只要保证接口的稳定性,无论我们怎么把系统架构重写,我们可以马上就重用上原有的业务功能代码。

重构的目标之一,就是把框架和业务完全分离

有志于深入了解的同学,可以了解下反射、Ioc和插件话编程等。

学会单元测试,培养你的重构意识

可能上面说了这么多,还是有很多人并不理解重构。没关系,在这里我教你们一个快速入门的办法,就是单元测试。什么是单元测试,请自行google。单元测试有什么要求?就是要求你要把每个方法都弄成尽量可以测试的。尽量让你的方法变成是可测试的,就是培养你重构意识的利器。在你要求把方法变成可测试的过程,你就会发现你必须得不断的修改你的方法,让它的职责尽量单一,让它尽量的与上下文无关,让它尽可能通过方法参数的输入输出就能完成相关的功能,让依赖的类都尽量改为接口而不是实例。最终,你就会发觉,这就是重构!而且是在不知不觉中,你重构的功力就会大大提升,你编程的水平也会大大提升!

看到这里,有经验的程序员就会问,你这是在鼓励我使用TDD吗?不,不是的。TDD(Test-Driven Development)鼓励的是测试驱动开发,未开发之前先编写单元测试用例代码,测试代码确定需要编写什么产品代码。这是一种比较先进的开发方法,但是在编程的实践过程中,我认为它过于繁琐,很多中小企业很难实施,更别提我们个人开发者。我这里提倡你用单元测试培养你的重构意识,可以说是一种后驱动,用于提高你的重构能力和重构愿望,你完全可以把我的这个方法称为“TDR(Test-Driven Refactoring)——测试驱动重构”。当然,在开发之前如果你有意识的让方法可测试,那么你写出来的函数将会是比较高质量的代码。当你的函数都是一个个可重用性高的函数之时,你将会发现,写代码其实就像堆积木一样,可以把一个大型的需求分解成无数细小的功能,很快的把需求实现。

以下是一个超大方法中的一段代码,如果你懂得怎样让这段代码编程一个可测试的方法,那么,恭喜你,你入门了。

所谓重构

如果你有耐心看到这里,你应该知道,我并非一个标题党,而这篇文章也许称为“如何在编程中应用重构的思想”更为贴切,但是我不想用这么严肃的标题。

很多编程初学者,或者有多年编程经验的人都觉得阅读别人的代码非常困难,重构更是无从谈起,他们要么对这些代码望洋兴叹,要么就是推翻从来。但是,如果我们有重构的意识,以及在编程的过程中熟悉一些代码调整和优化的小技巧,你自然而然就会培养出重构的能力。

重构,其实很简单:

  • 把基础打牢固
  • 多看点优秀的代码
  • 避免复制粘贴,如果看见重复代码时应该有意识要消灭它
  • 减少对代码生成器的依赖
  • 在处理现有代码时尽量用重构代替重写,在重写之前一定要先重构
  • 尽量让所有的方法都是可测试的

如果你坚持这么去做了,一段时间之后感觉自然就出来了。

重构的目的,是让你的代码更为精简、稳定、能够重用,是最大程度的让功能和业务分离。在重构的过程中,你的阅读代码的能力、写出优秀代码的能力以及系统架构能力都会稳步提升。你成为一个优秀的程序员将指日可待。

从把3000行代码重构成15行代码谈起,首发于博客 - 伯乐在线

01 Dec 00:37

9本Java程序员必读的书

by 技术小黑屋

本文列出的9本书在Java程序员界都是被认为很棒的书。当一个程序员开始初学Java时,他的第一个问题应该是如何选择一本书来作为指导学习Java。这个问题也就表明,相对于其他的教程和博客,Java书籍还是很重要的参考,主要表现在以下两点

  • 通常书籍是由比较权威的程序员来撰写的。
  • 相比其他媒介,书籍对于内容的描述更加详细,解释更加明确。

本文列出的九本书是我个人非常喜欢的Java书籍,当我有时间的时候,我就会将它们捧在手里阅读。甚至有些书我反复读过很多遍,每次重新读的时候总会有新的收获。因此这些书也是大部分Java程序员喜欢的书籍。

Head First Java

Head First Java是所有编程或者Java初学者最适合的书籍,我很喜欢轻松和寓教于乐的Head First风格,这应该是最有意思的关于Java的书。无论是初级,中级还是高级都能从中有所收获。学习本书,你可以了解到类,对象,线程,集合等编程知识,还可以了解到泛型,枚举,可变参数和自动装箱等语言特性。本书中还涉及到了Java高级编程中的Swing,网络编程,IO操作等,可以让初学者对Java有比较完整地概念。如果你是一位Java初学者,不要犹豫,这本书最适合你了。
查看详细:亚马逊

Head First 设计模式

Head First设计模式又是一部Head First系列的书籍。作者为Kathy Sierra及其团队。当我在2006年开始读这本书的时候,我对设计模式并不是很了解。设计模式解决什么问题,怎么解决问题,如何使用设计模式,设计模式有什么好处,这些问题我几乎都无法回答出来。但是当我读完这本书的时候,一切都豁然开朗。在本书介绍继承和组合一章,使用简单有趣的例子,一步一步提出问题并解决问题,最终得出最优解。本书中会有很多要点总结,幽默对话,练习题还有有名的单词接龙等帮助你更好地了解设计模式。如果你想了解Java中的设计模式,请带走这本书。
查看详细:亚马逊

Effective Java

Effective Java这本书也是一本我最喜欢的。本书为领导开发Java集合框架和并发API包的 约书亚·布洛克 大神所著。本书适合于有着数年开发经验Java程序员,通过本书我们可以很多编程中的最佳实践,并且可以从JDK贡献者布洛克大神这里汲取经验。Effective Java从质量,内容和问题解答方式上来说都是一本评价很高的书,因此读这本书会是一种感觉很棒的体验。另外本书的章节相对比较轻量,与其他章节耦合度较低,因此在旅行或者闲暇时间阅读也是可以的。在内容方面,Effective Java包含了从静态工厂,序列化,equals和hashcode到泛型,枚举,可变参数以及反射的最佳解决方法。本书对Java各方面知识点的讲解会让你受益匪浅。
查看详细:亚马逊

Concurrency Practice in Java

又一部 约书亚·布洛克 大神的经典之作,当然本书的另一位主作者为 Doug Lea(影响两次Java历史上的大变革的大神)。这本书几乎就是Java并发和多线程编程方面的权威,同时也是核心Java开发人员必读的一本书。本书的强大主要表现在

  • 本书非常详细地描述了多线程和并发中的诸多(小)细节。
  • 本书并非聚焦于核心Java代码的实现,而是关注并发引起的问题,比如死锁,饥饿,线程安全,竞争条件,然后提供可行的方法来解决这些问题。因此这本书可以很好地帮助开发者了解并掌握并发包以及其中的CountDownLatch,CyclicBarrier,BlockingQueue,Semaphore这些类。这也是我一遍一遍阅读这本书的原因。
  • 书中的例子简明扼要清晰,很能描述问题。
  • 解释明确:本书很好地解释了什么是错的,为什么错,怎样改正。这也是本书畅销的原因之一。

查看详细:亚马逊

Java Generics and Collections

这本书是来自O’Reilly的一本成功之作,作者为Naftalin和Philip Wadler。正如书名可知,这本书的内容关注于泛型和集合这两个Java语言的核心方面。本书对于那些有编程经验的程序员加强对集合和泛型的理解和掌握有很大的帮助。本书详细介绍了每一个集合的API,Set, List, Map, Queue,以及他们的实现,对比不同场景下它们的性能优劣。每个章节最后的对比图表很不错。
查看详细:亚马逊

Java performance

本书为我个人的最爱,本书重点关注性能监视,性能剖析以及如何使用工具对性能分析。本书不同于其他的编程书籍,因为本书中涉及到很多Java虚拟机的细节,垃圾回收机制,Java堆内存监视以及对程序性能剖析。其中讲述JVM的章节写的很不错,很值得咀嚼。注意,这本书属于编程高级层次,阅读需要具有足够的Java编程经验。初级和中级Java开发者也可以阅读本书也会学到不同程度的干货。所以,你想进行性能调优,把这本书放到你的书架上吧。
查看详细:亚马逊

Java Puzzlers

再介绍一本 约书亚·布洛克 大神写的书。本书的另一位作者为Neal Gafter(已转到微软做Donet Compiler技术Lead)。本书讲述了Java语言中的极端情况和陷阱。相对于C++,Java更加安全低风险,JVM有着垃圾回收机制,让Java程序员不用关心内存分配和释放,大大提高了程序的开发效率。但是有些时候,即便是经验丰富的程序员也会被Java中的极端情况或陷阱给绊倒。本书列举并详细描述了Java中的这些陷阱。如果你喜欢刨根问底,钻牛角尖,这本书就是为你而写。通过本书,你可以了解很多java的核心知识并对自己的Java水平有所知晓。本人更加喜欢上面提到的 约书亚·布洛克 的 Effective Java 和 Concurrency Practice in Java。但是如果你感兴趣,还是可以去尝试的。为了让收获最大化,你可以先尝试解决书中的问题,然后对着书中的解释进行比较。
查看详细:亚马逊

Head First Object Oriented Analysis and Design

这是一本属于Head First系列的关于面向对象编程的书。本书建议和Head First设计模式结合阅读,效果更佳。本书关注于面向对象设计原则,比如多用组合少用继承,针对接口编程而非针对实现编程,不要重复你自己等。这本书可以帮助你写出好代码并且参考最佳实践进行优化改善。当然本书中的内容也适合使用其他面向对象的语言的程序员。想学好面向对象编码和设计规则,现在就开始读这本书哈。
查看详细:亚马逊

Thinking in Java

Thinking in Java book应该是Java中文界最有名的书籍了,中文又名 Java编程思想。作者为Bruce Eckel,他也是Thinking in C++的作者,他用自己很独特的观点讲述了Java。据我所知,这本书获得了很高的认可,本书是一本介绍齐全的Java学习参考书。如果你不喜欢Head First类似小人书那样的讲解,可以尝试这本书。本书的内容讲解详细,成熟(相对Head First有种教科书的感觉)。
查看详细:亚马逊

这就是我的关于Java编程相关的推荐的书,这些都可以说是看成经典中的经典。像Effective Java 和 Head First 系列我已经反复阅读了很多遍。相信看完本文,根据自己的水平和想要提升的知识侧重,你应该能选出最适合自己的书了。

关于翻译

本文原文为http://javarevisited.blogspot.com/2013/01/top-5-java-programming-books-best-good.html

译文再原文基础上采用意译较多,并在某些地方进行了些许修改。

01 Dec 00:34

10 个 Python IDE 和代码编辑器

by chosendai

Python 非常易学,强大的编程语言。Python 包括高效高级的数据结构,提供简单且高效的面向对象编程。

Python 的学习过程少不了 IDE 或者代码编辑器,或者集成的开发编辑器(IDE)。这些 Python 开发工具帮助开发者加快使用 Python 开发的速度,提高效率。高效的代码编辑器或者 IDE 应该会提供插件,工具等能帮助开发者高效开发的特性。

这篇文章收集了一些对开发者非常有帮助的,最好的 10 款 Python IDEs。如果你有其他更好的推荐,请在评论和大家分享一下:)

1. Vim

Vim 可以说是 Python 最好的 IDE。Vim 是高级文本编辑器,旨在提供实际的 Unix 编辑器‘Vi’功能,支持更多更完善的特性集。Vim 不需要花费太多的学习时间,一旦你需要一个无缝的编程体验,那么就会把 Vim 集成到你的工作流中

2. Eclipse with PyDev

Eclipse 是非常流行的 IDE,而且已经有了很久的历史。Eclipse with Pydev 允许开发者创建有用和交互式的 Web 应用。PyDev 是 Eclipse 开发 Python 的 IDE,支持 Python,Jython和 IronPython 的开发。

3. Sublime Text

Sublime Text 是开发者中最流行的编辑器之一,多功能,支持多种语言,而且在开发者社区非常受欢迎。Sublime 有自己的包管理器,开发者可以使用TA来安装组件,插件和额外的样式,所有这些都能提升你的编码体验。

4. Emacs

GNU Emacs 是可扩展,自定义的文本编辑器,甚至是更多的功能。Emacs 的核心是 Emacs Lisp 解析器,但是支持文本编辑。如果你已经使用过 Vim,可以尝试一下 Emacs。

5. Komodo Edit

Komodo Edit 是非常干净,专业的 Python IDE。

6. PyCharm

PyCharm 是 JetBrains 开发的 Python IDE。PyCharm用于一般IDE具备的功能,比如, 调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制……另外,PyCharm还提供了一些很好的功能用于Django开发,同时支持Google App Engine,更酷的是,PyCharm支持IronPython

7. Wing

Wingware 的 Python IDE 兼容 Python 2.x 和 3.x,可以结合 Django, matplotlib, Zope, Plone, App Engine, PyQt, PySide, wxPython, PyGTK, Tkinter, mod_wsgi, pygame, Maya, MotionBuilder, NUKE, Blender 和其他 Python 框架使用。Wing 支持测试驱动开发,集成了单元测试,nose 和 Django 框架的执行和调试功能。Wing IDE 启动和运行的速度都非常快,支持 Windows, Linux,  OS X 和 Python versi。

8. PyScripter

PyScripter 是款免费开源的 Python 集成开发环境(IDE)。

9. The Eric Python IDE

Eric 是全功能的 Python 和 Ruby 编辑器和 IDE,是使用 Python 编写的。Eric 基于跨平台的 GUI 工具包 Qt,集成了高度灵活的 Scintilla 编辑器控件。Eric 包括一个插件系统,允许简单的对 IDE 进行功能性扩展。

10. Interactive Editor for Python

IEP 是跨平台的 Python IDE,旨在提供简单高效的 Python 开发环境。包括两个重要的组件:编辑器和 Shell,并且提供插件工具集从各个方面来提高开发人员的效率。

10 个 Python IDE 和代码编辑器,首发于博客 - 伯乐在线

29 Nov 08:04

数据可视化:基本图表

"数据可视化"可以帮助用户理解数据,一直是热门方向。

图表是"数据可视化"的常用手段,其中又以基本图表----柱状图、折线图、饼图等等----最为常用。

用户非常熟悉这些图表,但如果被问道,它们的特点是什么,最适用怎样的场合(数据集)?恐怕答得上来的人就不多了。

本文是电子书《Data Visualization with JavaScript》第一章的笔记,总结了六种基本图表的特点和适用场合,非常好地回答了上面的问题。

零、序言

进入正题之前,先纠正一种误解。

有人觉得,基本图表太简单、太原始,不高端,不大气,因此追求更复杂的图表。但是,越简单的图表,越容易理解,而快速易懂地理解数据,不正是"数据可视化"的最重要目的和最高追求吗?

所以,请不要小看这些基本图表。因为用户最熟悉它们,所以只要是适用的场合,就应该考虑优先使用。

一、柱状图(Bar Chart)

柱状图是最常见的图表,也最容易解读。

它的适用场合是二维数据集(每个数据点包括两个值x和y),但只有一个维度需要比较。年销售额就是二维数据,"年份"和"销售额"就是它的两个维度,但只需要比较"销售额"这一个维度。

柱状图利用柱子的高度,反映数据的差异。肉眼对高度差异很敏感,辨识效果非常好。柱状图的局限在于只适用中小规模的数据集。

通常来说,柱状图的X轴是时间维,用户习惯性认为存在时间趋势。如果遇到X轴不是时间维的情况,建议用颜色区分每根柱子,改变用户对时间趋势的关注。

上图是英国足球联赛某个年度各队的赢球场数,X轴代表不同球队,Y轴代表赢球数。

二、折线图(Line Chart)数据

折线图适合二维的大数据集,尤其是那些趋势比单个数据点更重要的场合。

它还适合多个二维数据集的比较。

上图是两个二维数据集(大气中二氧化碳浓度,地表平均气温)的折线图。

三、饼图(Pie Chart)

饼图是一种应该避免使用的图表,因为肉眼对面积大小不敏感。

上图中,左侧饼图的五个色块的面积排序,不容易看出来。换成柱状图,就容易多了。

一般情况下,总是应该用柱状图替代饼图。但是有一个例外,就是反映某个部分占整体的比重,比如贫穷人口占总人口的百分比。

四、散点图(Scatter Chart)

散点图适用于三维数据集,但其中只有两维需要比较。

上图是各国的医疗支出与预期寿命,三个维度分别为国家、医疗支出、预期寿命,只有后两个维度需要比较。

为了识别第三维,可以为每个点加上文字标示,或者不同颜色。

五、气泡图(Bubble Chart)

气泡图是散点图的一种变体,通过每个点的面积大小,反映第三维。

上图是卡特里娜飓风的路径,三个维度分别为经度、纬度、强度。点的面积越大,就代表强度越大。因为用户不善于判断面积大小,所以气泡图只适用不要求精确辨识第三维的场合。

如果为气泡加上不同颜色(或文字标签),气泡图就可用来表达四维数据。比如下图就是通过颜色,表示每个点的风力等级。

六、雷达图(Radar Chart)

雷达图适用于多维数据(四维以上),且每个维度必须可以排序(国籍就不可以排序)。但是,它有一个局限,就是数据点最多6个,否则无法辨别,因此适用场合有限。

下面是迈阿密热火队首发的五名篮球选手的数据。除了姓名,每个数据点有五个维度,分别是得分、篮板、助攻、抢断、封盖。

画成雷达图,就是下面这样。

面积越大的数据点,就表示越重要。很显然,勒布朗·詹姆斯(红色区域)是热火队最重要的选手。

需要注意的时候,用户不熟悉雷达图,解读有困难。使用时尽量加上说明,减轻解读负担。

七、总结

图表 维度 注意点
柱状图 二维 只需比较其中一维
折线图 二维 适用于较大的数据集
饼图 二维 只适用反映部分与整体的关系
散点图 二维或三维 有两个维度需要比较
气泡图 三维或四维 其中只有两维能精确辨识
雷达图 四维以上 数据点不超过6个

(完)

文档信息

28 Nov 00:29

正则表达式和grep命令的用法

by ♂游泳的鱼
     摘要: 正则表达式(或称Regular Expression,简称RE)就是由普通字符(例如字符 a 到 z)以及特殊字符(称为元字符)组成的文字模式。

萌萌的it人   阅读全文

♂游泳的鱼 2014-11-27 17:43 发表评论