改变CSS世界纵横规则的writing-mode属性

作者:张鑫旭(@张鑫旭)

链接:http://www.zhangxinxu.com/wordpress/2016/04/css-writing-mode/

一、冉冉升起的writing-mode

writing-mode这个CSS属性,我们是不是很少见到,很少用到!我们往往称不常见的东西为“生僻”,就像是不常见的文字我们叫“生僻字”,因此不常见的CSS属性,我们可以叫做“生僻属性”,writing-mode给我们的感觉就是一个“生僻属性”,很弱,可有可无。

但是,实际上,我们都错了,大错特错,writing-mode很弱?卧槽,别开玩笑了,writing-mode可以说是CSS世界里面最逆天的CSS属性了,直接颠覆CSS世界的众多规则。

而writing-mode之所以给人“生僻”的感觉,是有原因的。

实际上writing-mode这个CSS属性在上古时代就诞生了,IE5.5浏览器就已经支持了:

那就奇怪了!writing-mode既然这么鸟,同时时间早,资格老,为啥一直沉寂了差不多20年呢?

那是因为,在很长一段时间里,FireFox, Chrome这些现代浏览器都不支持writing-mode,writing-mode基本上就是IE浏览器的私有产物,大家对IE一直没啥好感,对吧,爱屋及乌由此及彼,自然对writing-mode也不待见。

然而,就在我们被流行前端技术一叶蔽目的时候,各大现代浏览器纷纷对writing-mode实现了更加标准的支持(主要得益于FireFox浏览器的积极跟进),也就是说,不知什么时候起,writing-mode的兼容性已经不成问题了,加上该属性本身特性逆天,我去,我仿佛看到了一个冉冉升起的新星,不对,是新月,而且是圆月。

二、writing-mode的原本作用

和float属性有些类似,writing-mode原本设计的是控制内联元素的显示的(即所谓的文本布局-Text Layout)。因为在亚洲,尤其像中国这样的东亚国家,存在文字的排版不是水平式的,而是垂直的,例如中国的古诗古文。

因此,writing-mode就是用来实现文字可以竖着呈现的。

