WEB请求处理(2):Nginx请求反向代理

陶邦仁

http://blog.jobbole.com/100526/

上一篇《WEB请求处理一:浏览器请求发起处理》,我们讲述了浏览器端请求发起过程,通过DNS域名解析服务器IP,并建立TCP连接,发送HTTP请求。本文讲述请求到达反向代理服务器的一个处理过程,比如:在Nginx中请求的处理流程,请求都是经过了哪些模块,做了哪些处理,又是如何找到应用服务器呢?

为直观明了,先上一张图,红色部分为本章所述模块:

Nginx有五大优点:模块化、事件驱动、异步、非阻塞、多进程单线程。其中,模块化设计类似于面向对象中的接口类,它增强了nginx源码的可读性、可扩充性和可维护性。

Nginx由内核和模块组成的,其中内核完成的工作比较简单,仅仅通过查找配置文件将客户端请求映射到一个location block,然后又将这个location block中所配置的每个指令将会启动不同的模块去完成相应的工作

所以,在讲述Nginx请求处理过程,首先要了解Nginx模块结构与功能,Nginx中有哪些模块,其功能又是如何。

1 Nginx模块

Nginx总共有5大一类模块:core、conf、event、http、mail,和48个二类模块每个模块有属于自己的配置项,由commands字段决定;每个模块在初始化和退出销毁时均有回调函数

多进程模式下的模块初始化主要有四个方面:脚本初始化、静态初始化、动态初始化、进程初始化

脚本初始化是指在安装nginx时,由configure脚本生成的相关文件,比如ngx_modules.c文件包含了nginx的所有模块;

静态初始化在编译时就完成,主要通过定义全局变量实现;

动态初始化在运行时完成,主要通过master进程main函数,ngx_init_cycle函数,及各模块文件内定义的init函数实现;

进程初始化是指各worker进程执行init_process函数。当nginx退出或重读配置文件或nginx平滑升级时,worker进程会调用各模块的exit_process函数来销毁资源。

开发人员可以根据nginx模块规则注册自己的模块,添加模块后要重新编译源码,并且修改nginx.conf配置文件才能使新模块生效。

1.1 四种角色模块

Nginx模块主要有4种角色:

(1) core(核心模块):构建nginx基础服务、管理其他模块;

(2) handlers(处理模块):用于处理HTTP请求,然后产生输出。

(3) filters(过滤模块):过滤handler产生的输出。

(4) load-balancers(负载均衡模块):当有多于一台的后端备选服务器时,选择一台转发HTTP请求。

Nginx的核心模块主要负责建立nginx服务模型、管理网络层和应用层协议、以及启动针对特定应用的一系列候选模块。其他模块负责分配给web服务器的实际工作:

(1) 当Nginx发送文件或者转发请求到其他服务器,由handlers(处理模块)或load-balancers(负载均衡模块)提供服务;

(2) 当需要Nginx把输出压缩或者在服务端加一些东西,由filters(过滤模块)提供服务。

1.2 模块结构设计

点击查看大图

点击查看大图

 

1.3 模块数据结构

  1. 核心:ngx_module_t(nginx所有模块的数据结构模板)
  2. 配置文件指令:ngx_command_t(主要负责模块与配置文件nginx.conf的交互)
  3. 指令配置:ngx_conf_t(解析某个具体的指令)
  4. 全局配置:ngx_cycle_t(nginx绝大部分初始化操作都围绕该结构体)

1.4 模块调用流程

  1. 当服务器启动,每个handlers(处理模块)都有机会映射到配置文件中定义的特定位置(location);如果有多个handlers(处理模块)映射到特定位置时,只有一个会“赢”(说明配置文件有冲突项,应该避免发生)。处理模块以三种形式返回:

    OK

    ERROR

    或者放弃处理这个请求而让默认处理模块来处理(主要是用来处理一些静态文件,事实上如果是位置正确而真实的静态文件,默认的处理模块会抢先处理)。

  2. 如果handlers(处理模块)把请求反向代理到后端的服务器,就变成另外一类的模块:load-balancers(负载均衡模块)。负载均衡模块的配置中有一组后端服务器,当一个HTTP请求过来时,它决定哪台服务器应当获得这个请求。Nginx的负载均衡模块采用两种方法:

    轮转法,它处理请求就像纸牌游戏一样从头到尾分发;

    IP哈希法,在众多请求的情况下,它确保来自同一个IP的请求会分发到相同的后端服务器。

  3. 如果handlers(处理模块)没有产生错误,filters(过滤模块)将被调用。多个filters(过滤模块)能映射到每个位置,所以(比如)每个请求都可以被压缩成块。它们的执行顺序在编译时决定。filters(过滤模块)是经典的“接力链表(CHAIN OF RESPONSIBILITY)”模型:一个filters(过滤模块)被调用,完成其工作,然后调用下一个filters(过滤模块),直到最后一个filters(过滤模块)。过滤模块链的特别之处在于:

    每个filters(过滤模块)不会等上一个filters(过滤模块)全部完成;

    它能把前一个过滤模块的输出作为其处理内容;有点像Unix中的流水线。

    过滤模块能以buffer(缓冲区)为单位进行操作,这些buffer一般都是一页(4K)大小,当然你也可以在nginx.conf文件中进行配置。这意味着,比如,模块可以压缩来自后端服务器的响应,然后像流一样的到达客户端,直到整个响应发送完成。

    总之,过滤模块链以流水线的方式高效率地向客户端发送响应信息。

  4. 所以总结下上面的内容,一个典型的HTTP处理周期是这样的:

    客户端发送HTTP请求 –>

    Nginx基于配置文件中的位置选择一个合适的处理模块 ->

    (如果有)负载均衡模块选择一台后端服务器 –>

    处理模块进行处理并把输出缓冲放到第一个过滤模块上 –>

    第一个过滤模块处理后输出给第二个过滤模块 –>

    然后第二个过滤模块又到第三个 –>

    依此类推 –> 最后把响应发给客户端。

    下图展示了nginx模块处理流程:


    点击查看大图

2 Nginx请求处理

Nginx在启动时会以daemon形式在后台运行,采用多进程+异步非阻塞IO事件模型来处理各种连接请求。多进程模型包括一个master进程,多个worker进程,一般worker进程个数是根据服务器CPU核数来决定的master进程负责管理Nginx本身和其他worker进程。如下图:

从上图中可以很明显地看到,4个worker进程的父进程都是master进程,表明worker进程都是从父进程fork出来的,并且父进程的ppid为1,表示其为daemon进程。

需要说明的是,在nginx多进程中,每个worker都是平等的,因此每个进程处理外部请求的机会权重都是一致的。

所有实际上的业务处理逻辑都在worker进程。worker进程中有一个ngx_worker_process_cycle()函数,执行无限循环,不断处理收到的来自客户端的请求,并进行处理,直到整个Nginx服务被停止。

worker 进程中,ngx_worker_process_cycle()函数就是这个无限循环的处理函数。在这个函数中,一个请求的简单处理流程如下:

  1. 操作系统提供的机制(例如 epoll, kqueue 等)产生相关的事件。
  2. 接收和处理这些事件,如是接收到数据,则产生更高层的 request 对象。
  3. 处理 request 的 header 和 body。
  4. 产生响应,并发送回客户端。
  5. 完成 request 的处理。
  6. 重新初始化定时器及其他事件。

2.1 多进程处理模型

下面来介绍一个请求进来,多进程模型的处理方式:

首先,master进程一开始就会根据我们的配置,来建立需要listen的网络socket fd,然后fork出多个worker进程。

其次,根据进程的特性,新建立的worker进程,也会和master进程一样,具有相同的设置。因此,其也会去监听相同ip端口的套接字socket fd

然后,这个时候有多个worker进程都在监听同样设置的socket fd,意味着当有一个请求进来的时候,所有的worker都会感知到。这样就会产生所谓的“惊群现象”。为了保证只会有一个进程成功注册到listenfd的读事件,nginx中实现了一个“accept_mutex”类似互斥锁,只有获取到这个锁的进程,才可以去注册读事件。其他进程全部accept 失败。

最后,监听成功的worker进程,读取请求,解析处理,响应数据返回给客户端,断开连接,结束。因此,一个request请求,只需要worker进程就可以完成。

进程模型的处理方式带来的一些好处就是:进程之间是独立的,也就是一个worker进程出现异常退出,其他worker进程是不会受到影响的;此外,独立进程也会避免一些不需要的锁操作,这样子会提高处理效率,并且开发调试也更容易。

如前文所述,多进程模型+异步非阻塞模型才是胜出的方案。单纯的多进程模型会导致连接并发数量的降低,而采用异步非阻塞IO模型很好的解决了这个问题;并且还因此避免的多线程的上下文切换导致的性能损失。

worker进程会竞争监听客户端的连接请求:这种方式可能会带来一个问题,就是可能所有的请求都被一个worker进程给竞争获取了,导致其他进程都比较空闲,而某一个进程会处于忙碌的状态,这种状态可能还会导致无法及时响应连接而丢弃discard掉本有能力处理的请求。这种不公平的现象,是需要避免的,尤其是在高可靠web服务器环境下。

针对这种现象,Nginx采用了一个是否打开accept_mutex选项的值,ngx_accept_disabled标识控制一个worker进程是否需要去竞争获取accept_mutex选项,进而获取accept事件

ngx_accept_disabled值,nginx单进程的所有连接总数的八分之一,减去剩下的空闲连接数量,得到的这个ngx_accept_disabled。

当ngx_accept_disabled大于0时,不会去尝试获取accept_mutex锁,并且将ngx_accept_disabled减 1,于是,每次执行到此处时,都会去减1,直到小于0。不去获取accept_mutex锁,就是等于让出获取连接的机会,很显然可以看出,当空闲连接越少时,ngx_accept_disable越大,于是让出的机会就越多,这样其它进程获取锁的机会也就越大。不去accept,自己的连接就控制下来了,其它进程的连接池就会得到利用,这样,nginx就控制了多进程间连接的平衡了。

2.2 一个简单的HTTP请求

从 Nginx 的内部来看,一个 HTTP Request 的处理过程涉及到以下几个阶段:

初始化 HTTP Request(读取来自客户端的数据,生成 HTTP Request 对象,该对象含有该请求所有的信息)。

处理请求头。

处理请求体。

如果有的话,调用与此请求(URL 或者 Location)关联的 handler。

依次调用各 phase handler 进行处理。

以上步骤,如下图所示:

输入图片说明

在这里,我们需要了解一下 phase handler 这个概念。phase 字面的意思,就是阶段。所以 phase handlers 也就好理解了,就是包含若干个处理阶段的一些 handler

在每一个阶段,包含有若干个 handler,再处理到某个阶段的时候,依次调用该阶段的 handler 对 HTTP Request 进行处理。

通常情况下,一个 phase handler 对这个 request 进行处理,并产生一些输出。通常 phase handler 是与定义在配置文件中的某个 location 相关联的

一个 phase handler 通常执行以下几项任务:

获取 location 配置。

产生适当的响应。

发送 response header。

发送 response body。

当 Nginx 读取到一个 HTTP Request 的 header 的时候,Nginx 首先查找与这个请求关联的虚拟主机的配置。如果找到了这个虚拟主机的配置,那么通常情况下,这个 HTTP Request 将会经过以下几个阶段的处理(phase handlers):

NGX_HTTP_POST_READ_PHASE: 读取请求内容阶段

NGX_HTTP_SERVER_REWRITE_PHASE: Server 请求地址重写阶段

NGX_HTTP_FIND_CONFIG_PHASE: 配置查找阶段

NGX_HTTP_REWRITE_PHASE: Location请求地址重写阶段

NGX_HTTP_POST_REWRITE_PHASE: 请求地址重写提交阶段

NGX_HTTP_PREACCESS_PHASE: 访问权限检查准备阶段

NGX_HTTP_ACCESS_PHASE: 访问权限检查阶段

NGX_HTTP_POST_ACCESS_PHASE: 访问权限检查提交阶段

NGX_HTTP_TRY_FILES_PHASE: 配置项 try_files 处理阶段

NGX_HTTP_CONTENT_PHASE: 内容产生阶段

NGX_HTTP_LOG_PHASE: 日志模块处理阶段

在内容产生阶段,为了给一个 request 产生正确的响应,Nginx 必须把这个 request 交给一个合适的 content handler 去处理。 如果这个 request 对应的 location 在配置文件中被明确指定了一个 content handler,那么Nginx 就可以通过对 location 的匹配,直接找到这个对应的 handler,并把这个 request 交给这个 content handler 去处理。这样的配置指令包括像,perl,flv,proxy_pass,mp4等。

如果一个 request 对应的 location 并没有直接有配置的 content handler,那么 Nginx 依次尝试:

如果一个 location 里面有配置 random_index on,那么随机选择一个文件,发送给客户端。

如果一个 location 里面有配置 index 指令,那么发送 index 指令指明的文件,给客户端。

如果一个 location 里面有配置 autoindex on,那么就发送请求地址对应的服务端路径下的文件列表给客户端。

如果这个 request 对应的 location 上有设置 gzip_static on,那么就查找是否有对应的.gz文件存在,有的话,就发送这个给客户端(客户端支持 gzip 的情况下)。

请求的 URI 如果对应一个静态文件,static module 就发送静态文件的内容到客户端。

内容产生阶段完成以后,生成的输出会被传递到 filter 模块去进行处理。filter 模块也是与 location 相关的。所有的 fiter 模块都被组织成一条链。输出会依次穿越所有的 filter,直到有一个 filter 模块的返回值表明已经处理完成。

这里列举几个常见的 filter 模块,例如:

server-side includes。

XSLT filtering。

图像缩放之类的。

gzip 压缩。

在所有的 filter 中,有几个 filter 模块需要关注一下。按照调用的顺序依次说明如下:

copy: 将一些需要复制的 buf(文件或者内存)重新复制一份然后交给剩余的 body filter 处理。

postpone: 这个 filter 是负责 subrequest 的,也就是子请求的。

write: 写输出到客户端,实际上是写到连接对应的 socket 上。

WEB请求处理(1):浏览器请求发起处理

来源:伯乐在线 – 陶邦仁

链接:http://blog.jobbole.com/100461/

最近,终于要把《WEB请求处理系列》提上日程了,一直答应小伙伴们给分享一套完整的WEB请求处理流程:从浏览器、Nginx、Servlet容器,最终到应用程序WEB请求的一个处理流程,前段时间由于其他工作事情的安排,一直未进行整理。不过还好该系列终于启动了,给大家分享的同时,也顺便整理下自己的思路,以便温故而知新吧。希望大家都能在此过程中得到新的收获吧。

本系列主要分五部分:

1.《WEB请求处理一:浏览器请求发起处理》:分析用户在浏览器中输入URL地址,浏览器如何找到服务器地址的过程,并发起请求;

2.《WEB请求处理二:Nginx请求反向代理》:分析请求在达反向代理服务器内部处理过程;

3.《WEB请求处理三:Servlet容器请求处理》:分析请求在Servlet容器内部处理过程,并找到目标应用程序;

4.《WEB请求处理四:WEB MVC框架请求处理》:分析请求在应用程序内部,开源MVC框架的处理过程;

5.《WEB请求处理五:浏览器请求响应处理》:分析请求在服务器端处理完成后,浏览器渲染响应页面过程;

为直观明了,先上一张图,红色部分为本章所述模块:

1 B/S网络架构概述

B/S网络架构从前端到后端都得到了简化,都基于统一的应用层协议HTTP来交互数据,HTTP协议采用无状态的短链接的通信方式,通常情况下,一次请求就完成了一次数据交互,通常也对应一个业务逻辑,然后这次通信连接就断开了。采用这种方式是为了能够同时服务更多的用户,因为当前互联网应用每天都会处理上亿的用户请求,不可能每个用户访问一次后就一直保持住这个连接。

当一个用户在浏览器里输入www.google.com这个URL时,将会发生如下操作:

  1. 首先,浏览器会请求DNS把这个域名解析成对应的IP地址;
  2. 然后,根据这个IP地址在互联网上找到对应的服务器,建立Socket连接,向这个服务器发起一个HTTP Get请求,由这个服务器决定返回默认的数据资源给访问的用户;
  3. 在服务器端实际上还有复杂的业务逻辑:服务器可能有多台,到底指定哪台服务器处理请求,这需要一个负载均衡设备来平均分配所有用户的请求;
  4. 还有请求的数据是存储在分布式缓存里还是一个静态文件中,或是在数据库里;
  5. 当数据返回浏览器时,浏览器解析数据发现还有一些静态资源(如:css,js或者图片)时又会发起另外的HTTP请求,而这些请求可能会在CDN上,那么CDN服务器又会处理这个用户的请求;

以上流程,具体如图所示:

不管网络架构如何变化,但是始终有一些固定不变的原则需要遵守:

  1. 互联网上所有资源都要用一个URL来表示。URL就是统一资源定位符;
  2. 必须基于HTTP协议与服务端交互;
  3. 数据展示必须在浏览器中进行;

2 HTTP协议解析

B/S网络架构的核心是HTTP协议,最重要的就是要熟悉HTTP协议中的HTTP Header,HTTP Header控制着互联网上成千上万的用户的数据传输。最关键的是,它控制着用户浏览器的渲染行为和服务器的执行逻辑。

常见的HTTP请求头:

常见的HTTP响应头:

常见的HTTP状态码:

2.1 浏览器缓存机制

当我们使用Ctrl+F5组合键刷新一个页面时,首先是在浏览器端,会直接向目标URL发送请求,而不会使用浏览器缓存的数据;其次即使请求发送到服务端,也有可能访问到的是缓存的数据。所以在HTTP的请求头中会增加一些请求头,它告诉服务端我们要获取最新的数据而非缓存。最重要的是在请求头中增加了两个请求项Pragma:no-cache和Cache-Control:no-cache。

  1. Cache-Control/Pragma这个HTTP Head字段用于指定所有缓存机制在整个请求/响应链中必须服从的指令,如果知道该页面是否为缓存,不仅可以控制浏览器,还可以控制和HTTP协议相关的缓存或代理服务器。Http Head字段的可选值:

    Cache-Control请求字段被各个浏览器支持的较好,而且它的优先级也比较高,它和其他一些请求字段(如Expires)同时出现时,Cache-Control会覆盖其他字段。

    Pragma字段的作用和Cache-Control有点类似,它也是在HTTP头中包含一个特殊的指令,使相关的服务器来遵守,最常用的就是Pragma:no-cache,它和Cache-Control:no-cache的作用是一样的。

  2. Expires 缓存过期时间Expires通常的使用格式是Expires:Sat,25 Feb 2012 12:22:17 GMT,后面跟着一个日期和时间,超过这个值后,缓存的内容将失效,也就是浏览器在发出请求之前检查这个页面的这个字段,看该页面是否已经过期了,过期了将重新向服务器发起请求。
  3. Last-Modified/Etag 最后修改时间Last-Modified字段一般用于表示一个服务器上的字段的最后修改时间,资源可以是静态(静态内容自动加上Last-Modified)或者动态的内容(如Servlet提供了一个getLastModified方法用于检查某个动态内容是否已经更新),通过这个最后修改时间可以判断当前请求的资源是否是最新的。一般服务器端在响应头中返回一个Last-Modified字段,告诉浏览器这个页面的最后修改时间,如:Sat,25 Feb 2012 12:55:04 GMT,浏览器再次请求时在请求头中增加一个If-Modified-Since:Sat,25 Feb 2012 12:55:04 GMT字段,询问当前缓存的页面是否是最新的,如果是最新的就会返回304状态码,告诉浏览器是最新的,服务器也不会传输新的数据。

    与Last-Modified字段有类似功能的还有一个Etag字段,这个字段的作用是让服务端给每个页面分配一个唯一编号,然后通过这个编号来区分当前这个页面是否是最新的。这种方式比使用Last-Modified更加灵活,但是在后端的Web服务器有多台时比较难处理,因为每个Web服务器都要记住网站的所有资源编号,否则浏览器返回这个编号就没有意义了。

3 WEB工作流程

对于正常的上网过程,系统其实是这样做的:

浏览器本身是一个客户端,当你输入URL的时候,首先浏览器会去请求DNS服务器,通过DNS获取相应的域名对应的IP,然后通过IP地址找到IP对应的服务器后,要求建立TCP连接,等浏览器发送完HTTP Request(请求)包后,服务器接收到请求包之后才开始处理请求包,服务器调用自身服务,返回HTTP Response(响应)包;客户端收到来自服务器的响应后开始渲染这个Response包里的主体(body),等收到全部的内容随后断开与该服务器之间的TCP连接。

一个Web服务器也被称为HTTP服务器,它通过HTTP协议与客户端通信。这个客户端通常指的是Web浏览器(其实手机端客户端内部也是浏览器实现的)。

Web服务器的工作原理可以简单地归纳为:

  1. 浏览器通过DNS域名解析到服务器IP;
  2. 客户机通过TCP/IP协议建立到服务器的TCP连接;
  3. 客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档;
  4. 服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端;
  5. 客户机与服务器断开。由客户端解释HTML文档,在客户端屏幕上渲染图形结果;

一个简单的HTTP事务就是这样实现的,看起来很复杂,原理其实是挺简单的。需要注意的是客户机与服务器之间的通信是非持久连接的,也就是当服务器发送了应答后就与客户机断开连接,等待下一次请求。

4 DNS域名解析

4.1 DNS域名解析过程

当用户在浏览器中输入域名,如:www.google.com,并按下回车后,DNS解析过程大体如下:

  1. 浏览器缓存检查(本机)浏览器会首先搜索浏览器自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存),看自身的缓存中是否有www.google.com对应的条目,而且没有过期,如果有且没有过期则解析到此结束。浏览器缓存域名也是有限制的,不仅浏览器缓存大小有限制,而且缓存的时间也有限制,通常情况下为几分钟到几小时不等,域名被缓存的时间限制可以通过TTL属性来设置。这个缓存时间太长和太短都不好,如果缓存时间太长,一旦域名被解析到的IP有变化,会导致被客户端缓存的域名无法解析到变化后的IP地址,以致该域名不能正常解析,这段时间内有可能会有一部分用户无法访问网站。如果时间设置太短,会导致用户每次访问网站都要重新解析一次域名。

    注:我们怎么查看Chrome自身的缓存?可以使用 chrome://net-internals/#dns 来进行查看

  2. 操作系统缓存检查(本机)+hosts解析(本机)如果浏览器自身的缓存里面没有找到对应的条目,其实操作系统也会有一个域名解析的过程,那么Chrome会首先搜索操作系统自身的DNS缓存中是否有这个域名对应的DNS解析结果,如果找到且没有过期则停止搜索解析到此结束。其次在Linux中可以通过/etc/hosts文件来设置,你可以将任何域名解析到任何能够访问的IP地址。如果你在这里指定了一个域名对应的IP地址,那么浏览器会首先使用这个IP地址。当解析到这个配置文件中的某个域名时,操作系统会在缓存中缓存这个解析结果,缓存的时间同样是受这个域名的失效时间和缓存的空间大小控制的。
  3. 本地区域名服务器解析(LDNS)如果在hosts文件中也没有找到对应的条目,浏览器就会发起一个DNS的系统调用,就会向本地配置的首选DNS服务器(LDNS一般是电信运营商提供的,也可以使用像Google提供的DNS服务器)发起域名解析请求(通过的是UDP协议向DNS的53端口发起请求,这个请求是递归的请求,也就是运营商的DNS服务器必须得提供给我们该域名的IP地址)。在我们的网络配置中都会有“DNS服务器地址”这一项,这个地址就用于解决前面所说的如果两个过程无法解析时要怎么办,操作系统会把这个域名发送给这里设置的LDNS,也就是本地区的域名服务器。这个DNS通常都提供给你本地互联网接入的一个DNS解析服务,例如你是在学校接入互联网,那么你的DNS服务器肯定在你的学校,如果你是在一个小区接入互联网的,那这个DNS就是提供给你接入互联网的应用提供商,即电信或者联通,也就是通常所说的SPA,那么这个DNS通常也会在你所在城市的某个角落,通常不会很远。这个专门的域名解析服务器性能都会很好,它们一般都会缓存域名解析结果,当然缓存时间是受域名的失效时间控制的,一般缓存空间不是影响域名失效的主要因素。大约80%的域名解析都到这里就已经完成了,所以LDNS主要承担了域名的解析工作。

    运营商的DNS服务器首先查找自身的缓存,找到对应的条目,且没有过期,则解析成功。

  4. 根域名服务器解析(Root Server)如果LDNS没有找到对应的条目,则有运营商的DNS代我们的浏览器发起迭代DNS解析请求。它首先是会找根域的DNS的IP地址(这个DNS服务器都内置13台根域的DNS的IP地址),找到根域的DNS地址,就会向其发起请求(请问www.google.com这个域名的IP地址是多少啊?)。
  5. 根域名服务器返回给本地域名服务器一个所查询域的主域名服务器(gTLD Server)地址,gTLD是国际顶级域名服务器,如.com、.cn、.org等,全球只有13台左右。根域发现这是一个顶级域com域的一个域名,于是就告诉运营商的DNS我不知道这个域名的IP地址,但是我知道com域的IP地址,你去找它去。
  6. 本地域名服务器(Local DNS Server)再向上一步返回的gTLD服务器发送请求。于是运营商的DNS就得到了com域的IP地址,又向com域的IP地址发起了请求(请问www.google.com这个域名的IP地址是多少?),com域这台服务器告诉运营商的DNS我不知道www.google.com这个域名的IP地址,但是我知道google.com这个域的DNS地址,你去找它去。
  7. 接受请求的gTLD服务器查找并返回此域名对应的Name Server域名服务器的地址,这个Name Server通常就是你注册的域名服务器,例如你在某个域名服务提供商申请的域名,那么这个域名解析任务就由这个域名提供商的服务器来完成。于是运营商的DNS又向google.com这个域名的DNS地址(这个一般就是由域名注册商提供的,像万网,新网等)发起请求(请问www.google.com这个域名的IP地址是多少?),这个时候google.com域的DNS服务器一查,果真在我这里,于是就把找到的结果发送给运营商的DNS服务器,这个时候运营商的DNS服务器就拿到了www.google.com这个域名对应的IP地址。
  8. Name Server域名服务器会查询存储的域名和IP的映射关系表,正常情况下都根据域名得到目标IP记录,连同一个TTL值返回给DNS Server域名服务器。
  9. 返回该域名对应的IP和TTL值,Local DNS Server会缓存这个域名和IP的对应关系,缓存的时间由TTL值控制。
  10. 把解析的结果返回给用户,用户根据TTL值缓存在本地系统缓存中,域名解析过程结束。

通过上面的步骤,我们最后获取的是IP地址,也就是浏览器最后发起请求的时候是基于IP来和服务器做信息交互的。在实际的DNS解析过程中,可能还不止这10个步骤,如Name Server也可能有多级,或者有一个GTM来负载均衡控制,这都有可能会影响域名解析的过程。根据以上解析流程,DNS解析整个过程,分为:递归查询过程和迭代查询过程。如图所示:

所谓 递归查询过程 就是 “查询的递交者” 更替, 而 迭代查询过程 则是 “查询的递交者”不变。

举个例子来说,你想知道某个一起上法律课的女孩的电话,并且你偷偷拍了她的照片,回到寝室告诉一个很仗义的哥们儿,这个哥们儿二话没说,拍着胸脯告诉你,甭急,我替你查(此处完成了一次递归查询,即,问询者的角色更替)。然后他拿着照片问了学院大四学长,学长告诉他,这姑娘是xx系的;然后这哥们儿马不停蹄又问了xx系的办公室主任助理同学,助理同学说是xx系yy班的,然后很仗义的哥们儿去xx系yy班的班长那里取到了该女孩儿电话。(此处完成若干次迭代查询,即,问询者角色不变,但反复更替问询对象)最后,他把号码交到了你手里。完成整个查询过程。

4.2 跟踪域名解析过程

在Linux系统中还可以使用dig命名来查询DNS的解析过程,如下所示:dig +cmd +trace www.google.com

上面清楚地显示了整个域名是如何发起和解析的,从根域名(.)到gTLD Server(.com.)再到Name Server (google.com.)的整个过程都显示出来了。还可以看出DNS的服务器有多个备份,可以从任何一台查询到解析结果。

4.3 清除缓存的域名

我们知道DNS域名解析后会缓存解析结果,其中主要在两个地方缓存结果,一个是Local DNS Server,另外一个是用户的本地机器。这两个缓存都是TTL值和本机缓存大小控制的,但是最大缓存时间是TTL值,基本上Local DNS Server的缓存时间就是TTL控制的,很难人工介入,但是我们的本机缓存可以通过如下方式清除。

在Linux下可以通过/etc/init.d/nscd restart来清除缓存。如下:

JVM缓存DNS解析结果:在Java应用中JVM也会缓存DNS的解析结果,这个缓存是在InetAddress类中完成的,而且这个缓存时间还比较特殊,它有两种缓存策略:一种是正确解析结果缓存,另一种是失败的解析结果缓存。这两个缓存时间由两个配置项控制,配置项是在%JAVA_ HOME%libsecurityjava.security文件中配置的。两个配置项分别是networkaddress.cache.ttl 和networkaddress.cache.negative.ttl,它们的默认值分别是-1(永不失效)和10(缓存10秒)。

要修改这两个值同样有几种方式,分别是:直接修改java.security文件中的默认值、在Java的启动参数中增加-Dsun.net.inetaddr.ttl=xxx来修改默认值、通过InetAddress类动态修改。

在这里还要特别强调一下,如果我们需要用InetAddress类解析域名时,一定要是单例模式,不然会有严重的性能问题,如果每次都创建InetAddress实例,每次都要进行一次完整的域名解析,非常耗时,这点要特别注意。

4.4 几种域名解析方式

  1. A记录,A代表的是Address,用来指定域名对应的IP地址如将item.taobao.com指定到115.238.23.241,将switch.taobao.com指定到121.14.24.241。A记录可以将多个域名解析到一个IP地址,但是不能将一个域名解析到多个IP地址。
  2. MX记录,表示的是Mail Exchange,就是可以将某个域名下的邮件服务器指向自己的Mail Server如taobao.com域名的A记录IP地址是115.238.25.245,如果MX记录设置为115.238.25.246,是xxx@taobao.com的邮件路由,DNS会将邮件发送到115.238.25.246所在的服务器,而正常通过Web请求的话仍然解析到A记录的IP地址。
  3. CNAME记录,全称是Canonical Name(别名解析),所谓的别名解析就是可以为一个域名设置一个或者多个别名如将taobao.com解析到xulingbo.net,将srcfan.com也解析到xulingbo.net,其中xulingbo.net分别是taobao.com和srcfan.com的别名。前面的跟踪域名解析中的“www.taobao.com. 1542 IN CNAME www.gslb.taobao.com”就是CNAME解析。
  4. NS记录,为某个域名指定DNS解析服务器,也就是这个域名有指定的IP地址的DNS服务器去解析前面的“google.com. 172800 IN NS ns4.google.com.”就是NS解析。
  5. TXT记录,为某个主机名或域名设置说明如可以为google.com设置TXT记录为“谷歌|中国”这样的说明。