您可以狠狠地点击这里:CSS writing-mode与文字垂直排版demo(http://www.zhangxinxu.com/study/201604/writing-mode-text-vertical-layout.html)

截自IE11浏览器IE8模式:

writing-mode语法

writing-mode的语法学习相比其他CSS属性要高一些,因为我们需要记住两套不同的语法。一个是IE私有属性,第二个是CSS3规范属性。

先看下未来所需的CSS3语法:

/* 关键字值 */

writingmode: horizontaltb;    /* 默认值 */

writingmode: verticalrl;

writingmode: verticallr;

 

/* 全局值-关键字inherit IE8+,initial和unset IE13才支持 */

writingmode: inherit;

writingmode: initial;

writingmode: unset;

各个关键字属性值的含义,我们透明名称就能知道其大概的意思,例如,默认值horizontal-tb表示,文本流是水平方向(horizontal)的,元素是从上往下(tb:top-bottom)堆叠的。

vertical-rl表示文本是垂直方向(vertical)展示,然后阅读的顺序是从右往左(rl:right-left),跟我们古诗的阅读顺序一致。

vertical-lr表示文本是垂直方向(vertical)展示,然后阅读的顺序还是默认的从左往右(lr:left-right),也就是仅仅是水平变垂直。

下面是各个值下的中英文表现对照(参考自MDN):

//zxx: 大家会发现英文字符横过来了,可以试试使用text-orientation:upright让其直立,IE不支持,FireFox, Chrome支持。

下面来看下老IE浏览器的语法,由于历史原因,显得相当的复杂,IE官方文档显示如下:

-ms-writing-mode: lr-tb | rl-tb | tb-rl | bt-rl | tb-lr | bt-lr | lr-bt | rl-bt | lr | rl | tb

根据自己的测试(非原生IE8,IE9),-ms-私有前缀是可缺省的,直接writing-mode所以IE浏览器都是支持的。-ms-writing-mode这种写法IE7浏览器是不支持的,但是官方有如下说明:

Windows Internet Explorer 7. The rl-tb, and bt-rl values are available to the -ms-writing-mode

就是说IE7的-ms-writing-mode可以使用rl-tb和bt-rl这两个值,但这和自己的测试不符,我觉得可能是原生IE7浏览器,但我没有原生IE7,没有进行过测试,因此,此说法(原生IE7支持)只是自己的推测。

我扳指头数了数,IE浏览器下的关键字值多达11个,正好可以组个足球队,

lr-tb

IE7+浏览器支持。初始值。内容从左往右(left-right),从上往下(top-bottom)水平流动,以及下一行水平元素在上一行元素的下面,所有符号都是直立定位。大部分的书写系统都是使用这种布局。

rl-tb

IE7+浏览器支持。内容从右往左(right-left,从上往下(top-bottom)水平流动,以及下一行水平元素在上一行元素的下面,所有符号都是直立定位。这种布局适合从右往左书写的语言,例如阿拉伯语,希伯来语,塔安那文,和叙利亚语。

tb-rl

IE7+浏览器支持。内容从上往下(top-bottom),从右往左(right-left)垂直流动, 下一个垂直行定位于前一个垂直行的左边,全角符号直立定位,非全角符号(也可以被称作窄拉丁文或者窄假名符号)顺时针方向旋转90°。这种布局多见于东亚排版。

bt-rl

IE7+浏览器支持。内容从下往上(bottom-top),从右往左(right-left)垂直流动, 下一个垂直行定位于前一个垂直行的左边,全角符号直立定位,非全角符号(也可以被称作窄拉丁文或者窄假名符号)顺时针方向旋转90°。此布局多见于在东亚垂直排版从右往左的文本块上。

tb-lr

IE8+浏览器支持。 内容从上往下(top-bottom),从左往右(left-right)垂直流动。下一个垂直行在前一个的右边。

bt-lr

IE8+浏览器支持。 内容从下往上(bottom-top),从左往右(left-right)垂直流动。

lr-bt

IE8+浏览器支持。 内容从下往上(bottom-top),从左往右(left-right)水平流动。下一个水平行在前一行的上面。

rl-bt

IE8+浏览器支持。内容从下往上(bottom-top), 从右往左(right-left)水平流动。

lr

IE9+浏览器支持。在SVG和HTML元素上使用。等同于lr-tb.

rl

IE9+浏览器支持。在SVG和HTML元素上使用。等同于rl-tb.

tb

IE9+浏览器支持。在SVG和HTML元素上使用。等同于tb-rl.

各个属性值的表现如下(form微软官网)

一些说明:

  • 相同的writing-mode属性值并不会累加,例如父子均设置了writing-mode:tb-rl,只会渲染一次,子元素并不会2次“旋转”。
  • IE浏览器下,一个自身具有布局的元素(不是纯文本之类元素)如果writing-mode属性值和父元素不同,当子元素的布局流变化的时候,其父元素坐标系统的可用空间会被充分利用。左边文字太过术语,大家可能不懂,我解释下就是,IE浏览器下,当布局元素从水平变成垂直的时候(举个例子),你就想象为元素在垂直方向是100%自适应父元素高度的。所以,IE浏览器下(不包括IE13+),元素vertical流的时候会发现高度高的吓人,布局和其他现代浏览器不一样,就是这个原因。
  • Chrome浏览器下目前还需要-webkit-私有前缀,虽然Chrome和Opera认识tb-rl等老的IE属性值,但是,仅仅是认识而已,根本不鸟,没有任何效果,聋子的耳朵——摆设!

需要关注的writing-mode属性值

从着眼于直接开发的角度而言,虽然IE支持多达11个私有的属性值,但是,我们需要关注的,也就那么几个,那究竟是哪几个呢?

如果你的项目需要兼容IE7,则只有关注这两个值就可以了:初始值lr-tb和tb-rl,对应于CSS3规范中的horizontal-tb和vertical-rl!其他9个属性值就让它们去过家家好了。

如果你的项目只需要兼容IE8+,恭喜你,你可以和CSS3规范属性完全对应上了,而且IE8下的writing-mode要比IE7强大的多。我们需要关注:初始值lr-tb, tb-rl以及tb-lr,分别对应于CSS3中的horizontal-tb, vertical-rl以及vertical-lr。

看上去复杂的属性是不是变得很简单了,重新整一个实战版:

writingmode: lrtb | tbrl | tblr (IE8+);

writingmode: horizontaltb | verticalrl | verticallr;

对,大家只要记住上面几个就可以了,enough! 因为所谓的垂直排版,实际web开发是很少很少遇到的。

有同学可能要疑问了,既然writing-mode实现文本垂直排版场景下,那还有什么学习的意义呢?

前面也提到了,虽然writing-mode创造的本意是文本布局,但是,其带来的文档流向的改变,不仅改变了我们多年来正常的CSS认知,同时可以巧妙实现很多意想不到的需求和效果。

三、writing-mode不经意改变了哪些规则?

writing-mode将页面默认的水平流改成了垂直流,颠覆了很多我们以往的认知,基于原本水平方向才适用的规则全部都可以在垂直方向适用!

1. 水平方向也能margin重叠

W3C文档margin重叠之一:

The bottom margin of an in-flow block-level element always collapses with the top margin of its next in-flow block-level sibling, unless that sibling has clearance.

清清楚楚写的bottom margin和top margin会重叠;然而,这是CSS2文档中的描述,在CSS3的世界中,由于writing-mode的存在,这种说法就不严谨了,应该是对立流方向的margin值会发生重叠。换句话说,如果元素是默认的水平流,则垂直margin会重叠;如果元素是垂直流,则水平margin会重叠。

您眼见为实,您可以狠狠地点击这里:CSS writing-mode与margin水平重叠demo

结果:

2. 可以使用margin:auto实现垂直居中

我们应该都是的,传统的web流中,margin设置auto值的时候,只有水平方向才会居中,因为默认width是100%自适应的,auto才有计算值可依,而垂直方向,height没有任何设置的时候高度绝不会自动和父级高度一致,因此,auto没有计算空间,于是无法实现垂直居中。但是,在writing-mode的世界里,纵横规则已经改变,元素的行为表现发生了翻天覆地的变化。

  • 图片元素

我们先来看下,图片元素margin:auto实现垂直居中,您可以狠狠地点击这里:CSS writing-mode与图片margin:auto垂直居中demo(http://www.zhangxinxu.com/study/201604/writing-mode-vertical-image-margin-auto.html)其中图片:

img { display: block; margintop: auto; marginbottom: auto; }

FireFox浏览器下(P白省流量):

但是,在IE浏览器下,却没有垂直居中~~

纳尼?!难道IE不支持垂直流下的垂直居中?非也,根据鄙人的测试,也就是图片这类替换元素貌似不行,普通的block元素都是可以的。

  • 普通块状元素

您可以狠狠地点击这里:CSS writing-mode与普通block元素margin:auto垂直居中demo(http://www.zhangxinxu.com/study/201604/writing-mode-vertical-margin-auto.html)此时,不仅IE11 edge,甚至IE8浏览器也都垂直居中了!IE8浏览器下block元素的margin:auto居中

3. 可以使用text-align:center实现图片垂直居中

前面提过,auto无法实现IE浏览器下的图片垂直居中,如果我们非要让图片垂直居中,可以使用text-align:center,您可以狠狠地点击这里:CSS writing-mode与图片text-align:center垂直居中demo(http://www.zhangxinxu.com/study/201604/writing-mode-vertical-image-text-align-center.html)

结果,之前病恹恹的IE浏览器活了:

由于我们直接使用内联特性进行控制的,因此,IE7浏览器也是可以实现text-align:center下的图片垂直居中,但是,根据我在IE11↘IE7下的测试,writing-mode需要写在最后重置下(原生估计不会这样),因此,完整的writing-mode代码为:

.verticlemode {

    writingmode: tbrl;

    –webkitwritingmode: verticalrl;      

    writingmode: verticalrl;

    *writingmode: tbrl;

}

4. 可以使用text-indent实现文字下沉效果

这是真实项目例子,要增加一个按钮按下文字下沉的效果。如果你来实现,你会这么实现呢?行高控制?但默认文本就不居中(对于高度自适应的按钮,line-height下沉为了避免按钮高度变化,默认是不能完全居中的)。padding+height精确控制,又略烦。然而,在writing-mode垂直流下,我们又有了新思路,例如,直接使用text-indent实现垂直方向的控制,没想到吧,无需关心height高度padding间距大小,任何按钮都可以通用,因为text-indent不会影响元素原本的盒布局。

您可以狠狠地点击这里:CSS writing-mode与text-indent文字下沉效果demo(http://www.zhangxinxu.com/study/201604/writing-mode-text-indent-vertical-down.html)

包括IE7在内的浏览器都是支持的(同上最后要*writing-mode覆盖下)都是支持下沉的。

为什么有如此的实现呢?这要归功于中文,在垂直流排版的时候,中文是不会旋转的,还是直立的,也就是说,虽然我们肉眼看上去文字没什么变化,但是,布局流已经发生了变化,以前类似text-indent/letter-spacing等水平控制属性都作用在垂直方向了。

当然,我们这个例子比较巧的是按钮文字只有一个,要是按钮文字有多个,怕是就没这么轻松和绝妙了。

5. 可以实现全兼容的icon fonts图标的旋转效果

在老的IE浏览器下,我们要实现小图标的旋转效果是不是很烦?要使用IE的旋转或翻转滤镜(filter)什么的,具体可参见我之前的“CSS垂直翻转/水平翻转提高web页面资源重用性”以及“IE矩阵滤镜Matrix旋转与缩放及结合transform的拓展”一文。

现在我们有了writing-mode,我们就不要这么烦心了。

前面可能也注意到了,当writing-mode把文档变成垂直流的时候,我们的英文和数字符号是会“躺着”显示,也就是天然90°旋转了。此时,我们不妨脑洞大开一下,假如我们使用icon fonts技术让这些字符直接映射某个小图标,那岂不是松松实现小图标旋转了,关键在于,就算是千年杀的IE6,IE7浏览器也是支持的啊,这要比滤镜什么的简单多了!

眼见为实,您可以狠狠地点击这里:writing-mode实现icon fonts图标旋转效果demo(http://www.zhangxinxu.com/study/201604/writing-mode-font-face-icon-rotate.html)

就算是IE7浏览器,也是很给力的!

6. 充分利用高度的高度自适应布局

卧槽,不行了,内容太多了

往下的7,8,9,10一起都略了吧~~

总之,放开自己的大脑,理论上讲,有了writing-mode,我们能够做的事情比以前多了50%,就怕你想不到,不怕做不到。

四、writing-mode和direction的关系

上个月刚刚介绍了CSS direction属性,也是个好东西,具体参见“CSS direction属性简介与实际应用”,其可以改变文字的走向,那他和writing-mode是个什么关系呢?

writing-mode, direction, unicode-bidi(MDN文档)是CSS世界中3大可以改变文本布局流向的属性。其中direction, unicode-bidi属于近亲,经常在一起使用,也是唯一两个不受CSS3 all属性影响的CSS属性,基本上就是和内联元素一起使用使用,且据说貌似为阿拉伯文字设计。

乍一看,writing-mode似乎包含了direction, unicode-bidi某些功能和行为,例如vertical-rl的rl和direction的rtl值有相似之处,都是从右往左。然而,实际上,两者是没有交集的。因为vertical-rl此时的文档流为垂直方向,rl表示水平方向,此时再设置direction:rtl,实际上值rtl改变的是垂直方向的内联元素的文本方向,一横一纵,没有交集。而且writing-mode可以对块状元素产生影响,直接改变了CSS世界的纵横规则,要比direction强大和鬼畜的多。且据说貌似为东亚文字设计。

然而,CSS的奇妙就在于,某些特性当初可能就是问了某些图文排版设计,但是,我们可以利用其带来的特性,发挥自己的创造力,实现其他很多意想不到的效果。所以,上面出现的三剑客都是非常好的资源。

五、writing-mode和*-start属性的流机制

CSS3中出现了诸多*-start/*-end属性(亦称为CSS逻辑属性),例如:margin-start/margin-end, border-start/border-end, padding-start/padding-end, 以及text-align:start/text-align:end声明。

下面问题来了,为什么会蹦出这么多*-start/*-end鬼?

那是因为现代浏览器加强了对流的支持,包括老江湖direction,以及最近年月跟进的writing-mode。

在很久以前,我们的认知里,网页布局就一种流向,就是从左往右,从上往下,因此,我们使用margin-left/margin-right没有任何问题。但是,如果我们流是可以变化的,例如,一张图片距离左边缘20像素,我们希望其文档流是从右往左,同时距离右侧是20像素,怎么办?

此时,margin-left:20px在图片direction变化后,就无效了;但是,margin-start就不会有此问题,所谓start, 指的是文档流开始的方向,换句话说,如果页面是默认的文档流,则margin-start等同于margin-left,如果是水平从右往左文档流,则margin-start等同于margin-right。margin-end也是类似的。

webkit内核的浏览器还支持*-before和*-end,默认流下的margin-before近似于margin-top,margin-after近似于margin-bottom,然而,规范貌似没提及,FireFox也没支持,*-before和*-after出场的机会并不多,为什么呢?因为实际上,配合writing-mode,*-start/*-end已经可以满足我们对逻辑位置的需求了,水平和垂直都可以控制,对立方向适用老的*-top/*-bottom.

例如,我们设置writing-mode值为vertical-rl,此时margin-start等同于margin-top,如果此时margin-start,margin-top同时存在,会遵循权重和后来居上原则进行相互的覆盖。

可以看到,场景不同,margin-start的作用也不能,能上能下,能左能右简直在世百变星君。

关于*-start/*-end以后有机会会具体展开论述,这里就先点到为止,大家估计目前也不会在实际项目中使用。

小公司的前端应该怎么做?

作者:叶小钗(@欲苍穹)

链接:http://www.cnblogs.com/yexiaochai/p/5311712.html

前言

近期工作生活比较漂泊,从上海换到了成都,这个是以家庭为单位的重大决定,离开一线城市对于职业生涯和技术前沿来说是有一定问题的,但是出于房价考虑(主要是买不起房子)与生活舒适度来说,回蓉似乎是一个比较好的选择,何况成都的互联网也还行。

随着能力的提升,负责的工作种类会逐渐增多,考虑的方向也会有所不同,这个时候不太会有太多单独的知识点成为阻碍了,工作中碰到的问题要么太“大”,总结起来费力,要么太“小”,不适合单独整理成文,这种现象反而造成了一段时期的博客空白期,总的来说就是又开始懒了,思维又开始懈怠,人又开始想待在安逸区了。

因为开始懒了,所以没能学习到新知识,自然出不了新文;因为开始懒了不愿意再对工作中的问题一个个深入研究、深入挖掘自然出不了新文。

所以人还是得不停的鞭笞自己啊,不能让自己陷入“瞎忙”的境地,得有效的利用时间,才能走的更远!!!

现在我在一家小公司做前端,因为公司以及职位的变化,对于在小公司如何做前端有一些心得,拿出来与各位做个分享,希望对处于小公司的前端有一定用处,也鞭笞自己重新学习总结。

什么是优秀的前端团队?

团队初期缺什么

在公司中,层级越高对业务关注比例越高,反而不太关注个人成长,所以评价一个leader是以团队为单位,团队成员比他强是应该的;对于个人来说的话,要多关注自身能力成长,然后能力匹配自己的职位,甚至超出自己的职位,这样的团队的话,战斗力是比较强的。

主管(包括前端主管)设定目标必须可量化 ,比如你做一个业务,kpi是多少,那么技术就需要考虑如何才能达成,细化到研发甚至前端层级,就是所谓技术kpi了。

比如,今年H5站想达到单日平均出票量10000,那么这个就是业务目标,需要消化分到各个业务团队,可以是:

① SEO优化

② SEM优化

③ 营销广告

④ 微信&支付宝&手机百度流量接入(微信钱包是十分优秀的流量入口,可以极大程度的增加流量)

⑤ 实地推广

……

以上当然只能解决部分问题,具体到前端,可能我们就要从页面转换率入手,建立订单漏斗模型,做性能优化,做交互优化,每一个具体的层面都需要转化目标。

这些都是直接可量化的东西,因为当前业务已经到了一个瓶颈,或者公司已经到了一个瓶颈,业务上就需要做不停的尝试,对应到技术就是需要你快速迭代,低成本迭代,不断的容错试错。

这个时候就会提出很多问题:

第一是你的团队在类似高压下会不会主动加班去实现公司的目标、个人的kpi。

第二是你的团队在这轮高压拼搏后有没有留下什么东西?

根据之前经验,没有团队可以无休止的承受高压加班的压力

以之前携程无线高压迭代的经历来说,就算是那么优秀的团队事实上到后期也是疲惫不堪,疲惫的时候容易犯错,亢龙有悔,盈不可久。

第三是如何帮助新人快速的融入团队,如何让1+1=2。

我们都清楚,好的项目绝不是堆人可以堆出来的,如何让一个项目可以分解到各个人手中,如何让良莠不齐的同事可以更好的协作,这个是我们需要考虑的。

要解决这些问题是要靠平时的积累,具体体现到前端是:

1 在不停的迭代中,你的业务流程是不是最优(产品到设计到前端到最终上线流程)

2 在不停的迭代中,是否沉淀出来了公共服务与工具化服务

好的前端是什么样的

首先,好的前端是一定愿意加班的,同时,好的前端是会找办法让团队少加班的。

和一些朋友做过交流,很多好的点子,改善工作效率的点子都是几个人讨论后私下晚上搞出来,然后反复实践用于生产的。

一般来说业务kpi对于能力强的朋友来说不会太难,所以对他们的期待也会更多:

有强烈的意识,能深刻了解到当前项目性能的缺陷,开发效率低下的原因,并会找寻处理办法

很多团队在快速迭代中会开始“欠账”,时间久了就不愿意还,问题的存在搁置需要想办法去解决,团队成员是看得到问题的,没人说,没人做是因为知道那是坑,你如果能解决的话,一到二次便能提升自己在团队中的位置。

好的前端应该有良好的架构设计能力

首先,好的前端能向人清晰有条理的描述自己的技术方案,并且让人听得懂!

然后架构设计能满足长久的需求发展,就算业务频道扩大了10倍,用户量增加了100倍,也不会有根本的变动。

好的前端应该具有良好的交流能力

对内,好的前端需要了解团队成员的性格与能力,做出适当的任务分配分解;对外,需要抢占业务还不能产生利益冲突,这类人是项目推进的主力。

小公司的前端应该怎么做

不是所有的小公司都这样,但是我见过的小公司的前端都在扑业务,并且疲于奔命,这个是个恶性循环,第一次做业务:

加班赶业务-业务结束轻松一周-加班赶迭代-业务结束轻松一周-加班新业务-业务结束轻松下……

偶尔你会问这些朋友为什么没有什么积累,得到的答案基本是一致的,忙啊!他们忙起来的时候是真的很忙,但是第二次如果依旧这么忙的话就有问题,第三次还这样就是团队不健康了,一个好的做法是:

① 完成前后分离,这步做不到,后面也不用做了

② 形成几套UI库

③ 根据业务形态,形成公共业务

④ 前端重复工作工具化

⑤ 形成优化体系

⑥ 形成统计体系

⑦ 建立页面转化漏斗模型

⑧ 做ABTesting方案

……

首先,无论出于什么考虑,前后一定要做分离,如果有SEO需求,那么再后续推进nodeJS方案,毕竟现在不给钱想排前面还是很难,SEO基本没意义。

其实,小公司有很多坑可以占住,这个会帮助你建立团队威望,下面我举几个细节点说一说。

UI库

UI库的形成与UI库的多少将决定你后续项目重复工作量的多少,这个UI库需要注意几点:

① UI是否可重用

② UI是否可定制

比如让很多朋友去做这个时间选择器,做出来就真的是时间选择器,如果让他换成城市选择器,就全傻眼了:

③ UI是否可拆分,可聚合

还是以上面UI为例,这个组件事实上是一个聚合组件,由一个select组件与一个弹出层组件组成,你的UI是不是可拆分是评价他质量的一个很大考虑点。

……

公共服务

公共服务可以说成一个大一点的“UI组件”,但是他是与业务相关的,UI来说一般不会与业务产生耦合,以上面的日期选择器来说,无论他装的是日期还是区域都是可以的,并且不应该请求服务,他是纯净的UI组件。

而公共服务是不纯净的是一定与业务相关的,移动端比较常见的公共服务是:

passport

包含登录注册、个人资料管理,甚至包含一些认证相关的,与公司账号相关的操作,登录注册是各种活动,各种业务频道都可能会使用的业务,这种东西是必须服务化的,但是很多小公司都没做。

因为公共的特点,页面设计最好中性一点,其中几个常用的页面,比如登录需要包含以下设计:

① 样式可定制化(弹出层、独立页面什么的都是常事)

② 回退可定制,其实所有的公共服务回退按钮都是需要定制的,登录成功去哪个URL登录失败去哪个URL,点击浏览器回退去哪个URL都得约定,少一个都不是公共服务

③ 单点登录,事实上初期根本用不到什么单点登录,甚至大家都不是跨域的,所以后续需要再支持即可

还有很多与passport一样的公共业务,比如:

① 钱包服务,包括用户支付订单相关管理

② 城市列表,这个要考虑参数如何传递

③ 反馈系统

④ 公司介绍

除了面向C端的公共页面服务,还会有面向B端的统计平台相关。

前端工具化

静态资源处理

评价一个前端团队是否优秀成熟的评判多以团队工具化的程度,一个简单的例子是:

① 你们前端静态资源是如何组织的、如何打包的

② 你们前端静态资源是如何解决缓存的(比较好的方案是MD5)

上面两点可以使用grunt/gulp一类的构建工具轻松做到,如果有公共框架文件还会需要引入种子文件的概念

跨域问题

另外,所有前端团队都会遇到跨域问题,特别是前后分离后,服务器端只提供API接口,前端代码随便在哪都能运行,那么这个时候你是怎么做呢?

① 使用fiddler&charles做代理

② 提供测试服务器

③ 支持jsonp跨域

④ 支持cors跨域

那么这些方案,哪种最适合团队,哪种成本最低(一般来说是代理),是我们需要考虑的

tips:我之前使用fiddler,现在换mac了使用charles,两款工具十分优秀,正则一块的处理很好,推荐使用

移动端适配

从后端转到前端的同学一般在业务逻辑上有一些天生的优势,但是往往在CSS一块比较弱,如何在开发人员无感的情况下引入rem,如何与现有机制无缝的使用less,如何处理单页应用中css的污染,这个是框架底层需要考虑的。

模块化&组件化开发

团队上规模后,如何使用模块化开发处理协作问题;业务代码复杂度上升后,如何使用组件化编程思维简单开发复杂度,这些需要应用到项目实践中,并且路径是可复制的;

一些优化手段,也需要工具化,框架化,让开发人员无感。

前后端协作

前端与服务器端,开发速度未必同步,事实上很多时候都不是同步的,在已经约定了接口格式的情况下,接口还没有写好,但是前端依然能写交互,团队是如何写这种假数据,这个方面实现会大大的提升工作效率。

订单下降分析

如果在某一个时间段,全站的流量或者全站的订单量下降了,你如何跟踪这次下降的原因,如何最大程度上避免下次出现类似的现象,这个时候数据统计会避免我们成为瞎子,所以得尽快建立统计平台,转换率模型。

快速迭代,通过迭代来优化产品,但是如果每一个迭代都完全颠覆了之前的设计,很多时候公司就是原地踏步,每迈出一步你要清晰的知道前一个版本哪里出了问题,针对问题做优化,而不是频繁改版。

这次改版后,你如何知道这次优化就比上一次的好,而不是其它因素造成,ABTesting方案应该是每一个成熟团队必须的,持续优化这些都是建立在有效的数据监控与意见反馈机制上的,我们不能做完网站变成瞎子。

结语

因为很久没有写博客了,又因为最近刚好来到了小团队,有一些想法以水文的形式发出来与各位讨论,希望对各位有用。

诚然,对于一个前端来说,要推动上述工作还是有一点难度的,但并不是不可能,前端对自己的定位要变,从前端工程师到软件工程师。

我常常听到很多前端自己都在质疑自己,或者就是膨胀到觉得自己团队没有什么可以做了,那么这个时候可以考虑下:

你们团队的自动化、安全性已经到位了吗,效率就真的完全没有上升空间了?

前端的重视程度需要你我一并努力,在大公司做前端难,在小公司做前端更难,你我共勉,最后补充下为什么回成都,以及回来的一些感受。

从一线城市到二线城市

几年前,怀着想学点新东西的想法,我来到了上海,在这边也确实学到了很多东西,关于学习有几个必要条件:

① 好的项目

② 好的团队

③ 加班总结

很多时候不是一些朋友不努力,但是PV上亿的项目国内真的不多,创业团队这种机会就更少,所以根本没有这种机会,这个时候看人家做的很多优化就是看热闹,似乎懂得了什么,其实什么都没懂。

就算是携程最好的时光,依旧有很多团队没有跟上好时候,比如不在无线的团队,那么无线团队的经验包,如果不是真正好学的人是接收不了的,就算我处于无线团队,native团队中很多经验包都是遗漏状态,而且无论怎么打听都是打听不出来的,就比如native资源更新机制,我就前前后后关注了两年,从各处零碎的得到了很多知识,现在看来依旧不够系统,不可谓不遗憾。

又比如有一次携程全站出现了一个重大BUG,因为是server端的问题,自己当时目光短浅没有去深入,到现在想深入都没有案例了,只能说机会稍纵即逝啊!!

所以就算后来进入百度了解到了fis的好,但是真正想去深入的话怎么都摸不着门道,只能学习一些思想走;这个也是一个框架成型后如果后面的人来维护怎么都维护不好的原因,因为他们找不着精髓,后来的人甚至会喷框架的各种不好,自己却找不出方案。

这个就是一线城市的一大好处:

一线城市,具有好项目(PV过亿),好团队的机会要好很多,只要你努力上进,就能收获很大的经验包。

这就意味着,只要你愿意学,就一定可以收获很大的经验礼包,这里有很多热心的大神,愿意与你讨论,愿意与你分享,而你的分享也会带给你快乐与成就感。

另外,外面的薪酬包也要比二线城市大得多,就我而言,回成都薪酬包只有原来的70%不到,如果这次没回成都的话,待遇应该会有长足的涨幅,但是最后还是选择的归蓉。

关于机会好的伪命题

很多人到一线城市打拼是觉得那里机会好,事实上这是一个伪命题(在我看来),不可否认沿海的机会很多,但真心说不上好。

职场这个东西真心不好说,你能力强需要人赏识你,你上去了还得你的老板上去,你老板上去了,还得你老板的老板也上去了,如果中间有一个老板失势了,那么新的老板可能会洗牌,情况好的话你留下,并且依旧令人尊敬,但是更多的情况是可能你得卷铺盖走人,互联网离职率高不是开玩笑的。

能力强不等于职位高,因为一般来说你的能力还没有那么强,能玩转公司的毕竟是少数,搞技术的更多的情况还是比较实在,想安安静静写代码,很少搞政治斗争,又心高气傲,又比较脆弱,面对不合理的KPI,或者被产品无限制的坑,是非常容易离职的。

外面优秀的人扎堆,所以真的机会只是相对的,你如果抱着学习收获经验包的想法出来会收获很大,如果你经验包已经够了,想更往上发展,那么这个是很难的,因为管理或者交流未必是开发擅长的。

再往上会关注业务发展,关注KPI,需要大量的产品知识,自己甚至会成为产品,这个时候搞技术的时间就会相对变少,但技术不好又很难让技术员工服众,光是技术好又上不去,竟然有点矛盾。

我身边就有几个人出去做CTO,结果团队刚刚上轨道就被扫地出门了……

另外,就是外面leader很辛苦,特别是团队上升期,好的leader会陪着团队加班,会很晚,我们之前好的老板(35岁左右)都是陪着团队加班的,总之钱不好赚,如果leader偷懒,几年下来可能会废掉,被淘汰掉,这个中年危机是不可忽视的。

虽然如此,一线城市的机会和成功的几率依旧比二线城市好得多,而你愿意拼搏,善于思考,是可以发展的很好的,那么我们为什么要去二线城市呢?

为什么去二线

去二线城市主要原因就是房价贵,房价太贵,同样的钱可以在成都买三套房子了,而且一线城市买的房子很偏远,上班得一个多小时,下班又得一个多小时,这样来说是相当令人疲惫的。

然后外面工作节奏太紧张,甚至已经紧张到没有生活了,有追求的人995是常态,996也是有可能的,这个会不断的透支你的身体,年轻点还好,稍微上点年纪,有了家庭的话就不行了,会考虑的更多了。

比如在一线城市落户的条件比较苛刻,小孩教育问题、父母养老问题都是你需要考虑的,想着想着,就会有一个想法,要不回老家算了,又不是找不到工作,于是就回来了。。。。。。

回成都后的感觉

回成都后,第二天便出差去北京,整个清明节都在加班,完了回成都也一直在加班,尼玛这边的工作强度一点也不比一线城市低啊!!!

但是,周末可以过得很惬意,一个月老妈也会来一次做些好吃的给我们吃,这种幸福感和归属感是外面没有的。

今天,我8点就下班,在软件园区信步而走,走了10多分钟,然后坐着公交车20分钟回家了,这种感觉还是很不错的。。。。。。

二线城市依旧会面临外面的各种挑战,不想面对依旧上不去,在哪都是面对,那就回老家面对咯。。。。。。

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

Java堆内存

来源: 朱小厮

链接: http://blog.csdn.net/u013256816/article/details/50764532

Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。

在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。

这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。

堆的内存模型大致为:

新生代:Young Generation,主要用来存放新生的对象。

老年代:Old Generation或者称作Tenured Generation,主要存放应用程序声明周期长的内存对象。

永久代:(方法区,不属于java堆,另一个别名为“非堆Non-Heap”但是一般查看PrintGCDetails都会带上PermGen区)是指内存的永久保存区域,主要存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域. 它和和存放Instance的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的应用会加载很多Class的话,就很可能出现PermGen space错误。

堆大小 = 新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。

默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。

默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。

JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。

因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。

回收方法区(附加补充)

很多人认为方法区(或者HotSpot虚拟机中的永久代[PermGen])是没有垃圾收集的,java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾收集,而且在方法去中进行垃圾收集的“性价比”一般比较低:在堆中,尤其是在新生代中,常规应用进行一次垃圾收集一般可以回收70%-95%的空间,而永久代的垃圾收集效率远低于此。

永久代的垃圾收集主要回收两部分内容:废弃的常量和无用的类。

1. 废弃的常量:回收废弃常量与回收java堆中的对象非常类似。以常量池字面量的回收为例,加入一个字符串“abc”已经进入了常量池中,但是当前系统没有任何一个String对象是叫做”abc”的,换句话说,就是有任何String对象应用常量池中的”abc”常量,也没有其他地方引用了这个字面量,如果这时发生内存回收,而且必要的话,这个“abc”常量就会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。(注:jdk1.7及其之后的版本已经将常量池从永久代中移出)

2. 无用的类:类需要同时满足下面3个条件才能算是“无用的类”:

  • 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是”可以“,而并不和对象一样,不使用了就必然会回收。是否对类进行回收,HotSpot虚拟机提供了-Xnoclassgc(关闭CLASS的垃圾回收功能,就是虚拟机加载的类,即便是不使用,没有实例也不会回收。)参数进行控制。

在大量使用反射、动态代理、CGlib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

GC

Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:Minor GC、Full GC ( 或称为 Major GC )。

Minor GC 是发生在新生代中的垃圾收集动作,所采用的是复制算法。

新生代几乎是所有 Java 对象出生的地方,即 Java 对象申请的内存以及存放都是在这个地方。Java 中的大部分对象通常不需长久存活,具有朝生夕灭的性质。

当一个对象被判定为 “死亡” 的时候,GC 就有责任来回收掉这部分对象的内存空间。新生代是 GC 收集垃圾的频繁区域。

当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。

但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制(新生代采用复制算法收集内存)。

为了能够更好的适应不同的程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。

Full GC 是发生在老年代的垃圾收集动作,所采用的是“标记-清除”或者“标记-整理”算法。

现实的生活中,老年代的人通常会比新生代的人 “早死”。堆内存中的老年代(Old)不同于这个,老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉” 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。

在发生MinorGC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么MinorGC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试这进行一次MinorGC,尽管这次MinorGC是有风险的;如果小于,或者HandlePromptionFailure设置不允许冒险,那这是也要改为进行一次FullGC.

另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。

GC日志

首先看一下如下代码:

package jvm;

 

public class PrintGCDetails

{

public static void main(String[] args)

{

Object obj = new Object();

System.gc();

System.out.println();

obj = new Object();

obj = new Object();

System.gc();

System.out.println();

}

}

设置JVM参数为-XX:+PrintGCDetails,执行结果如下:

[GC [PSYoungGen: 1019K->568K(28672K)] 1019K->568K(92672K), 0.0529244 secs] [Times: user=0.00 sys=0.00, real=0.06 secs]

{博主自定义注解:[GC [新生代: MinorGC前新生代内存使用->MinorGC后新生代内存使用(新生代总的内存大小)] MinorGC前JVM堆内存使用的大小->MinorGC后JVM堆内存使用的大小(堆的可用内存大小), MinorGC总耗时] [Times: 用户耗时=0.00 系统耗时=0.00, 实际耗时=0.06 secs] }

[Full GC [PSYoungGen: 568K->0K(28672K)] [ParOldGen: 0K->478K(64000K)] 568K->478K(92672K) [PSPermGen: 2484K->2483K(21504K)], 0.0178331 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]

{博主自定义注解:[Full GC [PSYoungGen: 568K->0K(28672K)] [老年代: FullGC前老年代内存使用->FullGC后老年代内存使用(老年代总的内存大小)] FullGC前JVM堆内存使用的大小->FullGC后JVM堆内存使用的大小(堆的可用内存大小) [永久代: 2484K->2483K(21504K)], 0.0178331 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]}

 

[GC [PSYoungGen: 501K->64K(28672K)] 980K->542K(92672K), 0.0005080 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[Full GC [PSYoungGen: 64K->0K(28672K)] [ParOldGen: 478K->479K(64000K)] 542K->479K(92672K) [PSPermGen: 2483K->2483K(21504K)], 0.0133836 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]

 

Heap

PSYoungGen      total 28672K, used 1505K [0x00000000e0a00000, 0x00000000e2980000, 0x0000000100000000)

eden space 25088K, 6% used [0x00000000e0a00000,0x00000000e0b78690,0x00000000e2280000)

from space 3584K, 0% used [0x00000000e2600000,0x00000000e2600000,0x00000000e2980000)

to   space 3584K, 0% used [0x00000000e2280000,0x00000000e2280000,0x00000000e2600000)

ParOldGen       total 64000K, used 479K [0x00000000a1e00000, 0x00000000a5c80000, 0x00000000e0a00000)

object space 64000K, 0% used [0x00000000a1e00000,0x00000000a1e77d18,0x00000000a5c80000)

PSPermGen       total 21504K, used 2492K [0x000000009cc00000, 0x000000009e100000, 0x00000000a1e00000)

object space 21504K, 11% used [0x000000009cc00000,0x000000009ce6f2d0,0x000000009e100000)

注:你可以用JConsole或者Runtime.getRuntime().maxMemory(),Runtime.getRuntime().totalMemory(), Runtime.getRuntime().freeMemory()来查看Java中堆内存的大小。

再看一个例子:

package jvm;

public class PrintGCDetails2

{

/**

* -Xms60m -Xmx60m -Xmn20m -XX:NewRatio=2 ( 若 Xms = Xmx, 并且设定了 Xmn,

* 那么该项配置就不需要配置了 ) -XX:SurvivorRatio=8 -XX:PermSize=30m -XX:MaxPermSize=30m

* -XX:+PrintGCDetails

*/

public static void main(String[] args)

{

new PrintGCDetails2().doTest();

}

 

public void doTest()

{

Integer M = new Integer(1024 * 1024 * 1); // 单位, 兆(M)

byte[] bytes = new byte[1 * M]; // 申请 1M 大小的内存空间

bytes = null; // 断开引用链

System.gc(); // 通知 GC 收集垃圾

System.out.println();

bytes = new byte[1 * M]; // 重新申请 1M 大小的内存空间

bytes = new byte[1 * M]; // 再次申请 1M 大小的内存空间

System.gc();

System.out.println();

}

}

运行结果:

[GC [PSYoungGen: 2007K->568K(18432K)] 2007K->568K(59392K), 0.0059377 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]

[Full GC [PSYoungGen: 568K->0K(18432K)] [ParOldGen: 0K->479K(40960K)] 568K->479K(59392K) [PSPermGen: 2484K->2483K(30720K)], 0.0223249 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]

 

[GC [PSYoungGen: 3031K->1056K(18432K)] 3510K->1535K(59392K), 0.0140169 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]

[Full GC [PSYoungGen: 1056K->0K(18432K)] [ParOldGen: 479K->1503K(40960K)] 1535K->1503K(59392K) [PSPermGen: 2486K->2486K(30720K)], 0.0119497 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]

 

Heap

PSYoungGen      total 18432K, used 163K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)

eden space 16384K, 1% used [0x00000000fec00000,0x00000000fec28ff0,0x00000000ffc00000)

from space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)

to   space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)

ParOldGen       total 40960K, used 1503K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)

object space 40960K, 3% used [0x00000000fc400000,0x00000000fc577e10,0x00000000fec00000)

PSPermGen       total 30720K, used 2493K [0x00000000fa600000, 0x00000000fc400000, 0x00000000fc400000)

object space 30720K, 8% used [0x00000000fa600000,0x00000000fa86f4f0,0x00000000fc400000)

从打印结果可以看出,堆中新生代的内存空间为 18432K ( 约 18M ),eden 的内存空间为 16384K ( 约 16M),from / to survivor 的内存空间为 2048K ( 约 2M)。

这里所配置的 Xmn 为 20M,也就是指定了新生代的内存空间为 20M,可是从打印的堆信息来看,新生代怎么就只有 18M 呢? 另外的 2M 哪里去了?

别急,是这样的。新生代 = eden + from + to = 16 + 2 + 2 = 20M,可见新生代的内存空间确实是按 Xmn 参数分配得到的。

而且这里指定了 SurvivorRatio = 8,因此,eden = 8/10 的新生代空间 = 8/10 * 20 = 16M。from = to = 1/10 的新生代空间 = 1/10 * 20 = 2M。

堆信息中新生代的 total 18432K 是这样来的: eden + 1 个 survivor = 16384K + 2048K = 18432K,即约为 18M。

因为 jvm 每次只是用新生代中的 eden 和 一个 survivor,因此新生代实际的可用内存空间大小为所指定的 90%。

因此可以知道,这里新生代的内存空间指的是新生代可用的总的内存空间,而不是指整个新生代的空间大小。

另外,可以看出老年代的内存空间为 40960K ( 约 40M ),堆大小 = 新生代 + 老年代。因此在这里,老年代 = 堆大小 – 新生代 = 60 – 20 = 40M。

最后,这里还指定了 PermSize = 30m,PermGen 即永久代 ( 方法区 ),它还有一个名字,叫非堆,主要用来存储由 jvm 加载的类文件信息、常量、静态变量等。

附:JVM常用参数

-XX:+<option> 启用选项

-XX:-<option>不启用选项

-XX:<option>=<number>

-XX:<option>=<string>

堆设置

-Xms :初始堆大小

-Xmx :最大堆大小

-Xmn:新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90%

-XX:NewSize=n :设置年轻代大小

-XX:NewRatio=n: 设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4

-XX:SurvivorRatio=n :年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5

-XX:PermSize=n 永久代(方法区)的初始大小

-XX:MaxPermSize=n :设置永久代大小

-Xss 设定栈容量;对于HotSpot来说,虽然-Xoss参数(设置本地方法栈大小)存在,但实际上是无效的,因为在HotSpot中并不区分虚拟机和本地方法栈。

-XX:PretenureSizeThreshold (该设置只对Serial和ParNew收集器生效) 可以设置进入老生代的大小限制

-XX:MaxTenuringThreshold=1(默认15)垃圾最大年龄 如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率

该参数只有在串行GC时才有效.

收集器设置

-XX:+UseSerialGC :设置串行收集器

-XX:+UseParallelGC :设置并行收集器

-XX:+UseParallelOldGC :设置并行年老代收集器

-XX:+UseConcMarkSweepGC :设置并发收集器

垃圾回收统计信息

-XX:+PrintHeapAtGC GC的heap详情

-XX:+PrintGCDetails GC详情

-XX:+PrintGCTimeStamps 打印GC时间信息

-XX:+PrintTenuringDistribution 打印年龄信息等

-XX:+HandlePromotionFailure 老年代分配担保(true or false)

-Xloggc:gc.log 指定日志的位置

并行收集器设置

-XX:ParallelGCThreads=n :设置并行收集器收集时使用的CPU数。并行收集线程数。

-XX:MaxGCPauseMillis=n :设置并行收集最大暂停时间

-XX:GCTimeRatio=n :设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

并发收集器设置

-XX:+CMSIncrementalMode :设置为增量模式。适用于单CPU情况。

-XX:ParallelGCThreads=n :设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

其他

-XX:PermSize=10M和-XX:MaxPermSize=10M限制方法区大小。

-XX:MaxDirectMemorySize=10M指定DirectMemory(直接内存)容量,如果不指定,则默认与JAVA堆最大值(-Xmx指定)一样。

-XX:+HeapDumpOnOutOfMemoryError 可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照(.hprof文件)以便时候进行分析(比如Eclipse Memory Analysis)。

参考资料:

1. 《java 虚拟机–新生代与老年代GC》

2. 《Java 堆内存》

3. 《深入理解Java虚拟机》周志明著

Java内存模型

英文:Jakob Jenkov

译文:张坤

链接:http://ifeve.com/java-memory-model-6/

 

Java内存模型规范了Java虚拟机与计算机内存是如何协同工作的。Java虚拟机是一个完整的计算机的一个模型,因此这个模型自然也包含一个内存模型——又称为Java内存模型。

如果你想设计表现良好的并发程序,理解Java内存模型是非常重要的。Java内存模型规定了如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。

原始的Java内存模型存在一些不足,因此Java内存模型在Java1.5时被重新修订。这个版本的Java内存模型在Java8中人在使用。

Java内存模型内部原理

Java内存模型把Java虚拟机内部划分为线程栈和堆。这张图演示了Java内存模型的逻辑视图。

Java Memory Model

每一个运行在Java虚拟机里的线程都拥有自己的线程栈。这个线程栈包含了这个线程调用的方法当前执行点相关的信息。一个线程仅能访问自己的线程 栈。一个线程创建的本地变量对其它线程不可见,仅自己可见。即使两个线程执行同样的代码,这两个线程任然在在自己的线程栈中的代码来创建本地变量。因此, 每个线程拥有每个本地变量的独有版本。

所有原始类型的本地变量都存放在线程栈上,因此对其它线程不可见。一个线程可能向另一个线程传递一个原始类型变量的拷贝,但是它不能共享这个原始类型变量自身。

堆上包含在Java程序中创建的所有对象,无论是哪一个对象创建的。这包括原始类型的对象版本。如果一个对象被创建然后赋值给一个局部变量,或者用来作为另一个对象的成员变量,这个对象任然是存放在堆上。

下面这张图演示了调用栈和本地变量存放在线程栈上,对象存放在堆上。

enter image description here

一个本地变量可能是原始类型,在这种情况下,它总是“呆在”线程栈上。

一个本地变量也可能是指向一个对象的一个引用。在这种情况下,引用(这个本地变量)存放在线程栈上,但是对象本身存放在堆上。

一个对象可能包含方法,这些方法可能包含本地变量。这些本地变量任然存放在线程栈上,即使这些方法所属的对象存放在堆上。

一个对象的成员变量可能随着这个对象自身存放在堆上。不管这个成员变量是原始类型还是引用类型。

静态成员变量跟随着类定义一起也存放在堆上。

存放在堆上的对象可以被所有持有对这个对象引用的线程访问。当一个线程可以访问一个对象时,它也可以访问这个对象的成员变量。如果两个线程同时调用同一个对象上的同一个方法,它们将会都访问这个对象的成员变量,但是每一个线程都拥有这个本地变量的私有拷贝。

下图演示了上面提到的点:

enter image description here

两个线程拥有一些列的本地变量。其中一个本地变量(Local Variable 2)执行堆上的一个共享对象(Object 3)。这两个线程分别拥有同一个对象的不同引用。这些引用都是本地变量,因此存放在各自线程的线程栈上。这两个不同的引用指向堆上同一个对象。

注意,这个共享对象(Object 3)持有Object2和Object4一个引用作为其成员变量(如图中Object3指向Object2和Object4的箭头)。通过在Object3中这些成员变量引用,这两个线程就可以访问Object2和Object4。

这张图也展示了指向堆上两个不同对象的一个本地变量。在这种情况下,指向两个不同对象的引用不是同一个对象。理论上,两个线程都可以访问Object1和Object5,如果两个线程都拥有两个对象的引用。但是在上图中,每一个线程仅有一个引用指向两个对象其中之一。

因此,什么类型的Java代码会导致上面的内存图呢?如下所示:

public class MyRunnable implements Runnable() {

    public void run() {
        methodOne();
    }

    public void methodOne() {
        int localVariable1 = 45;

        MySharedObject localVariable2 =
            MySharedObject.sharedInstance;

        //... do more with local variables.

        methodTwo();
    }

    public void methodTwo() {
        Integer localVariable1 = new Integer(99);

        //... do more with local variable.
    }
}


public class MySharedObject {

    //static variable pointing to instance of MySharedObject

    public static final MySharedObject sharedInstance =
        new MySharedObject();


    //member variables pointing to two objects on the heap

    public Integer object2 = new Integer(22);
    public Integer object4 = new Integer(44);

    public long member1 = 12345;
    public long member1 = 67890;
}

如果两个线程同时执行run()方法,就会出现上图所示的情景。run()方法调用methodOne()方法,methodOne()调用methodTwo()方法。

methodOne()声明了一个原始类型的本地变量和一个引用类型的本地变量。

每个线程执行methodOne()都会在它们对应的线程栈上创建localVariable1localVariable2的私有拷贝。localVariable1变量彼此完全独立,仅“生活”在每个线程的线程栈上。一个线程看不到另一个线程对它的localVariable1私有拷贝做出的修改。

每个线程执行methodOne()时也将会创建它们各自的localVariable2拷贝。然而,两个localVariable2的不同拷贝都指向堆上的同一个对象。代码中通过一个静态变量设置localVariable2指向一个对象引用。仅存在一个静态变量的一份拷贝,这份拷贝存放在堆上。因此,localVariable2的两份拷贝都指向由MySharedObject指向的静态变量的同一个实例。MySharedObject实例也存放在堆上。它对应于上图中的Object3。

注意,MySharedObject类也包含两个成员变量。这些成员变量随着这个对象存放在堆上。这两个成员变量指向另外两个Integer对象。这些Integer对象对应于上图中的Object2和Object4.

注意,methodTwo()创建一个名为localVariable的本地变量。这个成员变量是一个指向一个Integer对象的对象引用。这个方法设置localVariable1引用指向一个新的Integer实例。在执行methodTwo方法时,localVariable1引用将会在每个线程中存放一份拷贝。这两个Integer对象实例化将会被存储堆上,但是每次执行这个方法时,这个方法都会创建一个新的Integer对象,两个线程执行这个方法将会创建两个不同的Integer实例。methodTwo方法创建的Integer对象对应于上图中的Object1和Object5。

还有一点,MySharedObject类中的两个long类型的成员变量是原始类型的。因为,这些变量是成员变量,所以它们任然随着该对象存放在堆上,仅有本地变量存放在线程栈上。

硬件内存架构

现代硬件内存模型与Java内存模型有一些不同。理解内存模型架构以及Java内存模型如何与它协同工作也是非常重要的。这部分描述了通用的硬件内存架构,下面的部分将会描述Java内存是如何与它“联手”工作的。

下面是现代计算机硬件架构的简单图示:

enter image description here

一个现代计算机通常由两个或者多个CPU。其中一些CPU还有多核。从这一点可以看出,在一个有两个或者多个CPU的现代计算机上同时运行多个线程 是可能的。每个CPU在某一时刻运行一个线程是没有问题的。这意味着,如果你的Java程序是多线程的,在你的Java程序中每个CPU上一个线程可能同 时(并发)执行。

每个CPU都包含一系列的寄存器,它们是CPU内内存的基础。CPU在寄存器上执行操作的速度远大于在主存上执行的速度。这是因为CPU访问寄存器的速度远大于主存。

每个CPU可能还有一个CPU缓存层。实际上,绝大多数的现代CPU都有一定大小的缓存层。CPU访问缓存层的速度快于访问主存的速度,但通常比访 问内部寄存器的速度还要慢一点。一些CPU还有多层缓存,但这些对理解Java内存模型如何和内存交互不是那么重要。只要知道CPU中可以有一个缓存层就 可以了。

一个计算机还包含一个主存。所有的CPU都可以访问主存。主存通常比CPU中的缓存大得多。

通常情况下,当一个CPU需要读取主存时,它会将主存的部分读到CPU缓存中。它甚至可能将缓存中的部分内容读到它的内部寄存器中,然后在寄存器中执行操作。当CPU需要将结果写回到主存中去时,它会将内部寄存器的值刷新到缓存中,然后在某个时间点将值刷新回主存。

当CPU需要在缓存层存放一些东西的时候,存放在缓存中的内容通常会被刷新回主存。CPU缓存可以在某一时刻将数据局部写到它的内存中,和在某一时 刻局部刷新它的内存。它不会再某一时刻读/写整个缓存。通常,在一个被称作“cache lines”的更小的内存块中缓存被更新。一个或者多个缓存行可能被读到缓存,一个或者多个缓存行可能再被刷新回主存。

Java内存模型和硬件内存架构之间的桥接

上面已经提到,Java内存模型与硬件内存架构之间存在差异。硬件内存架构没有区分线程栈和堆。对于硬件,所有的线程栈和堆都分布在主内中。部分线程栈和堆可能有时候会出现在CPU缓存中和CPU内部的寄存器中。如下图所示:

enter image description here

当对象和变量被存放在计算机中各种不同的内存区域中时,就可能会出现一些具体的问题。主要包括如下两个方面:

-线程对共享变量修改的可见性
-当读,写和检查共享变量时出现race conditions

下面我们专门来解释以下这两个问题。

共享对象可见性

如果两个或者更多的线程在没有正确的使用volatile声明或者同步的情况下共享一个对象,一个线程更新这个共享对象可能对其它线程来说是不接见的。

想象一下,共享对象被初始化在主存中。跑在CPU上的一个线程将这个共享对象读到CPU缓存中。然后修改了这个对象。只要CPU缓存没有被刷新会主 存,对象修改后的版本对跑在其它CPU上的线程都是不可见的。这种方式可能导致每个线程拥有这个共享对象的私有拷贝,每个拷贝停留在不同的CPU缓存中。

下图示意了这种情形。跑在左边CPU的线程拷贝这个共享对象到它的CPU缓存中,然后将count变量的值修改为2。这个修改对跑在右边CPU上的其它线程是不可见的,因为修改后的count的值还没有被刷新回主存中去。

enter image description here

解决这个问题你可以使用Java中的volatile关键字。volatile关键字可以保证直接从主存中读取一个变量,如果这个变量被修改后,总是会被写回到主存中去。

Race Conditions

如果两个或者更多的线程共享一个对象,多个线程在这个共享对象上更新变量,就有可能发生race conditions

想象一下,如果线程A读一个共享对象的变量count到它的CPU缓存中。再想象一下,线程B也做了同样的事情,但是往一个不同的CPU缓存中。现在线程A将count加1,线程B也做了同样的事情。现在count已经被增在了两个,每个CPU缓存中一次。

如果这些增加操作被顺序的执行,变量count应该被增加两次,然后原值+2被写回到主存中去。

然而,两次增加都是在没有适当的同步下并发执行的。无论是线程A还是线程B将count修改后的版本写回到主存中取,修改后的值仅会被原值大1,尽管增加了两次。

下图演示了上面描述的情况:

enter image description here

解决这个问题可以使用Java同步块。一个同步块可以保证在同一时刻仅有一个线程可以进入代码的临界区。同步块还可以保证代码块中所有被访问的变量将会从主存中读入,当线程退出同步代码块时,所有被更新的变量都会被刷新回主存中去,不管这个变量是否被声明为volatile。

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: Java内存模型

 

【重磅】Google开源全球最精准自然语言解析器SyntaxNet

来源:Google Research

编译:胡祥杰  朱焕

 

【新智元导读】Google Research今天宣布,世界准确度最高的自然语言解析器SyntaxNet开源。谷歌开源再进一步。据介绍,谷歌在该平台上训练的模型的语言理解准确率超过90%。近日,众多科技巨头人工智能相关平台开源步伐明显加快:谷歌和Facebook一直在领跑,马斯克的OpenAI欲打造一个完全公开的AI模型训练营,就连一直被批评“保守”的亚马逊也在尝试开源。这一股开源热潮背后,是人工智能研究者的福利,但同时也是一场激烈的数据和平台争夺战。

Google环境计算( Ambient  computing) 架构师Yonatan Zunger说:事实上,语言理解被我们认为是“AI的终极任务”,要解决这一难题,前提是要能解决全部人类水平人工智能的问题。

机 器对语言的理解过程,可以分为几个步骤,其中很多的不确定性是逐渐明晰的(语音识别的不确定性更多,因为还要解决从声音到词的转换)。第一步是要把词分 开,放到依存树上,看哪一个词是动词,对名词有哪些影响等等。随后,要理解每一个名字的含义。再次,再加入许多先验知识,即对这个世界的理解,因为很多句 子只有使用了这些信息才能真正理解。如果足够幸运的话,到这就能得到清晰的理解了。

谷歌资深研究科学家Slav Petrov在Google Research的博客上写到:在谷歌,我们花费了大量的时间在思考,计算机系统如何才能阅读和理解人类语言,以一种更加智能的方式处理这些语言?今天,我们激动地跟大家分享我们的研究,向更广阔的人群发布SyntaxNet。这是一个在TensoFlow中运行的开源神经网络框架,提供自然语言理解系统基础。我们所公开的包含了所有用你自己的数据训练新的SyntaxNet模型所需要的代码,以及Paesey  McParseface——我们已经训练好的,可用于分析英语文本的模型。

Paesey  McParseface 建立于强大的机器学习算法,可以学会分析句子的语言结构,能解释特定句子中每一个词的功能。此类模型中,Paesey  McParseface是世界上最精确的,我们希望他能帮助对自动提取信息、翻译和其它自然语言理解(NLU)中的应用感兴趣的研究者和开放者。

SyntaxNet是怎么工作的?

SyntaxNet是一个框架,即学术圈所指的SyntacticParser,他是许多NLU系统中的关键组件。在这个系统中输入一个句子,他会自动给句子中的每一个单词打上POS(part-of-Speech)标签,用来描述这些词的句法功能,并在依存句法树中呈现。这些句法关系直接涉及句子的潜在含义。

举一个很简单的例子,看下面这个句子“Alice saw Bob”的依存句法树:

在这个结构中,Alice和Bob被编码为名词,Saw是动词。只要的动词saw 是句子的根,Alice是saw的主语,Bob是直接宾语(dobj)。和期待的一样,Paesey  McParseface能正确地分析这一句子,也能理解下面这个更加复杂的例子:

句子:Alice, who had been reading about SynataxNet, saw Bob in the hallwayyesterday

在这个句子的编码中,Alice 和 Bob的分别是saw的主语和宾语,Alice由一个带动词“reading”的关系从句来修饰,而saw则由时态“yesterday”来修饰。依存句法树中的语法关系让我们可以轻易地找到不同问题的答案,比如,Alice看见了谁?谁看到了Bob?Alice正在读的是什么?或者Alice是在什么时候看到Bob的。

为什么让计算机正确处理句法分析如此困难?

 

句法分析如此困难的一个主要问题是,人类语言具有显著的歧义性。包含 20 到 30 个单词的中等长度的句子会具有数百、数千甚至数万种可能的句法结构,这样的情况并不少见。一个自然语言句法分析器必须能够搜索所有这些结构选择,并找到给定语境下最合理的那个结构。作为一个非常简单的例子,“Alice drove down the streetin her car”这个句子就具有至少两种可能的依存分析:

第一种分析是对应这句话的(正确)解释,按照这种解释,爱丽丝在汽车里进行驾驶,而汽车位于街道上;第二种分析对应于一种对这句话的(荒诞但仍然可能的)解释,按照这种解释,爱丽丝在街道上驾驶,而街道位于汽车之内。之所以会产生这种歧义,是因为“in”这个介词既可以用来修饰“drove(驾驶)”也可以用来修饰“street(街道)”。上面这个例子是所谓的“介词短语附着歧义”的一个实例。

 

人类在处理歧义方面有超强的能力,以至于人们甚至注意不到句子有歧义。而这里的挑战是,如何能让计算机做到同样好。长句中的多重歧义会共同造成句子的可能结构数量的组合爆炸。通常,这些结构中的绝大多数都极其不合理,但它们仍然是可能的,句法分析器必须以某种方式来丢弃它们。

 

SyntaxNet 将神经网络运用于歧义问题。一个输入句子被从左到右地处理。当句子中的每个词被处理时,词与词之间的依存关系也会被逐步地添加进来。由于歧义的存在,在处理过程的每个时间点上都存在多种可能的决策,而神经网络会基于这些决策的合理性向这些彼此竞争的决策分配分数。出于这一原因,在该模型中使用 Beam Search (集束搜索)就变得十分重要。不是直接取每个时间点上的最优决定,而是在每一步都保留多个部分性假设。只有当存在多个得分更高的假设的时候,一个假设才会被抛弃。下图将展示的,是“I booked a ticket to Google”这句话经过从左到右的决策过程而产生的简单句法分析。

而且,正如我们在论文中所描述的,十分重要的一点是,要把学习和搜索紧密整合起来才能取得最高的预测准确度。Parsey McParseface 和其他 SyntaxNet 模型是我们用谷歌的 TensorFlow 框架训练过的最复杂的网络结构。通过利用谷歌支持的 Universal Treebanks 项目中的数据,你也可以在自己的机器上训练句法分析模型。

 

 Parsey McParseface 的准确度到底有多高?

 

在(从具有二十年历史的宾大树库Penn Treebank中)随机抽取的英语新闻句子构成的标准测试中,Parsey McParseface 在提取词之间的个体依存关系时的准确率超过 94%,这打败了我们自己先前的最高水平,也超过了任何以前的方法。尽管在文献中并没有关于人类的句法分析成绩的明确研究,我们从我们内部的句法标注项目中了解到,那些在该任务上受过训练的语言学家在 96-97% 的情况下能达成一致。这说明,我们正在接近人类的水平——不过这仍然限于那些格式良好的文本。按照我们从 Google WebTreebank (谷歌网络树库,发布于 2011 年)中所学到的,那些从互联网上获得的句子要远远更难分析。在该网络数据集上,Parsey McParseface 只取得了略高于 90% 的句法分析准确率。

 

尽管准确率还不够完美,它已经足够高,能够用于许多应用程序了。目前,错误的主要来源是像上面描述过的介词短语附着歧义这样的情况,对这些情况的处理要求对现实世界的知识(例如,“街道不太可能位于汽车之内”)和深度语境推理。机器学习(特别是神经网络)已在解决这些歧义方面取得了显著的进展。不过我们仍想做进一步的工作:我们想要发展出一些方法,这些方法能够学习现实世界知识,也能够在所有语言和语境中都取得同样好的自然语言理解。

 

想试试吗,请阅读 SyntaxNet 的代码。并下载 Parsey McParseface 句法分析模型。主要研发者Chris Alberti, David Weiss, Daniel Andor, Michael Collins 和 Slav Petrov 祝你成功。

数据狂人必备的10本全球畅销书

2016-05-13 丑灿 大数据技术

来自:化学数据联盟

对于一位数据科学的狂热粉丝而言,可供选择阅读的书籍内容很多,包括大数据、机器学习、数据科学以及数据挖掘等等。除了这些技术范围内的书籍之外,也有很多工具类和语言类的书籍,比如Hadoop、Spark、Python和R语言等。关于数据的书籍和专题时常更新,所以只有你掌握了最新的信息才可以让你掌握这个领域内最先进的技术和技能。幸运的是(或者也可以称之为不幸?)关于数据科学与技术领域的各种专题从来不会缺少响应的书籍,所以你大可放心随便选取。

和数据有关的数据有很多具体的类目,而且每种类目都有很多畅销书列表可供你参考。实际上我们最近为读者列举了很多书籍列表,比如关于数据挖掘、数据库与大数据、统计学、AI和机器学习以及神经网络。但是以上这些枚举的列表都是根据Amazon书籍畅销榜的比较狭隘的分类得出的推荐结论,而且没有这些书籍缺少编辑决定权或者没有考虑到内容是否可以免费获取以及是否有电子书的格式。

首先,让我们把一个问题弄清楚:本文的标题有些误导性。为数据狂人(或者专业人员)推荐的必备书籍列表中的内容比较泛泛,我们向读者推荐的这10种类别的书籍当中,每一种类别的书籍内容都是与付费资源和免费资源有关的畅销书籍。尽管我们的工作内容涉及到的数据通道数量有限,但是我们通常情况下会想主动去了解比现有数据通道更加庞大的数量,你的初衷可能是为了工作的实践操作也可能是出于兴趣的目的。

所以,一位Hadoop专业人士可能不会对深度学习进行更加专业水准的洞察,他们只不过是对某些专题感兴趣。这篇文章可以让读者巩固自己的兴趣,并为那些想拓宽个人知识层面的数据狂人提供具体的建议。

需要大家注意的是,这些数据类目所涉及的内容有所重叠,这种情况无法避免。通常情况下书籍内容所专注的领域决定了他属于哪个类目。

01.数据科学DISCOVERY

畅销付费书籍推荐:

Data Science for Business

《商业数据科学-关于数据挖掘和数据分析思维你需要知道的一切》

当你尝试学习新的领域的时候,最常见的难题就是找到一本内容深度正合适的书籍。读者要么因为内容过于简单或者内容过于说教的学术性质而对买到的书籍“始乱终弃”,尽管书籍内容具有权威性和综合性,但是最终还是被读者放置于书架之上与尘埃为伴。但是《商业数据科学》这本书却一针见血,恰到好处。

——来自Amazon用户m I的读后感


畅销免费书籍推荐:

The Art of Data Science

《数据科学的艺术之美》

这本书用通俗易懂的词汇向读者描述了分析数据的具体过程。本书的作者不仅在管理数据分析方面拥有丰富经验,并且还能够指导团队进行数据分析。这本书集成了他们所有关于数据分析经验的精华,并通过较强的可应用性向数据科学的专业人士和管理者介绍了他们的经验。

——来自官方网站的书评


02.大数据DISCOVERY

畅销付费书籍推荐:

《大数据:可扩展的实时数据系统的原则和最佳实践》

Big Data:Principles and Best Practices of Scalable Realtime Data Systems

我几乎很少能有幸遇见这样一本优质的书籍。这本书对于数据建模、数据分析、数据处理需求分析以及数据架构和储存实施问题(还同时稍带了传动的数据库概念的介绍)的重要性进行了详尽而周全的讨论。这本书向广大读者传递了新鲜的整体解决方案。

——来自Amazon用户Kirk D. Borne的读后感


畅销免费书籍:

《大数据即刻出发:2015版》

 Big Data Now: 2015 Edition

在O’Reilly发布一年一度的《大数据即刻出发》年度报告的四年时间内,大数据领域已经从呱呱坠地的婴儿成长为年轻气盛的青少年。数据已经成为一些行业的领军人,而在其他行里中数据已经成为创新的驱动力。那些使用数据极其分析来制定决策的公司正在突飞猛进的超越同行的竞争对手。

——来自官方网站的书评


03.Apache HadoopDISCOVERY

畅销付费书籍推荐:

《Hadoop:通用指南》

Hadoop: The Definitive Guide

我很欣赏的一点就是这本书对你所需要了解的Hadoop设计、执行以及日常运行以及与之相关的各种技术,不仅涵盖了高层次的概念并且对技术细节的解释也非常接地气。

——来自Amazon用户AI Gordon的读后感


畅销免费书籍推荐:

Hadoop Explained

Hadoop对于那些建造在数据基础上的世界而言是至关重要的一种技术工具。在这本书中你可以发现很多有用的指导性意见,你可以发现他处理大数据挑战的手段的发展和进步。

——来自官方网站的书评


04.Apache SparkDISCOVERY

畅销付费书籍推荐:

《学习Spark》

 Learning Spark

互联网上可以获取的信息非常棒,但是这本书把大部分这些信息整合到了一处。如果你想学着像一名Spark编程人员一样思考问题,而不是单纯的像程序员一样思考问题,那么从这本书开始,作为Spark用户的你,你的思维方式即将开始发生改变。

——来自Amazon用户BrianCastelli的读后感


畅销免费书籍推荐:

《掌握Apache Spark》

Mastering Apache Spark

这本书是我们收集关于使用Apache Spark的各种具体细节的最后一块终极瑰宝。

­——来自官方网站的书评


05.机器学习理论DISCOVERY

畅销付费书籍推荐:

《模式识别与机器学习》

Pattern Recognition and MachineLearning (Information Science and Statistics)

该书的作者为一名专家,因为它可以通过机器学习算法背后隐含的复杂数学体系向读者提供独一无二的见解和领悟。我本人已经从事神经元网络方面的工作很长时间了,并且发表过线性代数、概率和回归分析方面的论文,我发现这本书中的确可以为你找到更多的启发。

——来自Amazon用户Sidhant的读后感


畅销免费书籍推荐:

《统计学习基础》

Elements of Statistical Learning

好消息来了,这将是全世界你读到的一本最重要的书籍。这本书将每一件重要的内容绑定在一起。只在此书,别无它有。

——来自Amazon用户Enceladus Transit的读后感


06.实用机器学习技术DISCOVERY

畅销付费书籍推荐:

《Python机器学习技术》

Python MachineLearning

即使对于像我这样一个机器学习技术新手来说,这本书也相当了不起。在第一次读这本书的时候,我的第一感觉就是无论从广度还是从深度,这本书将理论和实践完整的融合到了一起。

——来自Amazon用户Brian M. Thomas的读后感


畅销免费书籍推荐:

《统计学习在R语言中的应用介绍》

An Introduction to StatisticalLearning with Applications in R

这本书介绍了统计学习的基本方法。这本书主要为非数学科学专业的本科生、研究生以及博士生准备。这本书还包含大量的R实验研究,并详细的解释如何执行各种方法。对于有实践需求的数据科学家而言,这本书的确很有价值。

——来自官网的书评


07.深度学习DISCOVERY

由于目前关于深度学习优质的付费书籍资源非常少,所以这里向大家推荐两本畅销的免费书籍:

畅销免费书籍推荐第一名:

《神经网络与深度学习》

Neural Networks and Deep Learning

这是一本在线免费书籍,这本书可以教会你:

  • 一个绚烂的受生物学启发得到的程序设计范例,可以让计算机从所观察到的数据进行相应内容的学习
  • 深度学习,神经网络中强大的学习技术

——来自官网的书评


畅销免费书籍推荐第二名:

《深度学习》

Deep Learning

这本由Ian Goodfellow,、Yoshua Bengio和Aaron Courville合著的书籍正在筹备阶段,有可能是未来最佳的关于深度学习的书籍。这本书的开发版每月都在更新,在最终出版的时候读者可以免费获取。


08.数据挖掘DISCOVERY

畅销付费书籍推荐:

《据挖掘:概念与技术,第三版(摩根考夫曼数据管理系统系列)》

Data Mining:Concepts and Techniques, Third Edition

数据挖掘是对这个领域的综合性概览,我认为这本书是数据挖掘专业毕业生的绝佳之选,或者也可以做为一本参考书来使用。该书以技术为焦点(比如如何分析数据,包括准备),而且这本书包括了该领域内涉及数据存储和预处理在内的所有主要专题。然而这本书真的是非常好的方法分类资源,在第二章你可以发现非常强大的聚合分析的方法。

——来自Amazon用户SusanKatz的读后感


畅销免费书籍这本书推荐:

《巨型数据库的挖掘》

Mining of Massive Datasets

这本书的设计是在没有正式先决条件的本科计算机科学水平基础上进行。为了满足读者的进一步学习,很多章节都从读者参考的方式进行补充。

——来自官网的书评


09.SQLDISCOVERY

畅销付费书籍推荐:

《SQL 第二版》

Learning SQL,Second Edition

如果你正在编写任何种类的数据库驱动代码并且你认为你不需要懂SQL,你需要读这本书。之后你会发现你需要懂得SQL并且这本书可以很好地辅导你。

——来自Amazon用户Jack D. Herrington的读后感


畅销免费书籍推荐:

《SQL的艰难学习之旅》

Learn SQL The Hard Way

这本书可以教会你80%你所需要使用的SQL语言,同时会将数据建模的理论混杂在其中进行讲解。如果你还在因为无法了解SQL而摸索如何建立网站。桌面系统或者移动应用的话,那么这本书就是为你准备的。这本对那些之前不懂数据库和编程,但是知道至少一种计算机编程语言的人有很大的帮助。

——来自官网的书评

10.数据科学统计学DISCOVERY

畅销付费书籍推荐:

《用白话文解释何为统计学,第三版》

Statistics inPlain English, Third Edition

作为一名数据分析师并且日常工作就是处理统计学数据,我很期待知道更多的算法和模型。尽管统计软件可以我们完成每件事,但是识别出软件咀嚼后得到的结果的确是这个工作最难以拿捏的部分。我主修生物技术专业并且对这些我生命中遇到的大部分的统计学像白痴一样。长话短说,我真的需要这本书帮助我理解更多的统计学概念。

——来自Amazon用户Shyam Goli的读后感


畅销免费书籍推荐:

《Think Stats:程序员需要的概率论统计学,第二版》

Think Stats:Probability and Statistics for Programmers, Second Edition

Think Stats强调了让你使用简单的技术进行数据和有趣问题答案的开发。这本书介绍了美国国家卫生研究院使用数据进行的案例的研究。

——来自官网的书评