4.5 网络抓包分析

Linux虚拟机测试,使用命令 wget www.linux178.com 来请求,发现直接使用chrome浏览器请求时,干扰请求比较多,所以就使用wget命令来请求,不过使用wget命令只能把index.html请求回来,并不会对index.html中包含的静态资源(js、css等文件)进行请求。

抓包截图如下:

1号包,这个是那台虚拟机在广播,要获取192.168.100.254(也就是网关)的MAC地址,因为局域网的通信靠的是MAC地址,它为什么需要跟网关进行通信是因为我们的DNS服务器IP是外围IP,要出去必须要依靠网关帮我们出去才行。

2号包,这个是网关收到了虚拟机的广播之后,回应给虚拟机的回应,告诉虚拟机自己的MAC地址,于是客户端找到了路由出口。

3号包,这个包是wget命令向系统配置的DNS服务器提出域名解析请求(准确的说应该是wget发起了一个DNS解析的系统调用),请求的域名www.linux178.com,期望得到的是IP6的地址(AAAA代表的是IPv6地址)。

4号包,这个DNS服务器给系统的响应,很显然目前使用IPv6的还是极少数,所以得不到AAAA记录的。

5&6号包,这个还是请求解析IPv6地址,但是www.linux178.com.leo.com这个主机名是不存在的,所以得到结果就是no such name。

7号包,这个才是请求的域名对应的IPv4地址(A记录)。

8号包,DNS服务器不管是从缓存里面,还是进行迭代查询最终得到了域名的IP地址,响应给了系统,系统再给了wget命令,wget于是得到了www.linux178.com的IP地址,这里也可以看出客户端和本地的DNS服务器是递归的查询(也就是服务器必须给客户端一个结果)这就可以开始下一步了,进行TCP的三次握手。

5 发起TCP的3次握手

拿到域名对应的IP地址之后,User-Agent(一般是指浏览器)会以一个随机端口(1024 < 端口 < 65535)向服务器的WEB程序(常用的有httpd,nginx等)80端口发起TCP的连接请求。这个连接请求(原始的http请求经过TCP/IP4层模型的层层封包)到达服务器端后(这中间通过各种路由设备,局域网内除外),进入到网卡,然后是进入到内核的TCP/IP协议栈(用于识别该连接请求,解封包,一层一层的剥开),还有可能要经过Netfilter防火墙(属于内核的模块)的过滤,最终到达WEB程序,最终建立了TCP/IP的连接。

如下图所示:

  1. Client首先发送一个连接试探,ACK=0 表示确认号无效,SYN = 1 表示这是一个连接请求或连接接受报文,同时表示这个数据报不能携带数据,seq = x 表示Client自己的初始序号(seq = 0 就代表这是第0号包),这时候Client进入syn_sent状态,表示客户端等待服务器的回复。
  2. Server监听到连接请求报文后,如同意建立连接,则向Client发送确认。TCP报文首部中的SYN 和 ACK都置1 ,ack = x + 1表示期望收到对方下一个报文段的第一个数据字节序号是x+1,同时表明x为止的所有数据都已正确收到(ack=1其实是ack=0+1,也就是期望客户端的第1个包),seq = y 表示Server自己的初始序号(seq=0就代表这是服务器这边发出的第0号包)。这时服务器进入syn_rcvd,表示服务器已经收到Client的连接请求,等待client的确认。
  3. Client收到确认后还需再次发送确认,同时携带要发送给Server的数据。ACK 置1 表示确认号ack= y + 1 有效(代表期望收到服务器的第1个包),Client自己的序号seq= x + 1(表示这就是我的第1个包,相对于第0个包来说的),一旦收到Client的确认之后,这个TCP连接就进入Established状态,就可以发起http请求了。

看抓包截图:

TCP 为什么需要3次握手?

举个例子:假设一个老外在故宫里面迷路了,看到了小明,于是就有下面的对话:

老外: Excuse me,Can you Speak English?

小明: yes 。

老外: OK,I want …

在问路之前,老外先问小明是否会说英语,小明回答是的,这时老外才开始问路。

2个计算机通信是靠协议(目前流行的TCP/IP协议)来实现,如果2个计算机使用的协议不一样,那是不能进行通信的,所以这个3次握手就相当于试探一下对方是否遵循TCP/IP协议,协商完成后就可以进行通信了,当然这样理解不是那么准确。

为什么HTTP协议要基于TCP来实现?

目前在Internet中所有的传输都是通过TCP/IP进行的,HTTP协议作为TCP/IP模型中应用层的协议也不例外,TCP是一个端到端的可靠的面向连接的协议,所以HTTP基于传输层TCP协议不用担心数据的传输的各种问题。

6 建立TCP连接后发起http请求

经过TCP3次握手之后,浏览器发起了http的请求(看第⑫包),使用的http的方法 GET 方法,请求的URL是 / ,协议是HTTP/1.0:

下面是第12号包的详细内容:

以上的报文是HTTP请求报文。那么HTTP请求报文和响应报文会是什么格式呢?

起始行:如 GET / HTTP/1.0 (请求的方法 请求的URL 请求所使用的协议)

头部信息:User-Agent Host等成对出现的值

主体

不管是请求报文还是响应报文都会遵循以上的格式。那么起始行中的请求方法有哪些种呢?

GET: 完整请求一个资源 (常用)

HEAD: 仅请求响应首部

POST: 提交表单 (常用)

PUT: 上传

DELETE: 删除

OPTIONS: 返回请求的资源所支持的方法的方法

TRACE: 追求一个资源请求中间所经过的代理

那什么是URL、URI、URN?

URI Uniform Resource Identifier 统一资源标识符,如:scheme://[username:password@]HOST:port/path/to/source

URL Uniform Resource Locator 统一资源定位符,如:http://www.magedu.com/downloads/nginx-1.5.tar.gz

URN Uniform Resource Name 统一资源名称

URL和URN都属于URI,为了方便就把URL和URI暂时都通指一个东西。

请求的协议有哪些种?有以下几种:

http/0.9: stateless

http/1.0: MIME, keep-alive (保持连接), 缓存

http/1.1: 更多的请求方法,更精细的缓存控制,持久连接(persistent connection) 比较常用

下面是Chrome发起的http请求报文头部信息:

Accept 就是告诉服务器端,接受那些MIME类型

Accept-Encoding 这个看起来是接受那些压缩方式的文件

Accept-Lanague 告诉服务器能够发送哪些语言

Connection 告诉服务器支持keep-alive特性

Cookie 每次请求时都会携带上Cookie以方便服务器端识别是否是同一个客户端

Host 用来标识请求服务器上的那个虚拟主机,比如Nginx里面可以定义很多个虚拟主机,那这里就是用来标识要访问那个虚拟主机。

User-Agent 用户代理,一般情况是浏览器,也有其他类型,如:wget curl 搜索引擎的蜘蛛等

条件请求头部:If-Modified-Since是浏览器向服务器端询问某个资源文件如果自从什么时间修改过,那么重新发给我,这样就保证服务器端资源文件更新时,浏览器再次去请求,而不是使用缓存中的文件。

安全请求头部:Authorization: 客户端提供给服务器的认证信息;

什么是MIME?

MIME(Multipurpose Internet Mail Extesions 多用途互联网邮件扩展)是一个互联网标准,它扩展了电子邮件标准,使其能够支持非ASCII字符、二进制格式附件等多种格式的邮件消息,这个标准被定义在RFC 2045、RFC 2046、RFC 2047、RFC 2048、RFC 2049等RFC中。 由RFC 822转变而来的RFC 2822,规定电子邮件标准并不允许在邮件消息中使用7位ASCII字符集以外的字符。正因如此,一些非英语字符消息和二进制文件,图像,声音等非文字消息都不能在电子邮件中传输。

MIME规定了用于表示各种各样的数据类型的符号化方法。此外,在万维网中使用的HTTP协议中也使用了MIME的框架,标准被扩展为互联网媒体类型。

MIME 遵循以下格式:major/minor 主类型/次类型 例如:

image/jpg

image/gif

text/html

video/quicktime

appliation/x-httpd-php

专栏作者简介 点击 → 加入专栏作者 )


陶邦仁:专注于后端技术研究,前端技术略有涉猎,热衷于构建高性能、高可用网站,擅长于平台服务化、分布式服务、分布式存储等方面的解决方案。目前就职于千丁互联,任技术经理一职,负责社区产品技术研发。曾就职于京东,负责库存组缓存方案技术实现;曾就职于百度糯米,负责PC首页、APP个性化排单服务化解决方案。

Java中文乱码解决之道(2): 字符编码详解

来源: chenssy

链接:http://www.cnblogs.com/chenssy/p/4202688.html

在上篇博文(java中文乱码解决之道(一)—–认识字符集)中,LZ简单介绍了主流的字符编码,对各种编码都是点到为止,以下LZ将详细阐述字符集、字符编码等基础知识和ASCII、GB的详情。

一、基础知识

在了解各种字符集之前我们需要了解一些最基础的知识,如:编码、字符、字符集、字符编码基础知识。

编码

计算机中存储的信息都是用二进制表示的,我们在屏幕上所看到文字、图片等都是通过二进制转换的结果。编码是信息从一种形式或格式转换为另一种形式的过程,通俗点讲就是就是将我们看到的文字、图片等信息按照某种规则存储在计算机中,例如‘c’在计算机中怎么表达,‘陈’在计算机中怎么表达,这个过程就称之为编码。解码是编码的逆过程,它是将存储在计算机的二进制转换为我们可以看到的文字、图片等信息,它体现的是视觉上的刺激。

n位二进制数可以组合成2的n次方个不同的信息,给每个信息规定一个具体码组,这种过程也叫编码。

在编码和解码中,他们就如加密、解密一般,他们一定会遵循某个规则,即y  = f(x),那么x = f(y);否则在解密过程就会导致‘a’解析成‘b’或者乱码。

字符

字符是可使用多种不同字符方案或代码页来表示的抽象实体,它是一个单位的字形、类字形单位或符号的基本信息,也是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。

字符是指计算机中使用的字母、数字、字和符号,包括:1、2、3、A、B、C、~!·#¥%……—*()——+等等。在 ASCII 编码中,一个英文字母字符存储需要1个字节。在 GB 2312 编码或 GBK 编码中,一个汉字字符存储需要2个字节。在UTF-8编码中,一个英文字母字符存储需要1个字节,一个汉字字符储存需要3到4个字节。在UTF-16编码 中,一个英文字母字符或一个汉字字符存储都需要2个字节(Unicode扩展区的一些汉字存储需要4个字节)。在UTF-32编码中,世界上任何字符的存 储都需要4个字节。

2014112600001_thumb4

字符集

字符是各种文字和符号的总称,而字符集则是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同。而计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。

常见字符集名称:ASCII字符集、GB2312字符集、BIG5字符集、 GB18030字符集、Unicode字符集等。

字符编码

计算机中的信息包括数据信息和控制信息,然而不管是那种信息,他们都是以二进制编码的方式存入计算机中,但是他们是怎么展示在屏幕上的呢?同时在展 现过程中如何才能保证他们不出错?这个时候字符编码就起到了重要作用!字符编码是一套规则,一套建立在符合集合与数字系统之间的对应关系之上的规则,它是 信息处理的基本技术。

使用字符编码这套规则能够对自然语言的字符的一个集合(如字母表或音节表),与其他东西的一个集合(如号码或电脉冲)进行配对。

2014112600002_thumb1

二、ASCII

2.1、标准ASCII码

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语和其他西欧英语,它是现今最通用的单字节编码系统。

ASCII使用7位或者8位来表示128或者256种可能的字符。标准的ASCII码则是使用7位二进制数来表示所有的大小写字母、数字、标点符合和一些控制字符,其中:

0~31、127(共33个)是控制字符或者通信专用字符,如控制符:LF(换行)、CR(回车)、DEL(删除)等;通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等。ASCII值为8、9、10、13分别表示退格、制表、换号、回车字符。

32~126(共95个)字符,32为空格、48~57为阿拉伯数字、65~90为大写字母、97~122为小写字母,其余为一些标点符号和运算符号!

前面提过标准的ASCII码是使用七位来表示字符的,而最高位(b7)则是用作奇偶校验的。所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位b7添1;偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位b7添1。 (参考百度百科)

下面是ASCII字符对照表,更多详情请关注:》》 ASCII码表 《《

2014112400001_thumb3

2014112400002_thumb

2.2、扩展ASCII码

标准的ASCII是用七位来表示的,那么它的缺陷就非常明显了:只能显示26个基本拉丁字母、阿拉伯数目字和英式标点符号,基本上只能应用于现代美 国英语,对于其他国家,128个字符肯定不够。于是,这些欧洲国家决定利用字节中闲置的最高位编入新的符号,这样一来,可以表达的字符数最多就为256 个,但是随着产生的问题也就来了:不同的国家有不同的字母,可能同一个编码在不同的国家所表示的字符不同。但是不管怎么样,在这些编码中0~127所表示的字符肯定是一样的,不一样的也只是128~255这一段。

8位的ASCII在欧洲国家表现的不尽人意,那么在其他国家就更加不用说了,我们拥有五千年历史文化的中华名族所包含的汉字多大10多万,不知道是 多少个256。所以一个字节8位表示的256个字符肯定是不够的,那么两个字节呢?可能够了吧!我们常见的汉字就是用两个字节表示的,如GB2312。

2014112600003_thumb

三、GB**

对于欧美国家来说,ASCII能够很好的满足用户的需求,但是当我们中华名族使用计算机时,ASCII明显就不满足需求了,有5000年历史文化的 我们,拥有的汉字达到将近10万,所以为了显示中文,我们必须设计一套编码规则用于将汉字转换为计算机可以接受的数字系统的数。显示中文的常用字符编码 有:GB2312、GBK、GB18030。

GB2312

GB2312,中国国家标准简体中文字符集,全称《信息交换用汉字编码字符集·基本集》,由中国国家标准总局发布,1981年5月1日实施。

GB2312编码的规则:一个小于127的字符的意义与原来相同,但两个大于127的 字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,还把数学符号、罗马希腊的 字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127 号以下的那些就叫”半角”字符了。

在GB2312中,GB2312共收录6763个汉字,其中一级汉字3755个,二级汉字3008个,还收录了拉丁字母、希腊字母、日文等682个 全角字符。由于GB2312的出现,它基本上解决了我们日常的需要,它所收录的汉子已经覆盖了中国大陆99.75%的使用平率。但是我国文化博大精深,对 于人名、古汉语等方面出现的罕用字,GB2312还是不能处理,于是后面的GBK和GB18030汉字字符集出现了。

GB2312字符集库非常庞大,详情:GB2312简体中文编码表

GBK

GBK,全称《汉字内码扩展规范》,由中华人民共和国全国信息技术标准化技术委员会1995年12月1日制订,也是汉字编码的标准之一。

GBK是GB2312的扩展,他向下与GB2312兼容,,向上支持 ISO 10646.1 国际标准,是前者向后者过渡过程中的一个承上启下的标准。同时它是使用双字节编码方案,其编码范围从8140至FEFE(剔除xx7F),首字节在 81-FE 之间,尾字节在 40-FE 之间,共23940个码位,共收录了21003个汉字。

GB18030

GB18030,国家标准GB18030《信息技术 中文编码字符集》,是我国计算机系统必须遵循的基础性标准之一。它有两个版本:GB18030-2000、GB18030-2005。其中 GB18030-2000仅规定了常用非汉字符号和27533个汉字(包括部首、部件等)的编码,而GB18030-2005是全文强制性标准,市场上销 售的产品必须符合,它是GB18030-2000的基础上增加了42711个汉字和多种我国少数民族文字的编码。

GB18030标准采用单字节、双字节和四字节三种方式对字符编码。(码位总体结构见下图)

单字节部分采用GB/T 11383的编码结构与规则,使用0×00至0×7F码位(对应于ASCII码的相应码位)。双字节部分,首字节码位从0×81至0×FE,尾字节码位分 别是0×40至0×7E和0×80至0×FE。四字节部分采用GB/T 11383未采用的0×30到0×39作为对双字节编码扩充的后缀,这样扩充的四字节编码,其范围为0×81308130到0×FE39FE39。其中第 一、三个字节编码码位均为0×81至0×FE,第二、四个字节编码码位均为0×30至0×39。

2014112600004_thumb

四、参考文献&进一步阅读

编码:http://baike.baidu.com/subview/237708/11062012.htm(百度百科)

字符:http://baike.baidu.com/view/263416.htm(百度百科)

字符集:http://baike.baidu.com/view/51987.htm(百度百科)

字符编码:http://baike.baidu.com/view/1204863.htm(百度百科)

字符集和字符编码:http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html吴秦

ASCII:http://baike.baidu.com/view/15482.htm

GB2312:http://baike.baidu.com/view/443268.htm

GBK:http://baike.baidu.com/view/931619.htm

GB18030:http://baike.baidu.com/view/889058.htm


—–原文出自:http://cmsblogs.com/?p=1412请尊重作者辛勤劳动成果,转载说明出处.

—–个人站点:http://cmsblogs.com

Java中文乱码解决之道(1): 认识字符集

来源: chenssy

链接:http://www.cnblogs.com/chenssy/p/4200277.html

沉寂了许久(大概有三个多月了吧),LZ“按捺不住”开始写博了!

java编码中的中文问题是一个老生常谈的问题了,每次遇到中文乱码LZ要么是按照以前的经验修改,要么则是baidu.com来 解决问题。阅读许多关于中文乱码的解决办法的博文后,发现对于该问题我们都(更加包括我自己)没有一个清晰明了的认识,于是LZ想通过这系列博文(估计只 有几篇)来彻底分析、解决java中文乱码问题,如有错误之处望各位同仁指出!当然,此系列博文并非LZ完全原创,都是在前辈基础上总结,归纳,如果雷同 纯属借鉴……

问题起源

对于计算机而言,它仅认识两个0和1,不管是在内存中还是外部存储设备上,我们所看到的文字、图片、视频等等“数据”在计算机中都是已二进制形式存在的。不同字符对应二进制数的规则,就是字符的编码。字符编码的集合称为字符集。

在早期的计算机系统中,使用的字符是非常少的,他们只包括26个英文字母、数字符号和一些常用符号,对于这些字符进行编码,用1个字节就足够了,但 是随着计算机的不断发展,为了适应全世界其他各国民族的语言,这些少得可怜的字符编码肯定是不够的。于是人们提出了UNICODE编码,它采用双字节编 码,兼容英文字符和其他国家民族的双字节字符编码。

每个国家为了统一编码都会规定该国家/地区计算机信息交换用的字符集编码,为了解决本地字符信息的计算机处理,于是出现了各种本地化版本,引进 LANG, Codepage 等概念。现在大部分具有国际化特征的软件核心字符处理都是以 Unicode 为基础的,在软件运行时根据当时的 Locale/Lang/Codepage 设置确定相应的本地字符编码设置,并依此处理本地字符。在处理过程中需要实现 Unicode 和本地字符集的相互转换。

同然,java内部采用的就是Unicode编码,所以在java运行的过程中就必然存在从Unicode编码与相应的计算机操作系统或者浏览器支持的编码格式相互转化的过程,这个转换的过程有一系列的步骤,如果某个步骤出现错误,则输出的文字就会是乱码。

所以产生java乱码的问题就在于JVM与对应的操作系统/浏览器进行编码格式转换时出现了错误。

其实要解决java乱码问题的方法还是比较简单的,但是要究其原因,理解背后的原理还是需要了解

其实解决 JAVA 程序中的汉字编码问题的方法往往很简单,但理解其背后的原因,定位问题,还需要了解现有的汉字编码和编码转换。

常见字符编码

计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。常见的字符编码主要包括:ASCII编码、GB**编 码、Unicode。下面LZ就简单地介绍下!(为什么是简单介绍?因为LZ在网上查找资料想去了解字符编码时,发现这个问题比我想象的复杂太多了,所以 LZ需要另起一篇详细介绍,所以各位看客就简单看看吧!!)

1.ASCII编码

ASCII,American Standard Code for Information Interchange,是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统。

ASCII码使用指定的7位或者8为二进制数字组合表示128或者256种可能的字符。标准的ASCII编码使用的是7(2^7 = 128)位二进制数来表示所有的大小写字母、数字和标点符号已经一些特殊的控制字符,最前面的一位统一规定为0。其中0~31及127(共33个)是控制 字符或通信专用字符,32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字,65~90为26个大写英文字 母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。

2014112400001

2014112400002

2.GBK***编码

ASCII最大的缺点就是显示字符有限,他虽然解决了部分西欧语言的显示问题,但是对更多的其他语言他实在是无能为了。随着计算机技术的发展,使用 范围越来越广泛了,ASCII的缺陷越来越明显了,其他国家和地区需要使用计算机,必须要设计一套符合本国/本地区的编码规则。例如为了显示中文,我们就 必须要设计一套编码规则用于将汉字转换为计算机可以接受的数字系统的数。

GB2312,用于汉字处理、 汉字通信等系统之间的信息交换,通行于中国大陆。它的编码规则是:小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉 字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。虽然GB2312收录了这么多汉子,他所覆盖 的使用率可以达到99%,但是对于那些不常见的汉字,例如人名、地名、古汉语,它就不能处理了,于是就有下面的GBK、GB 18030的出现。(点击GB2312简体中文编码表查看)。

GB18030,全 称:国家标准GB 18030-2005《信息技术 中文编码字符集》,是我国计算机系统必须遵循的基础性标准之一,GB18030有两个版本:GB18030-2000和GB18030-2005。 GB18030-2000是GBK的取代版本,它的主要特点是在GBK基础上增加了CJK统一汉字扩充A的汉字。

GB 18030主要有以下特点:

与UTF-8相同,采用多字节编码,每个字可以由1个、2个或4个字节组成。

编码空间庞大,最多可定义161万个字符。

支持中国国内少数民族的文字,不需要动用造字区。

汉字收录范围包含繁体汉字以及日韩汉字

2014112400003

GBK,汉字编码标准之一,全称《汉字内码扩展规范》,它 向下与 GB 2312 编码兼容,向上支持 ISO 10646.1 国际标准,是前者向后者过渡过程中的一个承上启下的标准。它的编码范围如下图:

2014112400004

3.Unicode编码

正如前面前面所提到的一样,世界存在这么多国家,也存在着多种编码风格,像中文的GB232、GBK、GB18030,这样乱搞一套,虽然在本地运行没有问题,但是一旦出现在网络上,由于互不兼容,访问则会出现乱码。为了解决这个问题,伟大的Unicode编码腾空出世。

Unicode编码的作用就是能够使计算机实现夸平台、跨语言的文本转换和处理。它几乎包含了世界上所有的符号,并且每个符号都是独一无二的。在它的编码世界里,每一个数字代表一个符号,每一个符号代表了一个数字,不存在二义性。

Unicode编码又称统一码、万国码、单一码,它是业界的一种标准,是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定 了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。同时Unicode是字符集,它存在很多几种实现方式如:UTF-8、 UTF-16.

UTF-8

互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32,不过在互联网上基本不用。重复一遍:UTF-8是Unicode的实现方式之一。

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有两条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

推荐阅读

此篇博文只是开篇之作,启下之用, 对字符集的介绍也只是简简单单,没有太多的描述,因为LZ在查字符集的资料过程中发现字符集真的是太复杂了,LZ有点儿驾驭不了,需要仔细研究,然后写一篇较为详细的博文!各位敬请期待!!

参考文献:

字符集和字符编码:http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html

百度百科 ASCII:http://baike.baidu.com/view/15482.htm

百度百科:GB2312:http://baike.baidu.com/view/443268.htm?fromtitle=GB2312&fromid=483170&type=syn

百度百科:GB18030:http://baike.baidu.com/view/889058.htm

百度百科:GBK:http://baike.baidu.com/view/931619.htm?fromtitle=GBK&fromid=481954&type=search

百度百科:Unicode:http://baike.baidu.com/view/40801.htm

百度百科:UTF-8:http://baike.baidu.com/view/25412.htm

如有错误之处,忘指出!!不胜感激!!!


—–原文出自:http://cmsblogs.com/?p=1395,请尊重作者辛勤劳动成果,转载说明出处.

—–个人站点:http://cmsblogs.com

5 insanely great books about mathematics you should read

Kelly J. Rose

Source: https://wp.kjro.se/2013/12/27/5-insanely-great-books-about-mathematics-you-should-read/

I’ve been asked over and over for good books about mathematics for a layperson, someone who hasn’t taken advanced courses in university and is more simply interested in learning about what math is, and some of the more interesting historical figures and results from mathematics. Ironically, when you are a mathematics major at Waterloo, you get the opportunity in 4th year to take a course on the history of mathematics and you get introduced to a few really good books that start to explain the mindset and philosophy behind mathematics and not simply just the theorems and proofs.

Here are the 5 books about I most recommend to those who want to understand the mathematical mind and philosophy.

Boyer’s a History of Mathematics

A History of Mathematics,
Carl B. Boyer

This is the textbook from the History of Mathematics course I took almost a decade ago now, and it is still one of the best and most thorough discussions of how mathematics developed over the past millenia. It starts in with Egyptian and pre-classical mathematics, explaining how simple tasks were complicated by a lack of mathematical tools and then how over time different tools were developed that led to quantum leaps in our understanding of the field. It’s quite a tome, with over 700 pages of details, but it is fully accessible to the non-technical reader.

This is well worth having in any library and it can be read in chunks as each chapter covers a different aspect of mathematical history.

Journeys Through Genius

Journeys Through Genius

Journey through Genius
William Dunham

I picked up this book at a secondhand store many years back simply because it caught my attention and was a good price. I thought it would be an enjoyable read, but I never expected to be as amazed and excited by the contents as I started to dig through it. This book takes some of the most important and paradigm-shifting theorems of mathematics and explains them in a clear and accessible fashion. Historical artifacts around the development of the theorems are displayed in a fun and pleasing fashion, keeping the importance of the discovery in context with the time. As well, most importantly, beyond explaining the theorems, the characters behind the work as shown and their lives are taken into context with the immensity of their work. This is a beautiful read and worth picking up if you want to learn more about the biggest theorems in mathematics.

The Mathematical ExperienceThe Mathematical Experience,
Philip J. David, Reuben Hersh

My professor for the history of mathematics course lent me his copy of this book and it was probably one of the most eye-opening reads I’ve ever had. I spent an entire weekend reading it cover-to-cover and then re-reading it again, devouring and absorbing all of the ideas and concepts within it.

Without a doubt, this is the best book I’ve got on my library from the perspective of discussion what it means to be a mathematician and the experience shared by mathematicians worldwide. This book covers the entire gamut, from the philosophical to the social-emotional experience of a mathematician. It is well-written, concise and strikes a real chord with me. In this book I really felt that I was reading someone who got what it meant to love mathematics and get excited by it without delving really deep into difficult to process material. If there is one book on this entire list that I recommend going and purchasing right now, it is this one.

Go, buy it now!

Proofs from the Book

Proofs from the Book

Proofs from the Book,
Martin Aigner, Günter M. Ziegler

Paul Erdös, one of the most prolific mathematicians of the 20th century would commonly refer to a proof that was singularly beautiful as being “from the book.” As in, “from the book of God himself.” This book is a collection of some of the proofs that many mathematicians think to be essential and important, while still be uniquely beautiful in their elegance. If you want a book which is still accessible, but allows for exploration of the theorems themselves in am ore rigourous fashion, this is the book for you. It’s clean and covers some of the best proofs in a very wide variety of fields.

Proofs and Refutations

Proofs and Refutations

Proofs and Refutations,
Imre Lakatos

This books is probably the most advanced of the books on this list. It is however brilliantly written in the form of a discussion between a professor and their students. Lakatos weaves in and out over the process of mathematics, covering how mathematics is really done and evolves as theorems adapt based on a variety of very easy to understand techniques.

If you, or anyone you know, is actually considering to go into mathematics as a profession, I would recommend reading this book. This especially includes teachers as it explains how working through the technique and philosophy can help with overall understanding and creative use of the new tools learned as you move forward. This is a truly wonderful book and can be a very quick read.

 

Four steps to master machine learning with python

如何从菜鸟程序员成长为高手

2016-02-24 蛋疼的axb ACSE
关注上方“ACSE”来及时了解中国理工科留学生联盟的最新动态以及新鲜科技好文章
小编有话说
从今天开始ACSE公众号每天会更新一篇科技相关的精选好文章来帮助大家了解科技前沿以及提高自己的技术水平。
今天这篇文章是由大神级别程序员总结的学习方法,值得收藏起来每周看一遍,只要照着一一做到,总有一天能够成为高手。
1.摘要
最近有一些毕业不久的同事问我:“你工作的时候有没有什么窍门?怎么才能快速成为高手?”
想起当初刚入职,新人培训的时候,也跟其他同事讨论过这个问题:如何才能成为业界大牛?当时自己只是觉得兴趣是最好的老师,思路方法什么的没有多想。
加 入微博平台架构部的时间也不短了,趁着快过春节总结了一下自己入职微博以来的工作情况,从互联网开发的半个门外汉,到如今能设计一些架构、排查一些问题、 分享一些经验,收获颇多,感想颇多,也逐渐意识到思路和方法的重要性,在此跟大家分享一下。主要分为学、做、想三方面。
2.学会学习
学习无疑是程序员最为重要的素质之一,尤其是互联网这种日新月异的行业,把学习当做工作的一大半也不为过。
2.1.自主学习
最近发现身边的人并不是不想学习,只是每天都在纠结自己到底学什么好:简单的没挑战,复杂的看不懂;旧技术怕过时,新技术没方向…… 
讲 讲自己毕业后的经历,毕业之后去了个不大不小的公司,工作主要是做一些XX管理系统之类的东西,没什么挑战,也用不上什么技术,基本上前端用个extjs 后面套个sql server就解决了。工作稳定了几年,业余时间除了wow没别的事情做,觉得这么闲下去不是办法,于是之后一年的时间里,用上班摸鱼和下班休息的时间学 了这些东西:
1. 闲着无聊想做个小游戏,发现游戏相关的书大多是英文的,看不懂,一咬牙翻译了《Real-time rending 3rd》的前几章,刚开始前言都看不懂,只能一个词一个词的翻字典,一句话要琢磨几个钟头到底作者说的到底是什么意思。翻译了几百页英文书之后,发现自己 看英文书没什么障碍了,于是开始每天用休息和摸鱼的时间看书。
2. 看完游戏引擎的书之后,把irrlicht引擎的代码看了一遍,然后自己山寨了一个3d渲染的场景管理器,还有个朴素的渲染引擎。
给自己的游戏引擎写了个基于脚本语言的解释器,为此看了不少编译原理和虚拟机的书,了解了程序究竟是什么东西,这是我觉得收益很大的一件事情。
3. 看编译原理的书的时候发现操作系统的知识有些欠缺,又去看了linux内核相关的书。之后买了个开发板天天修改内核玩,毕业以后又一次了解了内核的cpu 调度、内存管理和文件系统,了解了应用是怎么跑在操作系统上,操作系统又是怎么运行在硬件上的,这也是收益很大的一件事情。
4. 看完操作系统又顺着看网络相关的书,之后把lighthttpd的代码看了一遍,用c写了个linux下的http服务器,把几种网络编程模型挨个实现了一遍。
5. 实现http服务器的过程中觉得自己编码能力还是有欠缺,把代码大全翻了一遍,顺着又去看了设计模式的书,并且用自己的理解把每个模式用文字重新描述了一遍。
6. 中间还看了很多语言和框架相关的书,就不一一列举了。可以参考这里。
我把学习的方向分为三类:
1. 为了工作,满足当前工作所必备的知识
2. 为了提升,与当前工作相关的知识(深度)
3. 拓展视野,与当前工作无关的知识(广度)
学习(1)之后只是个熟练工,2和3才是提升自己的途径,伴随着知识储备的提升,接触新事物时更容易找到相似的知识加以类比,加快理解,也更容易掌握本质。如果每天都在纠结“到底学什么”,那么只能说明还是学的太少了。(真正没什么可学的大牛们应该不会读到这里吧……)
所 以,如果觉着没什么东西可以学的时候,那么可以考虑一下学一下更有深度的知识(比如虚拟机或编译器),或者完全不同的知识(新的语言或当前比较火的方 向),甚至完全不相干的知识(单纯练习英文阅读,学习ppt排版之类)吧。随着知识储备增加,自己的不足和未来的学习的方向也会更加明确起来。
2.2.向历史学习
以微博为例,在微博发展的过程中经历了不少波折,并逐渐衍生出了目前的系统架构。很多新人最喜欢问的问题便是“现在线上是怎么做的?”
这 个问题不错,但是还不够好。在程序员的世界里罕有能解决所有问题的“银弹”,当前的做法用不了多久也会被替换掉,如果想了解一件事情,那么就多关注一下 “它是怎么变成今天这样的”吧。学会用发展的眼光看问题,了解一些经历过的经验教训,收获会比单纯学会一件什么事情多的多。
那么,如何向历史学习?
1. 公司内部的资料库、wiki等大都会有旧时的资料,刚入职时大多不会太忙,这些资料库简直是挖不完的宝藏
2. 部门内部分享,比如我当初入职时经常去听“微博XXXX架构演化历程”之类的内部分享
多问一下自己”它为什么不那么设计“
3. 老员工忆苦思甜吹牛逼的时候多奉承几句_(:з」 ∠)_
2.3.向他人学习
这里有两个极端,
1. 有的人喜欢自己闷头捣鼓,什么也不问,这必然是不利于自己提高的;
2. 也有人碰到问题就问,这也有问题,浪费他人时间不说,更关键的是说明这人向他人学习的思路错了,要学习他人的并不是具体某个知识(要学知识看书就能解决了),而是学习别人的思维方式。
但是思维方式这种东西很难通过交流的方式学到,后来我发现有个很简单的学习方式:口头禅。举几个例子,大家体会一下:
“这个其实是两个问题”
“有没有更好的方案”
“能不能举个例子”
“能不能给个一句话总结”
除了口头禅,很多牛人都会有非常鲜明的思维方式和处事原则,如果有幸与业界的大牛共事,那么恭喜你,只要多交流、多观察、多思考,那么提升速度会提升好几个数量级。
3.多做有意义的事情
有的人每天时间浪费在跟问题本身无关的事情上,比如我要设计架构的时候还要考虑架构图怎么画,写完代码还要反复部署测试好几轮才pass,查bug的时候把时间浪费在扫日志上。人的精力总是有限的,把时间浪费在这些事情上面,让自己提高的时间就变得少了。
3.1.练习,更多的练习
这里有个误区:“做有意义的事情”不等于“只做自己没做过的事情”。
对于程序员来说,写代码是基本功中的基本功,编码的规范、设计的权衡、甚至顺手的IDE快捷键都要靠平日的试错和积累,很难通过几本书或者几天培训领悟到。
曾 经目睹一些人写代码一年之后开始做一些小项目的设计,然后就迫不及待的把重心全都转移到设计甚至架构上,这种没有基础能力支撑做出的设计和架构最多只能算 是高级意淫,大多没等落地就荒废了,意义不大。究其原因,大多是设计出来的东西“不好做”或者“不好用”,就像是只看过一遍课本就去参加高数考试,现实 吗?(学霸们我错了……)
举 个例子,几年前在看设计模式的过程中,用qt做了个看漫画的应用,把能用的模式都试了一遍,当然有很多用的不合适的地方,正是这些不合适的地方让我对面向 对象编程和设计模式的思考深入了很多,如何权衡灵活性和复杂性也有了新的认识。之后在设计很多系统的时候少走了很多弯路,既保证了时间点又保证了质量。如 果当时指望着“用的时候再说”,大概已经被项目坑的不能自理了。
3.2.善用工具
工具能解决的事情就用工具去解决,好的工具能节约大把的时间用在更有意义的事情上。
工具的范畴很广,比如linux的各种命令、比如团队内部的各种系统、比如顺手的应用、甚至包括上下班骑的自行车。只要能节约时间、提高效率,那就值得一试。
在这里我列举几个大幅度提升了我的效率的东西:
1. 双屏显示器
2. 顺手的键盘
3. google(不是baidu!不是bing!)
4. mac
5. mac上的应用:idea、alfread、omnifocus、甚至synergy和istats menus之类跟开发本身关系不大的应用。
我更倾向于把“使用工具”作为一种生活态度:是否希望让自己的生活专注于有意义的事情。如果你认同这个观点,那么想一想投入和回报比例,还是很可观的。
(当然,为了不花钱而自己破解应用的大神也是极叼的……)
3.3.提高时间的利用率
时间是所有期待提升自己的人最宝贵的资源,效率再高,没时间做也没意义。
网上有个流传挺广的图:打扰程序员的成本。事实上我每天的工作时间非常碎片化,来到公司之后可能不断的接电话、被问问题、被拉去开会、回复邮件等等;也经常会有时间不够用或者没事做的困惑,这里分享一下心得:
1. GTD可以整合很多碎片时间。除了把事做完之外,把上下文相关的事情集中在一起完成也很有帮助。比如把几件想去其他办公室做的事情整合成一趟完成。
2. 减少无意义的时间浪费,比如家住在公司边上可以每天节省几个小时的时间用来学习或者做别的事情。(但如果节省下来的时间用来刷微博,那就没有必要了。)
另外一个很有趣的现象:一个软件的注册费就10几刀,贵些的几百刀,把日常用到的所有工具的费用全加起来都顶不上一个肾6贵,但是很多人还是坚持着没有破解不用的观念,为了几百块钱浪费了大把时间。
3. 加班可以创造很多时间,并且能有效减少被打扰的几率,但是也会给身体和精神带来很大负担。因此加班做的事情必须能对个人进步产生足够多的收益。如果加班只是用来处理无意义的工作的话,那应该是日常工作出了什么问题。
4. 事情可以分成紧急重要、紧急不重要、重要不紧急、不重要不紧急四类,在todo列表里随时要有重要不紧急的事情。
4.学会思考
4.1.深究
当有什么问题解决不了的时候,很多人会有畏难或者拖延的情绪,典型口头禅就是“就这么凑合着用吧”或者“先这样吧,以后有时间再研究”,说这些话的人大多并不是真的那么忙,甚至有人一边刷着微博一边跟我说没时间研究……(你tm在逗我?)
要克服畏难情绪其实很简单,找一个具体的似懂非懂的问题,想尽办法把问题研究清楚,体会几次解决问题时的愉悦感,建立自信。
大部分问题其实没有什么高深的科学原理,甚至只要翻几页书就解决了,但是遇到问题不深究,久而久之会形成自我暗示:这些问题是我懂的,那些是我不懂的,自己反而把自己进步的路给堵上了。
说到如何深究,也有几条心得:
1. 遇事多想为什么,并且要反复问为什么。很多貌似理解了的问题过一阵再重新想想,往往会发现之前还有没考虑到的地方
2. 问题要有明确答案,哲学之类的就别纠结了
3. 查找资料时选权威的书籍或者网站,避免被误导
4. 找人讨论,或者直接拉小伙伴入伙,既可以互相交流,又可以互相监督
5. 分享你的成果
6. 不要所有事情全都深究,会给自己太多压力
4.2.多说,多写,多交流
平常工作中有一个感受,有交流和写作习惯的人思路会更清晰一些,能接触到的观点也会多一些。这方面其实属于我的弱项,大概总结几个观点。
1. 隔一段时间最好能书面形式总结一下最近的工作,比如说写个心得感悟,或者持续更新自己的简历
2. 写作的时候有两个难点:对要说明的事情做总结和抽象,形成观点统一、调理清晰的主线;从对方的视角考虑,把事情说明白,避免自言自语。
3. 找人讨论之前自己先要有个基本完整的思路,否则大部分的时间都要耗在解释原理之类的上网查反而更快的事情上。
4. 讨论之后要有一句话就能说明白的结论和描述清晰的时间点。
5. 有些人喜欢纠结于“这个不是我的问题,为什么要我处理”之类的事情。在我看来这是很好的机会。既能增长见识,又能展示水平,还能留个认真负责的好名声,何乐而不为呢。
5.最后
最后分享一下关于我理解的程序员的自我修养,在我看来,可以总结为:负责任,重名声。
负责任,说的更具体些:写的代码自己有没有测过、做的框架自己有没有用过、设计的架构自己有没有认真权衡过。
重名声,说的直接些:没有测过的代码、没有用过的框架、没有权衡过的方案有没有脸交付给别人。
与各位共勉。
作者:蛋疼的axb

原网址:http://blog.2baxb.me/archives/1077
中国理工科留学生联盟(ACSE)
致力于推动留美各校理工科留学生间的相互交流以及资源共享和对接

程序员,你的安全感呢?

作者:杨栋

链接:http://www.cnblogs.com/yangdong/p/5190818.html

同病相连

最近跟一位同事谈心,他非常热爱技术,是一个不折不扣的极客,不仅如此他有很强的执行力,并能持之以恒,从他的身上我看到了未来几年将会成长起来的一个技术牛人。但是,他有比较严重的焦虑症,比如:几天没有看书就会非常纠结,通常情况是他需要花时间来陪孩子和家人,他会感觉自己浪费了宝贵时间,精力无法集中在当前所做的事情上。我接触的同事中,越是对技术有追求,好像越是有这样的感觉,算起来这种不安的感觉应该属于特定程序员群组中一种典型现象,其实做为导师的我也是常年被这种感觉所困扰。 他是这么描述自己的:

我已经工作4年了,但是我在数据库底层,HTTP协议,并发,分布式系统都没有深入地理解,我觉得特别害怕。

不用讨论工作4年的程序员应该有什么样的表现,掌握多上技术,深度如何,视野是否开阔,我们来看看别人眼中的他吧:

  • 对技术极度有热情
  • 学东西非常快,几周时间就搞定了Ansible, Chef, Puppet
  • 阅读非常广,除了技术之外,涉足项目管理,金融,心里,社会等

看看他的书单(我截取了其中1/4的一年要读完的书单,实际上他读完的书比计划要多),也会对他有更深刻的认识:

  • 像外行一样思考,像专家一样实践:科研成功之道(修订版)
  • 古今数学思想(新版·典藏本)(套装全3册)(第1~3册)
  • 思考,快与慢
  • 数据化管理:洞悉零售及电子商务运营
  • 探索复杂性
  • 日志管理与分析权威指南

心理学上的安全感

安全感是心理学研究中的重要概念之一,最早见于弗洛伊德精神分析的理论研究。虽然国内外对此做过诸多研究,然而对于“安全感”至今尚未达成共识,以下列举了一些关于安全感的定义:

  • 弗洛伊德认为当个体所受到的刺激超过了本身控制和释放能量的界限时,个体就会产生不安全感。换句话说,在弗洛伊德的精神分析理论中,冲突、焦虑、防御机制等是由个人幼年、成年阶段某种欲望的控制与满足方面缺乏安全感造成的。
  • 人本主义心理学家马斯洛最早对安全感作出了明确界定,指出“安全感”是一种从恐惧、焦虑中脱离出来的信心、安全和自由的感觉,特别是满足一个人现在和未来各种需要的感觉。
  • 安莉娟等对精神分析理论、人本主义心理学理论中的安全感理论进行总结,提出安全感的定义为:安全感是对可能出现的对身体或心理的危险或风险的预感,以及个体在应对处置时的有力无力感,主要表现为确定感和可控制感。

从以上的定义我们大致能够看出安全感大致有两大要素:负面的外部刺激,正面的内在能量。简单来说当内部能量能够抵消负面的外部刺激,人是有安全感,因为我的“能量结界”没有消散,反之则没有安全感,没有人会在“能量结界”被打穿之后还怡然自得。那么这个“能量结界”的大小就是所谓的舒适区域了,如果用绿色表示很安全的话,那么舒适区域应该是中心是深绿色周边是浅绿色的圆。理所当然,随着它的范围的增大,个人的舒适区域也就不断的增大。

为什么会焦虑

人为什么会焦虑,当某一件能够刺激内心产生负面影响的事情发生时,人们就有可能会产生焦虑的情绪。这是一种缺乏安全感的情绪和典型表现。在学生时代,我们会因为惧怕考试失败而焦虑;在职场上,我们会因为无法完成某项工作而焦虑;在孩子的教育过程中,我们会因为担心无法找到合适的学校而焦虑;在人到中年时,我们为因为身体的退步而焦虑。因为我们无法确定能否处理这种情形。因此对于一些场景,我们到会显得十分从容,“开卷考试,有书在手心中无忧”,“跟自己的下属沟通绩效问题,轻车熟路”,“住在一个教育资源较为发达的地区,孩子的教育问题就显得不那么明显”,“每天花半小时来做慢跑,非常显著的改善身体状况”。

奇普•康利(Chip Conley)在他的《如何控制自己的情绪》很好的阐释了为什么会焦虑:

焦虑 = 不确定性 * 无力感

在讲述如何控制焦虑者众情绪时,他分享了一段TED上的经历:

TED曾要求我谈谈幸福的无形资产,以及商界和政界领导人应如何开始学习衡量和重视人生最有意义的东西。这似乎是一个无情的讽刺,我的任务是登上讲坛,以一个专家的身份,探讨一个那时连我自己都觉得难以捉摸的主题。尽管我在《巅峰》一书中宣称要以自我实践的方法来经商,但我已经失去了那种神奇的感染力。“我陷入了觉得自己无用的思维定式,而能够克服这一挑战的唯一办法就是写下有关我人生经历的演讲词并练习无数遍。我以为追求完美是缓解焦虑的良药。你也许有过类似的遭遇,认为设定目标会让自己舒服。但事实是,追求完美只会放大焦虑。在接下来的几个月里,我一直痛苦地写着一份以幸福为主题的演讲词,这一次和我正常准备演讲的经历完全不同。我的风格是倾向于即兴发挥和贴近听众,而非机械式和反复演练。于是越临近TED大会,我就变得越焦虑。我的演讲被安排在会议的最后一天(就在电影导演詹姆斯·卡梅隆之前)。所以当我抵达会场时,我还有5天痛苦难熬的日子,要在聆听其他演讲者发言中度过,这使我越发焦虑。”

因此,越是不确定或者超出自己能力的事情,焦虑的情绪就越容易产生,而越是这样,从这种焦虑情绪正挣脱的信心就越低,不安全的感觉就越强。

哪样的程序员更有安全感

每个人都有一个自己的舒适区域,结合内部舒适区域和外部期待,下图能说明一些问题:

根据上图所示,具有安全感的程序员一般长这样:

跃跃欲试,接受挑战, 扩展自己的舒适区。我们的身边总是不乏这样的人,他们跃跃欲试,脸上总是挂着自信的笑容,总是各种场合中的焦点,从来不满足现状,每每有新挑战时,总会看到他们的身影。 如果用动态的眼光来看待他们的话,我们会发现他们的舒适区域始终处于一个动态的过程——始终在扩张之中,或者说他们在不断GET新技能。随着舒适区域的扩张,个人追求不断实现,而个人追求的实现,又带动其自身的扩张,还有什么比实现自己目标多带来的自信和成就感更足呢。等待的,将是社会期待的增加,也就是你的同事,领导对你有了认可,并提升了他们对你的期待,随之而来的将是更有挑战的工作,又一个扩展自己舒适区域或者GET新技能的机会便发生了。

快速学习, 迅速减小社会期望和舒适区的差别。程序员的一个非常厉(ku)害(bi)的技能就是学习能力。除去被逼迫的成分外,这是一个非常实用,堪称看家本领之一。因为IT行业技术迭代速度之快令人发指,当某一项技术还没有熟悉的时候,关于该技术的缺陷以及改进方案已经吵的很热了;当你刚刚为掌握某个软件开发实践而洋洋得意时,突如其来的xxx已死之说,真是让你死的心都有了。其中的例子数不胜数,我们不妨看看(一个例子说明)。ThoughtWorks对员工有这么一个要求,每年至少学习一门编程语言,其实真是不多,实际上每年学习的都要远远多于要求。因此,强悍的快速学习能力,能够快速武装自己,胜任新的工作,提神核心竞争力,迅速扩大舒适区同时减小社会期望与其的差别。当然,并非所有新技术都值得学习,但是有两点值得提出:1. 新技术的思想能够扩展自己的视野,拓宽自己的思路;2.新技术的获取,能够大大提升自己的学习能力,你会越学越快,这个加速度会一直保持到你的大脑达到生理水平的巅峰。

很强的执行力, 减小不确定性. 它的反义词大概是“拖延症”,俗称懒病。某百科上给出了这样的解释:

严重的拖延症会对个体的身心健康带来消极影响,如出现强烈的自责情绪、负罪感,不断的自我否定、贬低,并伴有焦虑症、抑郁症等心理疾病,一旦出现这种状态,需要引起重视。

看到这里,相信很多人都中招了吧。无限的拖延,无法正面改变即将发生的事情,但是它却能极大的增加事情的不确定性,并且随着事情的临近,这种不确定性在内心中的焦虑反应呈指数级增长,反过来,这种情绪又反哺拖延,除非有强大的外部力量介入,强行打破这种负能量闭环。说的这么高端,有没有实际的例子呢。不仅有,而且非常多,很多人大概都有这样的经历吧,在年终总结的截止日期的前一天晚上,奋笔疾书加班加点的写总结,又或者在做演讲的前一天晚上,开始写PPT。

获取安全感

Rule #1: 学习那些自信满满地程序员们

当然也需要甄别一下啦,有的人可能只是看起来很自信。不过上面提到的三个特征应该是妥妥的能够提升你的安全感的办法。其实我在写这条建议是,很是纠结。纠结在哪呢?我觉得内心没有安全感的程序员极度“肤浅”的分成两类:懒和其他。据非常不负责任统计(看看周围的人),其实很多时候还是因为懒,如果是真的懒,那就没救了。

Rule #2: 通过焦虑情绪公式来提升安全感

发动数学天赋,当某人或事情让人感到焦虑时,这两件事情能够帮忙:

制作一张平衡表列出你知道的和你能影响的,它可以帮助你消除不确定性,去掉你对不知道的事情的神秘感,明确你所面临的是什么。因为焦虑只会潜藏在黑暗中,人们对不了解的事物有一种天然的恐惧,就好比美味的螃蟹在一开始的时候并不是每个人都敢吃的。然而黑暗中的不明事物也有可能真的是你所恐惧的事物也说不定,但这又有什么关系呢!当你知道原来在黑暗之中潜藏了一个恐怖猎人——霸王龙,你的对手已经清晰了,接下来要考虑的是你是否有能力对付的了它。你的焦虑情绪现在全部由无力感组成了。

当你把所有的相关因素归类到这四个象限后,也就意味着能够帮助你消除的计划产生了:你不需要对不能影响的事情做出任何反应,因为只是徒劳而已;你需要做两件事情,弄清楚那些不知道的因素,做一些能够对解决当前问题产生正面影响的行动。

想想最极端的结果,降低无力感,如果最快的结果也是能够承受的,那还有什么是输不起的呢,更何况通常最坏的结果都不是最坏的,最坏的是由最坏的结果所引发的内心恐惧,焦虑和不安的负面情绪。当然每个人的无力感是不同的,因为每个人的舒适区域是不同的,舒适区域越大,无力感想对会越小。这很好理解,因为从概率的角度来讲,你能处理的问题范围,把握度会随着舒适区域的增大而增强。但直面极端结果是一个普适的方法。一方面,往往当最坏的事情发生时,你却释然了,反而一身轻松,能够坦然面对。这正是该公式所表达的核心思想之一,当结果发生时,不确定性为0,焦虑的情绪得到了释放,你当然会前所未有的轻松,这也是为什么人们到了让TA抓狂的事情发生时,却能坦然处之,临危不乱。另一方面,想想最极端的结果,有助于你评估失败的代价,很多时候人都是被自己或者他人吓怕了,一旦评估的结果并没有那么不可接受,自然也就没有必要战战兢兢的过着了。

Rule #3: 匹配外部期待和个人能力

左图是一张耶基斯-多德森定律图,其中包含三个要素:效率,动机和任务难易程度。在一般情况下,动机愈强烈,工作积极性愈高,潜能发挥的愈好,取得的效率也愈大;与此相反,动机的强度愈低,效率也愈差。因此,工作效率是随着动机的增强而提高的。然而,心理学家耶基斯和多德森的研究证实,动机强度与工作效率之间并不是线性关系,而是倒U形的曲线关系。具体体现在:动机处于适宜强度时,工作效率最佳;动机强度过低时,缺乏参与活动的积极性,工作效率不可能提高;动机强度超过顶峰时,工作效率会随强度增加而不断下降,因为过强的动机使个体处于过度焦虑和紧张的心理状态,干扰记忆、思维等心理过程的正常活动。

因此,你需要做的是让别人更好的了解自己,如果你觉得自己很厉害但是确没能让别人知晓,那么别人对你的期待就会低于你的预期,你将要做的工作的难度也不太可能匹配你所掌握的技能,当然面对这样的工作,估计大部分人也没有新鲜,激动,亢奋,大鸡血的感觉吧。另外一方面,如果你给别人的感觉是很牛X,结果有可能让你面临超出自己技能很多的任务,这会增加你的焦虑,降低你的效率,影响个人的名声。

Rule #4: 焦虑,焦虑着也就没那么焦虑了。

说了这么多,感觉焦虑是一种要完全消灭,即便不能消灭也叫尽量避免的情绪,但凡事没有绝对,完全没有焦虑这种情绪也是不可取的,程序员需要适时地扩大自己的舒适区,而且焦虑也有它正能量的一面。

走出舒适区会增加人的焦虑程度,从而产生应激反应,其结果是提升对工作的专注程度。在这个区域中被称作最佳表现区——在这个区域之中,人的工作表现将会得到改善,并且他们的技巧也会被优化。但是罗伯特 耶基斯(1907)的报告中提到“焦虑可以改善工作表现,但是当超过某一最佳激励状态之后,工作表现就开始恶化”,如果一个人离开最佳表现区,他将进入一个危险区,在危险区中焦虑程度的上升或者舒适程度下降会带来工作表现的快速恶化。

所以在绩效管理之中管理者,或者是教练应该让人进入最佳表现区,并且维持一段足够的时间,从而达到更好的工作表现,提升他们的技巧,并且将这些表现和技巧固定下来。同样的道理,在目标设定之中改变焦虑的程度就会改变工作表现。

程序员的编程能力层次模型

来源:良少的博客(@虚拟化良少)

网址:http://blog.csdn.net/shendl/article/details/43835421

前言

程序员的编程技能随着经验的积累,会逐步提高。我认为编程能力可以分为一些层次。

下面通过两个维度展开编程能力层次模型的讨论。

一个维度是编程技能层次,另一个维度是领域知识层次。

编程技能层次

编程技能层次,指的程序员设计和编写程序的能力。这是程序员的根本。

0段—非程序员:

初学编程者,遇到问题,完全是懵懵懂懂,不知道该怎么编程解决问题。也就是说,还是门外汉,还不能称之为“程序员”。计算机在他面前还是一个神秘的黑匣子。

1段—基础程序员:

学习过一段时间编程后,接到任务,可以编写程序完成任务。

编写出来的代码,正常情况下是能够工作的,但在实际运行中,碰到一些特殊条件就会出现各类BUG。也就是说,具备了开发Demo软件的能力,但开发的软件真正交付给客户使用,恐怕会被客户骂死。

程序员程序是写好了,但到底为什么它有时能正常工作,有时又不行,程序员自己也不知道。

运行中遇到了bug,或者需求改变,需要修改代码或者添加代码,很快程序就变得结构混乱,代码膨胀,bug丛生。很快,就连最初的开发者自己也不愿意接手维护这个程序了。

2段—数据结构:

经过一段时间的编程实践后,程序员会认识到“数据结构+算法=程序”这一古训的含义。他们会使用算法来解决问题。进而,他们会认识到,算法本质上是依附于数据结构的,好的数据结构一旦设计出来,那么好的算法也会应运而生。

设计错误的数据结构,不可能生长出好的算法。

记得某一位外国先贤曾经说过:“给我看你的数据结构!”

3段—面向对象:

再之后,程序员就会领略面向对象程序设计的强大威力。大多数现代编程语言都是支持面向对象的。但并不是说,你使用面向对象编程语言编程,你用上了类,甚至继承了类,你就是在写面向对象的代码了。

我曾经见过很多用Java,Python,Ruby写的面向过程的代码。

只有你掌握了接口,掌握了多态,掌握了类和类,对象和对象之间的关系,你才真正掌握了面向对象编程技术。

就算你用的是传统的不支持面向对象的编程语言,只要你心中有“对象”,你依然可以开发出面向对象的程序。

如,我用C语言编程的时候,会有意识的使用面向对象的技巧来编写和设计程序。用struct来模拟类,把同一类概念的函数放在一起模拟类。如果你怀疑用C语言是否能编写出面向对象的代码,你可以看一下Linux内核,它是用C语言编写的,但你也可以看到它的源代码字里行间散发出的浓浓的“对象”的味道。

真正掌握面向对象编程技术并不容易。

在我的技术生涯中,有两个坎让我最感头疼。

一个坎是Dos向Windows开发的变迁过程中,框架的概念,很长一段时间我都理解不了。Dos时代,都是对函数库的调用,你的程序主动调用函数。Windows时代,则换成了框架。就算是你的main程序,其实也是被框架调用的。UI线程会从操作系统获取消息,然后发送给你的程序来处理。Java程序员熟悉的Spring框架,也是这样一个反向调用的框架。

现在因为“框架”这个术语显得很高大上,因此很多“类库”/“函数库”都自称为“框架”。在我看来这都是名称的滥用。

“类库”/“函数库”就是我写的代码调用它们。

“框架”就是我注册回调函数到框架,框架来调用我写的函数。

另一个坎就是面向对象。很长一段时间我都不知道应该怎么设计类和类之间的关系,不能很好的设计出类层次结构来。

我记得当时看到一本外国大牛的书,他讲了一个很简单、很实用的面向对象设计技巧:“叙述问题。然后把其中的名词找出来,用来构建类。把其中的动词找出来,用来构建类的方法”。虽然这个技巧挺管用的,但也太草根了点,没有理论依据,也不严谨。如果问题叙述的不好,那么获得的类系统就会是有问题的。

掌握面向对象思想的途径应该有很多种,我是从关系数据库中获得了灵感来理解和掌握面向对象设计思想的。

在我看来,关系数据库的表,其实就是一个类,每一行记录就是一个类的实例,也就是对象。表之间的关系,就是类之间的关系。O-Rmapping技术(如Hibernate),用于从面向对象代码到数据库表之间的映射,这也说明了类和表确实是逻辑上等价的。

既然数据库设计和类设计是等价的,那么要设计面向对象系统,只需要使用关系数据库的设计技巧即可。

关系数据库表结构设计是很简单的:

1、识别表和表之间的关系,也就是类和类之间的关系。是一对一,一对多,多对一,还是多对多。这就是类之间的关系。

2、识别表的字段。一个对象当然有无数多的属性(如,人:身高,体重,性别,年龄,姓名,身份证号,驾驶证号,银行卡号,护照号,港澳通行证号,工号,病史,婚史etc),我们写程序需要记录的只是我们关心的属性。这些关心的属性,就是表的字段,也就是类的属性。“弱水三千,我取一瓢饮”!
4段—设计模式:

曾经在网上看到这样一句话:“没有十万行代码量,就不要跟我谈什么设计模式”。深以为然。

记得第一次看Gof的设计模式那本书的时候,发现虽然以前并不知道设计模式,但在实际编程过程中,其实还是自觉使用了一些设计模式。设计模式是编程的客观规律,不是谁发明的,而是一些早期的资深程序员首先发现的。

不用设计模式,你也可以写出满足需求的程序来。但是,一旦后续需求变化,那么你的程序没有足够的柔韧性,将难以为继。而真实的程序,交付客户后,一定会有进一步的需求反馈。而后续版本的开发,也一定会增加需求。这是程序员无法回避的现实。

写UI程序,不论是Web,Desktop,Mobile,Game,一定要使用MVC设计模式。否则你的程序面对后续变化的UI需求,将无以为继。

设计模式,最重要的思想就是解耦,通过接口来解耦。这样,如果将来需求变化,那么只需要提供一个新的实现类即可。

主要的设计模式,其实都是面向对象的。因此,可以认为设计模式是面向对象的高级阶段。只有掌握了设计模式,才能认为是真正彻底掌握了面向对象设计技巧。

我学习一门新语言时(包括非面向对象语言,如函数式编程语言),总是会在了解了其语法后,看一下各类设计模式在这门语言中是如何实现的。这也是学习编程语言的一个窍门。

5段–语言专家:

经过一段时间的编程实践,程序员对某一种常用的编程语言已经相当精通了。有些人还成了“语言律师”,擅长向其他程序员讲解语言的用法和各种坑。

这一阶段的程序员,常常是自己所用语言的忠实信徒,常在社区和论坛上和其他语言的使用者争论哪一种语言是最好的编程语言。他们认为自己所用的语言是世界上最好的编程语言,没有之一。他们认为,自己所用的编程语言适用于所有场景。他们眼中,只有锤子,因此会把所有任务都当成是钉子。

6段–多语言专家:

这一个阶段的程序员,因为工作关系,或者纯粹是因为对技术的兴趣,已经学习和掌握了好几种编程语言。已经领略了不同编程语言不同的设计思路,对每种语言的长处和短处有了更多的了解。

他们现在认为,编程语言并不是最重要的,编程语言不过是基本功而已。

他们现在会根据不同的任务需求,或者不同的资源来选择不同的编程语言来解决问题,不再会因为没有使用某一种喜爱的编程语言开发而埋怨。

编程语言有很多种流派和思想,有一些编程语言同时支持多种编程范式。

静态类型编程范式

采用静态类型编程范式的编程语言,其变量需要明确指定类型。代表语言:C,C++,Pascal,Objective-C,Java,C#,VB.NET,Swif,Golang。

这样做的好处是:

1、编译器可以在编译时就能找出类型错误。
2、编译器编译时知道类型信息,就可以提高性能。

这种范式认为,程序员肯定知道变量的类型,你丫要是不知道变量的类型,那你就别混了!编译时,程序会报错。
Swift和Go语言都是静态类型编程语言,但它们都不需要明确指定类型,而是可以通过推断由编译器自动确定其类型。

动态类型编程范式

采用静态类型编程范式的编程语言,其变量不需要明确指定类型。任意变量,可以指向任意类型的对象。代表语言:Python,Ruby,JavaScript。

动态类型的哲学可以用鸭子类型(英语:ducktyping)这个概念来概括。JamesWhitcombRiley提出的鸭子测试可以这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

这种范式认为,程序员肯定知道变量的类型和它支持的方法和属性,你丫要是不知道变量的类型,那你就别混了!运行时程序会崩溃!程序崩溃怨谁?怨你自己呗,你不是合格的程序员!

动态类型的好处是:

不需要明确定义接口和抽象类型。只要一个类型支持需要的方法和属性,那么就OK。程序会相当灵活和简单。C++,Java,C#视之为命脉的接口/基类,在动态语言这里都视如无物!

缺点是:

1、如果类型不对,编译器也无法找到错误,而是运行时程序崩溃
2、因为编译器不知道变量的类型,因此无法优化性能。

面向对象编程范式

面向对象编程范式,从上世纪70年代末开始兴起。它支持类和类的实例作为封装代码的模块。代表语言:Smalltalk,C++,Objective-C,Java,C#,VB.NET,Swift,Go,Python,Ruby,ActionScritp,OCaml.

早期编程语言都是面向过程的。就是顺序,条件,循环,构成一个个函数。随着代码规模的增大,人们发现有必要对代码进行模块化。一个概念对应的代码放在一个文件中,这样便于并发开发和进行代码管理。

人们还发现了“程序=数据结构+算法”的规律。因此,一个概念对应的数据结构和函数应该放在一个文件中。这就是类的概念。

面向对象编程范式,确实极大地提高了生产效率,因此得到了广泛的应用,因此在语言层面支持面向对象编程范式的语言是极多的。

C语言尽管在语言层面上并不支持面向对象编程范式,但现代的C语言开发都会应用面向对象的模块化思想,把同一类的数据结构和函数放在一个文件中,采用类似的命名方式。

毕竟C语言没有在语言层面上支持面向对象,因此就有很多程序员想给C语言添加面向对象支持。其中的代表是C++和Objective-C。

C++是一种新的语言,但大部分语言元素是和C兼容的。

Objective-C是完全兼容的C的。Objective-C是给C添加了薄薄的一层语法糖以支持接口(就是其他语言的类)和协议(就是其他语言的接口)。甚至,Objective-C一开始的实现,就是一个C语言的预编译器。Objective-C坦白讲,除了添加的语法不太符合C流外,实际上其面向对象系统设计是相当精妙的。乔布斯早年慧眼识珠,把Objective-C收人囊中,因为封闭于Apple/NextStep系统内,因此少有人知。随着iOs系统的普及,Objective-C近几年才名满天下。

函数式编程范式

函数式编程范式,是一些数学家发明的编程语言,他们认为程序就是数学函数嘛。代表语言:Lisp,Erlang,JavaScript,OCaml,Prog。

有很多大牛极力鼓吹过函数式编程语言,认为其极具革命性。但我认为他们过高估计了函数式编程范式的威力,我并不认为函数式编程范式相对于面向对象编程范式有何高明之处。

函数式编程语言,核心就是函数,它们没有Class类的概念。但它的函数又不是传统面向过程语言的函数,它的函数支持“闭包”的概念。

在我看来,函数式编程语言的函数,也就是“闭包”,说白了,其实就是“类”。编程语言发展到今天,就是需要模块化,就是需要把“数据结构”和“算法”结合起来。不论何种语言,不把它们结合起来的编程方式,都是没有出路的。

面向对象编程语言,用类把“数据结构”和“算法”结合起来。类的核心是“数据结构”,也就是其“属性”,而不是“算法”,其“函数”。在类中,是函数依附于属性。

而函数式编程语言,用闭包把“数据结构”和“算法”结合起来。是函数能够抓取外部的字段。是“属性”依附于“函数”。

“类”本质上和“闭包”是等价的。现在很多面向对象编程语言都加上了对闭包的支持。观察其代码,我们可以发现,它们实际上都是用“类”来实现“闭包”的。

“类”和“闭包”谁更易用?明显是“类”。

而“闭包”更简洁一些,因此“闭包”在面向对象编程语言中常用来替换匿名类。只有一个函数的类,写成一个类太麻烦,不如写成闭包,更加简洁。

吐槽一下OCaml语言,其前身Caml语言本身是一种挺好的函数式语言,硬生生添加了一套完整的面向对象机制,同时支持面向对象和函数式编程范式,很容易像C++一样脑裂的。

也有很多面向对象语言控看着JavaScript嫌烦,总是想把面向对象支持添加到JavaScript上。ActionScript就是其中一种尝试。我用过,真的是和Java没多少区别了。

再吐槽一下ExtJS。当初选型Web前端开发框架时比较了ExtJS和JQuery。

ExtJS明显是Java高手开发的,硬生生用JavaScript模拟Swing的设计思想,搞了一套UI库。

JQuery开发者明显是领悟了JavaScript的函数式编程范式,依据JavaScript的动态函数式编程语言的特点打造了一套UI库,立刻秒杀ExtJS。

由ExtJS和JQuery的故事,我们可以看到多语言编程能力是多么的重要。ExtJS的作者精通并喜爱Java,因此他把手术刀JavaScript当做锤子Java使,一通乱敲,费力不讨好。

函数式编程语言,还有尾递归等一些小技巧。尾递归可以不用栈,防止递归调用时栈溢出。

模板编程范式

模板编程,就是把类型作为参数,一套函数可以支持任意多种类型。代表语言:C++。

模板编程的需求,是在C++开发容器库的时候发明的。因为容器需要保存任意类型的对象,因此就有了泛型的需求。

C++的模板编程,是在编译时,根据源码中的使用情况,创建对应类型的代码。除了C++这种方式,Java,C#也有类似的机制,叫做“泛型”,但它们的实现方式和C++的模板很不同。它们的编译器不会生成新的代码,而是使用强制类型转换的方式实现。

在没有模板/泛型的编程语言中,怎样在容器中存放对象呢?存取公共基类类型(Java,C#)的对象,或者void*指针(C)即可,取出时自己强制类型转换为实际类型。动态类型语言,不关心类型,更是无所谓了,随便什么对象直接往容器里扔进去,取出来直接用即可。

一些C++高手又在模板的基础上搞出了“模板元编程”。因为模板编程,就是C++的编译器搞定的嘛,模板元编程就是让编译器运算,编译完结果也就算出来了。我不知道除了研究和炫技,这玩意有啥用?

小结

一门语言是否值得学习,我认为有几个标准:

1、是否要用,要用就得学,这么没有疑问的。毕竟我们都要吃饭的嘛。

2、其语言特性是否给你耳目一新的感觉。如果是,那就值回票价了。如Go语言废掉了异常,改用返回多值。我深以为然。我其实已经主动不用异常好多年了。因为,我觉得既然C不支持异常也活得很好,为什么需要异常呢?出错了,返回错误码。无法挽回的错误,直接Abort程序就可以嘛!而且,异常实际上是违反面向过程编程原则的。一个函数应该只有一个入口一个出口。抛出异常就多了出口了。

3、是否擅长某一个领域。如果你手里只有一把锤子,那么你就只能把所有任务都当做钉子猛锤一通。但如果工具箱里有多种工具,那面对不同的任务就得心应手多了。

7段—架构设计

还需要掌握架构设计的能力,才能设计出优秀的软件。架构设计有一些技巧:

1、分层

一个软件通常分为:

表现层–UI部分
接口层–后台服务的通讯接口部分
服务层–实际服务部分
存储层—持久化存储部分,存储到文件或者数据库。

分层的软件,可以解耦各个模块,支持并行开发,易于修改,易于提升性能。

2、SOA

模块之间通过网络通讯互相连接,松耦合。每一个模块可以独立部署,可以增加部署实例从而提高性能。每一个模块可以使用不同的语言和平台开发,可以重用之前开发的服务。SOA,常用协议有WebService,REST,JSON-RPC等。

3、性能瓶颈

1)化同步为异步。

用内存队列(Redis),工作流引擎(JBpm)等实现。内存队列容易丢失数据,但是速度快。工作流引擎会把请求保存到数据库中。

通过化同步请求为异步请求,基本上99.99%的性能问题都可以解决。

2)用单机并行硬件处理。

如,使用GPU,FPGA等硬件来处理,提高性能。

3)用集群计算机来处理。

如,Hadoop集群,用多台计算机来并行处理数据。

自己的软件栈中,也可以把一个模块部署多份,并行处理。

4)用cache来满足请求。常用的内容加热cache后,大量的用户请求都只是内存读取数据而已,性能会得到很大的提升。

cache是上帝算法,记得好像它的性能只比最佳性能低一些,就好像你是上帝,能够预见未来一样。现在X86CPU遇到了主频限制,CPU提升性能的主要途径就是增加高速Cache了。

4、大系统小做

遇到大型系统不要慌,把它切分成多个模块,用多个小程序,通过SOA协作来解决。这秉承了Unix的设计思想。Unix上开发了大量单一目的的小程序,它主张用户通过管道来让多个小程序协作,解决用户的需求。当然,管道方式通讯限制太多,不够灵活。因此,现在我们可以通过URI,通过SOA的方式来让多个程序协作。Andorid和iOS上的应用程序,现在都是通过URI实现协作的。这也算是Unix设计思想的现代发展吧?!

5、Sharding切片

现在有一个潮流,就是去IOE。I-IBM大型机,O-Oracle数据库,E-EMC存储。之前,大型系统常用IOE去架构,在大型机上部署一个Oracle数据库,Oracle数据库用EMC存储保存数据。IOE是当今最强的计算机,数据库和存储。但他们面对海量系统也有抗不住的一天。

Oracle数据库是Shareeverything的,它可以在一个计算机集群(服务器节点不能超过16个)上运行。计算机集群都共用一个存储。

去IOE运动,标志着ShareEverything模式的破产。必须使用ShareNothing,系统才能无限扩展。

用MySQL数据库就可以应付任意规模的数据了。前提是,你会Sharding分片。把大系统切分成若干个小系统,切分到若干台廉价服务器和存储上。更Modern一些,就是切分到大量虚拟机上。

如,铁道部的12306网站。我们知道火车票都是从属于某一列列车的。那么我们把每一个列车作为一个单元来切分,就可以把12306网站切分成几千个模块。一台虚拟机可以承载若干个模块。当某些列车成为性能瓶颈之后,就可以把它们迁移到独立的虚拟机上。即使最终有部分列出服务不可用,系统也不会完全不可用。

12306网站,只有一个全局的部分,就是用户登录。这个可以交给第三方负责。如可以让用户用微信,微博,qq等账户登录。

也可以自己实现用户登录服务。还是用切片的方式用多台Redis服务器提供服务。Redis服务器存储每一个登录用户的sessionId和userId,角色,权限等信息。sessionId是随机生成的,可选择其部分bit用于标识它在哪一个Redis服务器上。用户登录后,把sessionId发给客户。用户每次请求时把sessionId发回给服务器。服务器把sessionId发给Redis服务器查询得到其用户信息,对用户请求进行处理。如果在redis服务器上找不到sessionId,则让用户去登录。即使所有注册用户同时登陆,也不需要太多的内存。而且,可以在session内存过多时,删除最早登陆的用户的session,强制他再次登陆。同时活跃的用户数不会太多。

领域知识层次

前面的所有层次,都是关注编程本身的技能,说白了,就是基本功,本身并不能产生太大的价值。但有太多的程序员浪费太多的时间在那些筑基的层次上。

有些程序员特别喜欢钻研编程语言,每有一种新的编程语言出来或者旧语言被热炒,就会投入精力进去研究。我就是其中之一,浪费了很多精力在编程语言上,在奇技淫巧上。

我觉得C++语言是一个特别大的坑。刚开始是作为面向对象的C被开发的。后来发现了模板编程,就大力鼓吹模板编程和进一步的模板元编程。最近又推出了C++11,C++14等新标准,进一步添加了很多新东西,函数式编程,类型推断等。C++过分复杂,太多的坑消耗了大量程序员的大量精力。我使用C++时,只使用面向对象部分和模板部分,其他过于精深的特性都不使用。

计算机科学是一个面相当广泛的学科,有很多领域知识需要和值得我们深入研究,我们才能写出有价值的程序来。软件必须要和行业结合起来,要落地才有价值。仅仅研究编程技巧,不懂领域知识是写不出有价值的程序的。

计算机科学领域有很多,列举一些如下:

存储—-块设备,文件系统,集群文件系统,分布式文件系统,光纤SCSI,iSCSI,RAID等。

网络—-以太网,光纤网,蜂窝网络,WIFI,VLAN等。

计算机体系结构,主要就是CPU指令集。x86,ARM等。

USB协议。需要知道URB包。

PCI协议,PCI-E协议。现代计算机的外设都是PCI协议和PCI-E协议的。显卡现在全是通过 PCI-E协议连接到计算机上的。相对来说减少了很多需要学习的知识。搞虚拟化就需要深入掌握PCI协议。

图像处理–图像压缩,视频实时编码等。

3D游戏
关系数据库
NoSQL数据库
操作系统
分布式操作系统
编译原理
机器学习–现在大数据要用哦!

了解这些领域知识,也包括了解该领域现有的商用硬件、商用软件和开源软件。很多时候,你要完成的工作,已经有现成的工具了。你只要使用现成的工具就可以完成任务,不需要进行开发。有时候,只需要组合现有的工具,写一些脚本就可以完成任务。

如,我一次要实现一个双向同步任务。找到了一个优秀的开源软件Unison,编写一下配置文件就圆满地完成了任务。不需要编写任何代码。

还有一次,要做高可用,用Python调用了几个开源软件就轻松实现了。

编写安装程序,定制操作系统,知道了操作系统的领域知识,写几行脚本就可以轻松搞定。

不具备领域知识的人,就可能不得不进行大量无谓的开发,甚至开发很久之后才发现,这根本就是一条死路。

另外,扎实的领域知识,可以大大提高编程调试、查错的能力。知道编译器和编程语言运行时工作原理,就能快速根据编译错误和警告信息修改代码。

知道操作系统底层运行机制,就能快速找到运行时错误的问题根源。如,有一次我编写一个windows升级服务程序。它是一个windows服务,需要执行dos脚本,这个脚本会替换掉这个windows服务本身。发现有时脚本执行无效,查了一晚上,发现当windows服务安装后,第一次启动就执行脚本时就会有权限问题,log都正确,但实际执行这个脚本没有任何效果。但一旦windows服务程序启动一次之后就ok。这必然是windows操作系统底层安全机制的问题,因为我对Windows内核了解不多,因此花了很长时间才发现这个问题,并对造成这个问题的根源并不清楚。

0段—领域知识菜鸟

对领域知识没有多少认知,通过搜索引擎找到一些该领域的软件和硬件的介绍性文章,按照文章指示配置和使用软件。勉强能够使用现有软硬件。

1段—领域知识行家

了解领域内常用硬件,深入掌握领域内常用软件的配置和使用技巧。能够使用现有软硬件熟练搭建解决方案,能够解决实际工作中遇到的种种问题。

2段—领域知识专家

当你不仅仅掌握了该领域的软件和工具,知道怎么用,还知道其原理,“知其然,也知其所以然”,就是该领域的知识专家了。

你知道网络协议的原理,你才能在网络出现问题时知道是哪里可能出现了问题。是mac冲突,ip冲突,还是网络环路?

你知道存储的原理,你才能知道为什么这种存储方式不适合虚拟化,那种存储方式适合虚拟化,另一种方式适合资料备份。

你知道PCI协议,你才能知道你怎样才能虚拟化一个硬件设备。

你知道网卡硬件协议,你才能模拟出一个虚拟机能正常使用的虚拟网卡。

你知道视频编码格式和原理,才能知道什么视频格式占用带宽最少,什么视频格式占用CPU最少。

你了解IntelVT/Amd V指令集,才能知道虚拟化是怎样实现的。

你明白工作流其实就是状态机,在遇到复杂工作流程时,你才能知道怎样设计满足要求的工作流引擎。

3段—科学家

你是领域知识专家,但你的知识都是来自于书本,来自于其他人的。

如果你满足于当领域知识专家,你只能拾人牙慧,永远别想超越。别人的研究成果,未必愿意告诉你。当别人告诉你的时候,它可能已经发现了更新的理论,并且新一代产品可能马上就要发布了。

科学家是探索未知,勇于创新的人,是推动人类社会进步的人。

传说,思科的一位高管曾经半开玩笑地说过:“如果思科停止了新技术的研发,华为就会找不着方向”。这是在嘲笑华为只是处在领域知识专家的水平,只能山寨无法超越。我不知道华为的实际情况,但希望现在的华为已经走到了领跑者的位置。

欧文·雅各布斯发现了CDMA码分多址的原理,并发现它在通讯上大有可为,组建了高通公司。高通公司主要以专利授权费为生,它雇佣了大量科学家在通讯领域展开研究。有人说高通是专利流氓。这些人不明白知识的价值。在他们眼里,Windows的合理价格就应该是5元钱,一张光盘的价格。iPhone就应该是1000多元裸机的价格。高通是专利流氓,那你也流氓一个CDMA,LTE出来给我看看!

X86芯片在设计上没有考虑虚拟化。因此会有所谓的“虚拟化漏洞”出现。就是说,一些CPU特权指令执行时,在虚拟机环境下不会抛出异常,因此就无法切换到Host。这样,X86芯片上就无法运行虚拟机。

VmWare公司是由美国的几位科学家在1998年创建的。他们发现可以使用二进制翻译的技术,在X86计算机上运行虚拟机。

Xen虚拟化软件也是几位科学家发明的。他们发现只要修改虚拟机操作系统和Host操作系统的内核,在需要执行“虚拟化漏洞”指令时直接调用Host的功能,就可以实现虚拟化,而且大大提高了虚拟机的运行性能。

后来,Intel为自己的芯片添加了IntelVT指令集,Amd为自己的芯片添加了AmdV指令集,弥补了“虚拟化漏洞”。于是就有了KVM虚拟机软件,它直接用CPU硬件指令实现虚拟化。

KVM在执行CPU指令时,是直接在物理CPU上运行的,因此效率极高。但是,虚拟机运行虚拟外设时,就必须用软件模拟,因此虚拟机的IO访问速度很慢。

IBM科学家RustyRussell,借鉴了Xen的研发经验,创建了VirtIO技术。就是在虚拟机中编写一套PCI虚拟设备和驱动,这套虚拟PCI设备有一块虚拟设备内存。这个虚拟设备内存Host是可以访问的,虚拟机通过VirtIO驱动程序也可以访问。也就是一块内存在虚拟机和Host中共享,这就解决了虚拟机的IO性能问题。

再讲一个搜索引擎的故事:

很久以前,我要给一个程序添加搜索功能。刚开始使用sql查询实现,发现实在太慢了。后来找了开源的Lucene项目。它使用反向索引技术,通过在文件中创建反向索引,大大提高了搜索速度。

Google的两位创始人发现了html中link的秘密,他们发现可以通过html页面的link关系来为每一个html页面设置权重。也就是PageRank算法。于是,Google的自动搜索引擎击败了Yahoo人工分类的搜索引擎。

OK,利用反向索引技术和PageRank,以及一个简单的html爬虫机器人,我们就可以创建一个搜索引擎了。但是,互联网很大,每天产生大量新网页,要为整个互联网建立反向索引是很困难的。

若干年后Google又公开了三篇论文:Googlefs,Mapreduce,Bigtable。于是Lucene项目的开发者根据Google的Mapreduce论文开发了Hadoop项目。MapReduce就是使用大量计算机存储数据并计算,最后汇总结果。使用Hadoop+反向索引+PageRank,就可以创建搜索引擎了。Yahoo,Baidu等公司纷纷基于Hadoop开发了自己的搜索引擎。

但是,其他公司的搜索引擎效果还是没法和Google相比。这一点我们程序员最清楚。像我,就总是翻墙出去,只为了Google一下。

Google黑板报上发表了吴军博士的一些文章,其中介绍了很多机器学习方面的知识。从文中可以知道,Google其实使用机器学习来分析搜集到的页面。Google明显不会把这个公式公开出来。即使有一天Google真的公开了这个公式,那么可以想见Google肯定又研发出了更加犀利的秘籍,山寨货的搜索引擎效果还是比不上Google的。

山寨是通向创新的必由之路。在成为领域的领头羊和领导者之前,必然要经过学习,模仿的阶段。但要成为行业的老大,成为Champion,必须勇于弯道超车,勇敢地走上创新之路,成为真正的科学家,真正的大牛!

总结

编程能力可分为两个维度:一个是编程技能水平,另一个是领域知识水平。

有些程序员可能把精力都花在提升编程技能上了,领域知识知之甚少,这其实在日常工作中也是极其有害的。有些需求可能早已经有了现成、开源免费的解决方案,或者只需要组合几个现有软件就可以快速搞定,而他们却不得不自己花大量时间去开发。另外,缺少领域知识,在程序出现非预期状况时,很难快速定位到问题的根源,很难解决bug。

什么是真正的编程能力

2016-01-18 转载自:知乎 开点工作室

问题:什么才算是真正的编程能力?

还在读书,也在实验室帮忙做了些东西,自己也搭过几个网站。在周围人看来似乎好像我很厉害,做了那么多东西,但是我发现这些东西虽然是我做的,但是实际上我手把手自己写的代码却并没有多少,很多都是用开源的东西,我写的代码无非是把别人的东西整合下,类似于胶水一样的工作。

我之前所认为的编程是全手动一行一行敲代码,但是现在我发现哪怕是工程上也有很多人是复制黏贴来解决问题的,并且提倡不要重复造轮子。

但是靠谷歌和复制别人的轮子,虽然我做出了很多东西,可是我并不觉得自己能力上有提升,倒是利用搜索引擎的能力的确提升了不少。而学校里另外一部分在搞ACM的人,他们每天都在刷题练算法,但单凭我个人的感受感觉他们似乎对工程上有些东西并不了解,或许算法的能力才算是实打实的编程能力?那”胶水”的能力和整合轮子的能力算不算编程能力呢?

所以我现在就很困惑,所谓的编程能力到底是什么,我该如何提升自己的编程能力?

回答:

作者:刘贺

链接:http://www.zhihu.com/question/31034164/answer/61625952
来源:知乎

非常好的一个问题。这可能是我在知乎见到过的问编程有关的问题中问得最好的一个了。我非常喜欢这个问题。

计算机科学有两类根本问题。一类是理论:算法,数据结构,复杂度,机器学习,模式识别,等等等。一类是系统:操作系统,网络系统,分布式系统,存储系统,游戏引擎,等等等等。

理论走的是深度,是在追问在给定的计算能力约束下如何把一个问题解决得更快更好。而系统走的是广度,是在追问对于一个现实的需求如何在众多的技术中设计出最多快好省的技术组合。

搞ACM的人,只练第一类。像你这样的更偏向于第二类。其实挺难得的,但很可惜的是第二类能力没有简单高效的测量考察方法,不像算法和数据结构有ACM竞赛,所以很多系统的苗子都因为缺少激励和正确引导慢慢就消隐了。

所以比尔盖茨才会说,看到现在学编程的人经常都把编程看作解各种脑筋急转弯的问题,他觉得很遗憾。

做系统,确实不提倡“重复发明轮子”。但注意,是不提倡“重复发明”,不是不提倡“重新制造”。恰恰相反的,我以为,系统的编程能力正体现在“重新制造”的能力。

能把已有的部件接起来,这很好。但当你恰好缺一种关键的胶水的时候,你能写出来吗?当一个已有的部件不完全符合你的需求的时候,你能改进它吗?如果你用的部件中有bug,你能把它修好吗?在网上繁多的类似功能的部件中,谁好谁坏?为什么?差别本质吗?一个开源代码库,你能把它从一个语言翻译到另一个语言吗?从一个平台移植到另一个平台吗?能准确估计自己翻译和移植的过程需要多少时间吗?能准确估计翻译和移植之后性能是会有提升还是会有所下降吗?

系统编程能力体现在把已有的代码拿来并变成更好的代码,体现在把没用的代码拿来并变成有用的代码,体现在把一个做好的轮子拿来能画出来轮子的设计蓝图,并用道理解释出设计蓝图中哪些地方是关键的,哪些地方是次要的,哪些地方是不容触碰的,哪些地方是还可以改进的。

如果你一点不懂理论,还是应该学点的。对于系统性能的设计上,算法和数据结构就像在自己手头的钱一样,它们不是万能的,但不懂是万万不行的。

怎么提高系统编程能力呢?土办法:多造轮子。就像学画画要画鸡蛋一样,不是这世界上没有人会画鸡蛋,但画鸡蛋能驯服手指,感受阴影线条和笔触。所以,自己多写点东西吧。写个编译器?渲染器?操作系统?web服务器?web浏览器?部件都一个个换成自己手写的,然后和已有的现成部件比一比,看看谁的性能好,谁的易用性好?好在哪儿?差在哪儿?为什么?

更聪明一点的办法:多拆轮子。多研究别人的代码是怎么写的。然而这个实践起来经常很难。原因:大部分工业上用的轮子可能设计上的思想和技术是好的,都设计和制造过程都很烂,里面乱成一团,让人乍一看毫无头绪,导致其对新手来说非常难拆。这种状况其实非常糟糕。所以,此办法一般只对比较简单的轮子好使,对于复杂的轮子,请量力而行。

轮子不好拆,其实是一个非常严重的问题。重复发明轮子固然是时间的浪费,但当轮子复杂而又不好拆的时候,尤其是原来造轮子的人已经不在场的时候,重新发明和建造轮子往往会成为无奈之下最好的选择。这是为什么工业界在明知道重复发明/制造轮子非常不好的情况下还在不断重复发明/制造轮子的根本原因。

程序本质是逻辑演绎的形式化表达,记载的是人类对这个世界的数字化理解。不能拆的轮子就像那一篇篇丢了曲谱的宋词一样,能读,却不能唱。

鄙人不才,正在自己研究怎么设计建造一种既好用又好拆的轮子。您没那么幸运,恐怕是等不到鄙人的技术做出来并发扬光大了。在那之前,多造轮子,多拆好拆的小轮子,应该是提高编程能力最好的办法了。

以上。嗯。
(文章属个人观点,与本人工作雇主无关。)

回答:

作者:家飞猫
链接:http://www.zhihu.com/question/31034164/answer/61625952
来源:知乎

编程能力是一种解决问题的能力。如果问题没能被很好地解决,知道再多也没用。
编程能力是一种运用机器解决问题的能力。首先是要判断问题在什么程度上可被机器解决,比如理论计算机科学会告诉我们什么可做、什么理论上不可做、什么理论上可做实践上不可做。然后是让机器更好地理解问题,比如计算机都是(图灵-冯诺依曼模型)等价,但不同的问题可能会适用不同的编程语言。再后是让机器能更高效率地解决问题,比如同样的问题可能会有效率差别巨大的算法。
编程能力是一种抽象问题的能力。借用轮子是很好的办法,省力省时间。今天任何软件工程师都会有意无意地使用很多轮子,从操作系统编译器数据库网络到算法数据结构。想高效地借用轮子,就需要将问题分解再分解,抽象再抽象。任何一个实用的系统(不包括教科书上的示例程序和简单的脚本程序)都需要进行大量的分拆和组合。所以系统设计是编程能力里的高级技能,加合理的假设简化问题尤其有难度,此处不展开讨论。高手和新手的区别在于新手往往不知道轮子的适用范围,而高手的手上轮子数量多且熟知各种轮子的差异,所以对不同的问题可以轻松地找到合适的轮子,当实在找不到合适的轮子时可以自己动手改造现有的轮子。平时有时间拆装和改造已有的轮子会对水平提升有较大帮助。当然能知道怎样快速在搜索引擎里搜出轮子也是一种能力。
编程能力是一种需要考虑扩展性的能力。算法竞赛中的很多算法考虑的是单机的内存算法,计算模型经过高度抽象,在实践中机器的模型更为复杂。比如单机的多级结构带来的各种时间空间复杂度的取舍平衡,多机网络中如何能在提高单机性能外进一步优化整体性能。除了在机器端的扩展,在程序员一端的扩展也很重要。复杂的问题和工程往往意味着团队协同以及更长时间的开发维护,团队分工和设计沟通这里暂且不论。举个容易被忽视的例子,程序中的注释。高手会更在意完整且表达清楚的注释,因为这是写给现在和未来的团队(包括自己和其他成员)看的,直接影响到长期的整体开发维护效率。
编程能力是一种取舍的能力。局部的最优解未必是全局的最优解。如果一个美妙的解决方案需要将完工时间向后推迟一两个月,需要考虑是否先使用平凡方案解决问题,之后再进行优化。当你的工作延后会阻碍别人的工作时尤其如此。发现一个绝妙的优化方案时先想想这个优化是否真的有价值,如果只是系统中很小的部分,那么不要为了追求心理满足而花很多时间放一个漂亮的轮子上去(参考Amdahl定律)。
编程能力是一种预见未来的能力。目前的方案有哪些假设和局限性,在何种情形下会遇到问题甚至崩溃。在未来出现问题时问题是否需要重新定义,系统是否需要重新设计,代码是否需要重构或优化等等都需要未雨绸缪。
编程能力是一种工程能力。无它,唯手熟尔。

编程能力是一种解决问题的能力。如果问题没能被很好地解决,知道再多也没用。
编程能力是一种解决问题的能力。如果问题没能被很好地解决,知道再多也没用。
(重要的事情说三遍,重要的事情说三遍,重要的事情说三遍)