年度总结 – 2013

今天是2013年12月31日,再过几个小时,就到了2014年1月1日了。

fce2c11db61f20d680752457a7f6b4a8

2013对于我来说意味着改变很多,尽管本质上的变化不大,但是起码自己有了些突破。

2012年12月31日,我也写了一篇年度总结,在年度总结中提到了几个关键词:

  • 基金
  • 定期储蓄
  • 妹子
  • 用户体验
  • Scala
  • NodeJS
  • ETL

过去的一年中,有几个关键词倒是落实得还行。

  • 我投入了一部分资金到货币基金和股市,以及还有在小杨同学的带领下也尝试了一些P2P的交易
  • 至于对于寻找伴侣这件事情上来说,不能算是有很大的进步,但是至少有一些尝试了。2014年这件事情仍然是一个重点的关键词啊,不过一切还是随缘,自己提高主动性。
  • 后面的四点是围绕技术方面来说的,首先Scala很不幸的没有被我坚持下来;NodeJS也只是在自己有兴趣的范围零零碎碎的进行,也没有取得比较大的进步;最后一个ETL,则是因为参与了项目的进程,基本上整个年度都围绕这个产品来进行,无论从哪个方面来说对自己都是一个不错的变化点。从技术方面来说,我需要变得更专注。

每一年的年度总结都会有一个核心关键词,2012年的核心词汇是纠结,2013年的核心词汇则是改变。

工作的改变

首先工作上最主要的变化是,我换了一份工作:从工作了近8年的合众出来了,到了目前的新东家—蘑菇街。

年初开始参与到ETL项目的进程中去,主导了产品的Web化的过程。在这个过程主要进行的整体框架的建立,前端模块化和组件化的技术引入(比如SeaJS,Draw2d,Eventproxy)。引入这些内容在前期还是遭遇了一些问题,但是后期理顺思路后,这个套路让我觉得用得是越来越顺手了,当然也很感谢团队伙伴们的给力支持,让这个产品能顺利的进行下来了。

11月11日,我入职蘑菇街了。从一家传统的产品型公司到一家新兴的电子商务互联网公司。虽然都是技术活,但是明显可以感觉到两种不同类型公司的运作方式和节奏的差异性。蘑菇街是一家为女性服务的电商公司,所以很明显的感受是妹子真多。尽管刚来不久,但是可以明显得感觉到自己需要学习的东西很多,需要提高的地方也很多。有太多的东西还需要去适应和改变。

技术能力的改变

2013年前十个月,主要是参与ETL项目的管理和开发。从技术方面来说,最大的改变就是进一步提高了自己在前端Javascript开发方面的能力,尽管后来因为离职导致想尝试Web化更彻底的计划也没有去做(不过我相信团队伙伴们做得肯定更好)。在产品架构和风险控制方面也学习了一些经验,这一点对于我来说是成长比较大,因为以前是一个比较理想化的思路。在ETL WEB化的过程,由于需求和时间的一些变化,也让自己逐步学习了一些应变和调整的方法。

我以前特别喜欢对公司的产品进行组合分析,然后自己猜测公司未来可能会走的技术或者业务方向。现在想想,其实也没有考虑这么多,要在其位而谋其政啊。

现在做了一些以前自己关注过的东西,比如Nagios。也接触了以前从未听说过的Puppet。可能未来也还是有很多新的东西等着自己来尝试,所以需要去学习更多,让自己进步的更快一些。

生活的改变

自信了一点,尽管这一点很小,但是对于我来说算是一个蛮大的进步了。

  • 坚持每周至少两次时间在1小时以上的运动,为此办了一张健身年卡。
  • 每月至少去看两次电影,2013年记忆深刻的电影是一代宗师,当时我还写了一篇观后感。
  • 外出旅游一趟。我曾经想过每半月去江浙周围去游玩,但是始终没有出去过。离职前后出去游玩了一趟,认识了不少人,也见到许久不见的堂姐和表弟。

2013-11-24 203521

2013-11-24 224520

小杨每次拉我出去玩,但是真正成行的却不多。想来真正去玩次数真的为数不多。最早一次是去上海,是跟庞胖子拿手表;然后跟庞胖子去过苏州跨年,跟小杨、谢剑、徐东德,燕国他们一起去象山,去过几次临安。我2011年计划要去的南京却一直没有成行。

家庭和朋友的改变

对于我个人来说,个人问题依然高悬,仍然是明年需要推进一件大事。

父母今年都还算基本顺利,还是希望老爸来年少喝酒,路上注意安全;老妈年度做了静脉栓的手术,希望明年她能来杭州玩一趟。

老哥一家今年应该也算OK吧,尤其是之妍的成长真快啊。

堂弟也结婚了。表妹也出嫁了,前几天刚生了一个娃。

去了成都一趟,见到了许久不见得表弟和堂姐,真的非常开心。

丝瓜又创业了,转变还是很大了,他小孩都上幼儿园了,我都一次没去见过。

杨老大生完孩子也回来工作了。

刚刚在回来的路上,看到花生也离职了。

朋友们,如果你们看到这篇总结,希望你们能继续好好的生活和工作。

资产状况的改变

尽管总体上来说,我的资产状况依然很差,但是相比过去几年来说有了一些变化。前面已经说到了资金分流到了股市、基金以及一些其它的渠道内。

2013年初制定了一个存钱金额的规划,但是从目前来看是基本无法实现了,至少还距离有一半多的距离。

2013年单笔最大消费—购买了一台Iphone4s的合约机。还有就是在软件上面花费了不少的费用。

2012年的总结里说过:

结合我32岁前结婚的新目标,因此婚前积累一定的资金应该是一个非常重要的环节了。

所以2014年资金方面仍然还是需要继续积累的。希望自己能够坚持一些基本原则,也能够从小杨,老俞,老汪他们那边学习到丰富的理财经验。

2014碎语

改变才刚刚开始。2014年需要进行更坚定,更专注的做自己的东西了。

b3daf505ae23116b22401750175a2e7a

3d4d8061e564c420070cf1650e29cb9d

折腾与挣扎

折腾与挣扎

国庆长假,基本没有出门,只是4日出门前往植物园短暂游走一番。

假日期间,看到一篇小文,原文开题:折腾与挣扎。

问:世间人忙忙碌碌,没一刻消停,真不知道忙些什么?

答:千忙万忙。其实只有两种忙:一种叫挣扎;一种叫折腾。

问:何为挣扎?

答:迫于生计,奔波于衣食,住房没着落,孩子要学费,病没钱医治,不忙行么?此为挣扎型。

问:何为折腾?

答:衣食不缺,房屋不漏,孩子能自立,收入有保障,却硬要在弄出点事业来做否则难受,那就是折腾了。

问:人在世间,离了这两种活法,还有别的活法么?

答:不多事,不怕事,顺俗不逐俗,随缘不攀缘,任运而动止,得不见得,失不见失,闲时闲,忙时也忙,心闲者乃真闲也。

放下手中的事情,到大自然中去静静的倾听虚空的声音,感受生命的真谛,你会发现以往为之拼搏的事业只是折腾而已。

前不久的日子里也转发了一位朋友在QQ空间写的一段生活感悟:

不能总是往上比,有时也要往差的比,寻求点自我安慰。

想想曾经的万里帝王家,都得忍受春梦绕胡沙,那么我委屈也可开花了.

我先看到朋友写的这两句,然后才看到前面这段小文的。为什么读到这段小文又让我想起朋友这两句话,因为我知道这位朋友的一些经历,她所承受的生活压力其实远远比我复杂,但是她依然能在生活和工作中取得非凡的成绩,我觉得她应该一直走在自己坚信的道路上,感觉她正在努力做到第三种活法。她不挣扎,也不折腾,很好的女子。

在我看来:

挣扎是因为存有希望,就如年少时的我,跌入水中,挣扎中寻找周围水草。那水草是活着的希望。

折腾则由于漫无目的,就如工作上的我,做过很多岗位,折腾了很多东西,却并未取得良好的结果。因为没有方向,才在各种圆圈里起舞。

但是我觉得无论如何,我都不应该放弃折腾或者挣扎。我此时折腾可能只是我想要目标被我自己掩藏起来了,我相信我一定可以重新找到。当然也不应该放弃挣扎:

各位,我希望你们无论希望如何渺茫,都不要做一个放弃了挣扎的人,叫你们不要放弃挣扎不是因为挣扎才有希望,而是因为不停挣扎我们才不会被自己看不起,才不会成为自己眼中的行尸走肉,各位不妨想想看,如果一个人放弃了挣扎,那人生真的就提前结束了。

我可能写完这篇没有中心思想的文字以后,还是会继续折腾,亦或者是挣扎,但是我肯定那时的我是更好的我。

我所有做过的选择,犯过的错误,失去的感情,都可能是我讲故事给儿子听的素材。

所有的一切会过去的,先感谢已经、正在以及将要从我身边路过的人和故事。

所有的一切会过去的,在未来我要多关心家人、朋友和自己。

从前有一个老和尚和一个小和尚下山去化缘,回到山脚下时,天已经黑了。小和尚看着前方,担心地问老和尚:师傅,天这么黑,路这么远,山上还有悬崖峭壁,各种怪兽,我们怎么才能回到家啊?老和尚看看他,平静地说了三个字:看脚下。

我希望我能真正的读懂这个故事。

ExtJS中可编辑表格的自定义风格的数据显示

在ExtJS实际开发中,我们经常碰到需要使用可编辑表格的地方。表格一般用于显示数据之用,但是在实际产品中,我们也会需要利用可编辑表格来完成数据处理和提交的工作。

在ExtJS 4.X系列中,Grid提供的可编辑功能可以很好的帮助我们实现这一需求。偶尔我们还需要对于编辑后的进行一些显示上的处理才能满足功能的需求:

  • 输入编辑的数据,根据输入的数据现实不同的显示效果。比如数字框,需要根据用户输入的数值,将单元格的数字效果转换为红色或者其它颜色,而表格保存时仅保留数值。
  • 输入编辑可选择下拉框数据,然后单元格内现实的下拉框的displayfield的内容,而提交的时候需要提交下拉框的valueField的对应的值。

下面将简单介绍利用Grid自带的单元格renderer API实现以上功能的过程。

对于第一个需求实现方法如下:

{
    xtype: 'gridcolumn',
    dataIndex: 'dataRange',
    text: '数据范围',
    editor: 'textfield',
    renderer: function(val) {
        if(val > 100) {
            return '<font color="red">'+val+"</font>";
        } else {
            return val;
        }
    }
}

对于第二个需求实现方法:

var comboboxStore = Ext.create('Ext.data.store', {
    data: [
        {'display': '是', 'value': '1'},
        {'display': '否', 'value': '2'}
    ],
    fields: [
        {name: 'display'},
        {name: 'value'}
    ]
});
........
//grid定义
{
    xtype: 'gridcolumn',
    dataIndex: 'dataRange',
    text: '数据范围',
    editor: Ext.create('Ext.form.field.ComboBox',{
        editable: false,
        queryMode: 'local',
        triggerAction: 'all',
        selectOnTab: true,
        store: comboboxStore,
        displayField: 'display',
        valueField: 'value',
        listClass: 'x-combo-list-small'
    }),
    renderer: function(val) {
        var index = comboboxStore.findExact('value',val);
        if(index != -1) {
            var rs = comboboxStore.getAt(index).data;
            return rs.display;
        }

    }
}

实现效果如图:

combobox

ExtJS作为一个完整的RIA开发框架,其内部的丰富组件已经提供了非常完整的API实现了,通常情况下,我们可以通过简单的API组合就可以实现满足功能所需要的实现方法。因此对于ExtJS的API文档和样例应该有一定的认识和理解。除了标准的组件支持,ExtJS还提供了一些其它的组合组件,例如:MutliSelect,itemselector等等。

itemselector

mutliselector

简化Javascript的异步编码

在开发项目中的前端功能时,经常会有这种场景:

用户双击图形界面某处或者点击某个按钮,弹出窗口显示一个显示具体信息的窗体或者消息窗。

通常情况下,这个窗体的内容包含由一个可显示的表单信息,可能包含下拉菜单,多选框等组件,这些内容都需要跟后端服务器进行交互获得最新的数据。

enter image description here

以往在开发时,我基本都是写类似以下的代码:

function A(){
    //示例使用Ext
    Ext.Ajax.request({
        url:'myAction.do?action=showInfoA',
        params:{
            id: 1
        },
        success:function(response){
            var resultData = Ext.decode(response.responseText);
            //下拉框A的store loadData
            storeA.loadData(resultData.data);
            B();
        }
    })
}
function B(){
    //示例使用Ext
    Ext.Ajax.request({
        url:'myAction.do?action=showInfoB',
        params:{
            id: 1
        },
        success:function(response){
            var resultData = Ext.decode(response.responseText);
            //下拉框B的store loadData
            storeB.loadData(resultData.data);

            C();

        }
    })
}

function C(){
    //示例使用Ext
    Ext.Ajax.request({
        url:'myAction.do?action=showInfoC',
        params:{
            id: 1
        },
        success:function(response){
            var resultData = Ext.decode(response.responseText);
            //下拉框C的store loadData
            storeC.loadData(resultData.data);
            render();   
        }
    })
}
function render(){
    configWin.show();
}

当然除了这种方法,我们也可以先渲染待显示的Window窗体,给每个下拉框绑定一个事件,在下拉框激活时再去后台获取数据。

就目前编写的方法本身来看,它讲这些本可以异步处理的工作串行化了,导致效率的降低。

另外还有一种场景就是使用模板的情况,并且可能还涉及到前端的国际化。那么在编写代码时,通常就会类似以下代码:

var render = function (template, data) {
  _.template(template, data);
};
$.get("template", function (template) {
  // something
  $.get("data", function (data) {
    // something
    $.get("l10n", function (l10n) {
      // something
      render(template, data, l10n);
    });
  });
});

在这种情况下,执行的过程仍然被串行化了,导致执行的效率下降,并且在层次较深的情况下,导致代码难以阅读和维护。

如果深度很深的情况就会出现,最后一页都是}的情况了

世界上本没有嵌套回调,写得人多了,也便有了}}}}}}}}}}}}

下面将简单介绍使用三个有用的JS库来帮助我们将这些代码逻辑转换真正异步并行,并且易于阅读和维护的代码。这三个库分别是Eventproxy,Step,Async。

一、使用Eventproxy

首先来看在实际项目使用的第一个场景:

enter image description here

如上图所示,深度嵌套的代码经过华丽变身后,转化为以上的漂亮代码了。当然上面的代码仍然存在问题,针对异常的处理并不完整。

EventProxy提供了多组API,可以满足不同场景的需求.

1.1 多类型异步协作

前面示例中的模板和国际化以及数据加载的过程的代码可以转换为以下代码:

var ep = new EventProxy();
ep.all('tpl', 'data', function (tpl, data) {
  // 在所有指定的事件触发后,将会被调用执行
  // 参数对应各自的事件名
});
fs.readFile('template.tpl', 'utf-8', function (err, content) {
  ep.emit('tpl', content);
});
db.get('some sql', function (err, result) {
  ep.emit('data', result);
});

all方法将handler注册到事件组合上。当注册的多个事件都触发后,将会调用handler执行,每个事件传递的数据,将会依照事件名顺序,传入handler作为参数。

1.2 重复异步协作

此处以读取目录下的所有文件为例,在异步操作中,我们需要在所有异步调用结束后,执行某些操作。

var ep = new EventProxy();
ep.after('got_file', files.length, function (list) {
  // 在所有文件的异步执行结束后将被执行
  // 所有文件的内容都存在list数组中
});
for (var i = 0; i < files.length; i++) {
  fs.readFile(files[i], 'utf-8', function (err, content) {
    // 触发结果事件
    ep.emit('got_file', content);
  });
}

after方法适合重复的操作,比如读取10个文件,调用5次数据库等。将handler注册到N次相同事件的触发上。达到指定的触发数,handler将会被调用执行,每次触发的数据,将会按触发顺序,存为数组作为参数传入。

在最近开发的流程设计器中,画布需要异步加载对应控件的类文件,在开发中采用了类似方法解决:

a、传入待渲染到画布中的JSON数据

b、解析JSON文件,异步加载每个需要使用到的控件对应的JS模块类文件

c、全部解析完成后,在当前画布中渲染图形效果

具体的代码也类似上面读取文件的过程实现。

enter image description here

 

1.3 持续型异步协作

此处以股票为例,数据和模板都是异步获取,但是数据会是刷新,视图会重新刷新。

var ep = new EventProxy();
ep.tail('tpl', 'data', function (tpl, data) {
  // 在所有指定的事件触发后,将会被调用执行
  // 参数对应各自的事件名的最新数据
});
fs.readFile('template.tpl', 'utf-8', function (err, content) {
  ep.emit('tpl', content);
});
setInterval(function () {
  db.get('some sql', function (err, result) {
    ep.emit('data', result);
  });
}, 2000);

 

tailall方法比较类似,都是注册到事件组合上。不同在于,指定事件都触发之后,如果事件依旧持续触发,将会在每次触发时调用handler,极像一条尾巴。

1.4 小结与注意事项 {#id-[实践]简化Javascript的异步编码-1.4小结与注意事项}

除了能够处理以上三种通常场景以外,Evenproxy还提供了Group,以及异常处理方法等,具体可以查看Eventproxy官方示例和文档。

  • 请勿使用all作为业务中的事件名。该事件名为保留事件。
  • 异常处理部分,请遵循Node的最佳实践。(如果在浏览器端运行则遵守浏览器端异常处理一般方法)

如果对Eventproxy的实现感兴趣也可以前往阅读其代码说明http://html5ify.com/eventproxy/eventproxy.html

 

二、使用Step

Step(https://github.com/creationix/step)提供的功能与Eventproxy类似,但是它针对的目标主要是Node的运行环境,所以这里将只会进行简单的介绍。

首先我们来看官方文档给出的一个实例:

Step(
  function readSelf() {
    fs.readFile(__filename, this);
  },
  function capitalize(err, text) {
    if (err) throw err;
    return text.toUpperCase();
  },
  function showIt(err, newText) {
    if (err) throw err;
    console.log(newText);
  }
);

 

在上面的示例中,我们将this
作为一个回调传给fs.readFile.当读取文件结束时,step会将读取结果作为参数传递给函数链中的下一个函数。紧接着在capitalize函数里可以完成一些同步任务并简单返回新的值,并且在我们调用回调时step会继续传递结果出去。

step还能提供类似eventproxy的重复的处理方法:

Step(
  function readDir() {
    fs.readdir(__dirname, this);
  },
  function readFiles(err, results) {
    if (err) throw err;
    // Create a new group
    var group = this.group();
    results.forEach(function (filename) {
      if (/\.js$/.test(filename)) {
        fs.readFile(__dirname + "/" + filename, 'utf8', group());
      }
    });
  },
  function showAll(err , files) {
    if (err) throw err;
    console.dir(files);
  }
);

对于Step提供的其它方法,可以前往其官方查看具体的示例说明。

 

三、使用Async

相比前面介绍的Eventproxy和Step来说,Async(https://github.com/creationix/step)提供的对于异步流程控制的处理方法更为丰富,它主要提供了两类API

  1. 提供集合相关的处理方法
  2. 提供异步流程控制的处理方法

3.1 Collections

3.2 Control Flow

我们这里将简单针对前文提及的几种场景进行API使用的介绍,其它的API使用方法可以前往官方网站查看。

前面提到的模板
国际化,数据最后渲染页面的方法使用async实现后的代码将如下,可以使用的API有两种series和parallel。

async.series([
    function(callback){
        fs.readFile('template.tpl', 'utf-8', function (err, content) {
          callback(null, coptent);
        });

    },
    function(callback){
        db.get('some sql', function (err, result) {
         callback(null, result);
        });

    }
],
// optional callback
function(err, results){
    console.log(results);
    //这种结果将以[]数组方式存储
});


async.series([
    tpl:function(callback){
        fs.readFile('template.tpl', 'utf-8', function (err, content) {
          callback(null, coptent);
        });

    },
    data:function(callback){
        db.get('some sql', function (err, result) {
         callback(null, result);
        });

    }
],
// optional callback
function(err, results){
    console.log(results);
    //这种结果将以{tpl:tpl,data:data}数组方式存储
});

series方法中的函数链将以定义的顺序执行。比如

async.series([
    function(callback){
        setTimeout(function(){
            console.log("start 1");
            callback(null, 1);
        }, 200);

    },
    function(callback){
        setTimeout(function(){
            console.log("start 2");
            callback(null, 2);
        }, 100);

    }
],
// optional callback
function(err, results){
    console.log(results);
});

以上示例代码使用settimeout来模拟代码执行的延时情况,以上结果将会打印:

start 1
start 2
[1, 2]

由于模板和数据之前并没有直接的依赖关系,那么我们可以改成并发异步的方法来执行。上面的代码只需要将series修改为parallel即修改并发异步执行的代码了。

前面的示例代码修改为并发执行的代码后,打印结果将会是:

start 2
start 1
[1,2]

可以看到两个函数的执行顺序将不再按照定义顺序执行,但是结果的顺序仍然保持顺序。

相比较Eventproxy和Step来说,Async提供了更多更丰富的API方法,足以满足各种场景下的需求。

比如执行的事件间是有依赖关系的,比如先根据一个序号查找该用户的信息,然后根据返回结果再执行下一个事件请求,数据需要在这些函数间进行传递.Step可以满足实现这一场景。

下面我们再来看Async提供给我们的解决方法:

async.waterfall([
    function(callback){
        console.log("start 1");
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
        console.log("start 2");
        callback(null, 'three');
    },
    function(arg1, callback){
        console.log("start 3");
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    console.log(result);
   // result now equals 'done'    
});

上面的代码执行完成后的打印结果如下:

start 1
start 2
start 3
done

 

对于某些更复杂的场景,比如csser的一个应用场景:

  • 判断用户登录状态
  • 判断用户提交数据的合法性
  • 分析并获得可以存储的板贴数据
  • 查询并确认板贴所属贴板是否有效(是否存在,以及是否为发布者拥有)
  • 板贴入库
  • 更新关联操作(比如用户的板贴数、板贴列表,贴板的板贴数和板贴列表等等)
  • 产生用户动态
  • 产生标签动态
  • 通知关注者, 贴板成员,贴板作者
  • 最后返回结果

这一系列任务,如果组织不好,代码会相当凌乱,难以维护,借助
async.auto,产生了清晰明了的代码:

async.auto({
    // 分析并获取有效数据
    datas: function (callback) {},

    // 查贴板,确认是否存在
    board: function (callback) {},

    // 入库贴板内容
    post: ['datas', 'board', function (callback) {}],

    // 更新贴板表和用户表
    updateBoardAndUser: ['post', function (callback, result) {}],

    // 产生用户动态
    feed: ['post', function(callback, result) {}],

    // 产生标签动态
    tagFeed: ['feed', function (callback, result) {}],

    // 通知关注者, 贴板成员,贴板作者
    notify: ['post', function (callback, result) {}]

// 返回JSON结果
}, function (err, result) {});

 

总结

根据实际工作场景选择合适的库,可以帮助我们编写出易于阅读和维护的javascript异步代码,这可以让我们更容易的关注每个具体的事件背后的单一事件的实现。

解决WordPress无法显示主题列表的问题

本博客系统之前使用的是Typecho,本来已经足够使用了,但是由于Typecho团队转而重心集中经营SegmentFault上了,拖拖拉拉几年了系统一直没有更新,不过这也不能责怪人家团队。但是听说最近又再更新了,这是个不错的消息。

系统迁回Wordpress有一段日子了,距离上一篇博客也有些日子了。今天写点东西是迁回Wordpress的一点小问题的解决方法。

问题

安装Wordpress后,在后台主题管理界面中无法查看所有已经上传的主题列表,永远只能看到一个当前主题的一些信息。

分析

系统部署在linode的vps上,使用的服务器是lnmp的一键安装版本,最新为0.9。

目录访问权限肯定没有问题。

目测wordpress显示主体列表跟typecho获得主题列表的函数方法不一致。

猜想wordpress估计使用了类似文件夹扫描的方法来获得主体列表,类似java的fileList。

后经过查看php.ini,发现lnmp0.9版本的php配置了禁用了一些函数,其中就有scandir函数,

查阅php的函数说明:

scandir() 函数返回一个数组,其中包含指定路径中的文件和目录

解决

  1. 修改php.ini文件内容

    找到/usr/local/php/etc/php.ini文件。
    修改php.ini文件中的disable_functions,位于314行。
    将后面的禁用的函数列表中的scandir删除,并保存文件。

  2. 重启php-fpm或者调用lnmp重启

    使用/etc/init.d/php-fpm restart重启或使用lnmp restart重启。

备注

  • 本次问题出现在使用lnmp 0.9的版本中,如果非lnmp一键安装版本,可以先确认php是否禁用了scandir函数,如果没有禁用,请再确认文件夹权限问题。
  • 操作中需要注意,不要误删除了任何配置文件。

使用Google Guava来编写优雅的代码 – 集合4(BiMap)

BiMap提供了一种新的集合类型,它提供了key和value的双向关联的场景。

通常情况下,我们在使用Java的Map时,往往是通过key来查找value的,但是如果出现下面一种场景的情况,我们就需要额外编写一些代码了。

首先来看下面一种表示标识序号和文件名的map结构。

Map<String,String> logfileMap = Maps.newHashMap();
logfileMap.put("1","a.log");
logfileMap.put("2","b.log");
logfileMap.put("3","c.log");

当我们需要通过序号查找文件名,很简单。但是如果我们需要通过文件名查找其序号时,我们就不得不遍历map了。当然我们还可以编写一段Map倒转的方法来帮助实现倒置的映射关系。

// Generic method to reverse map.
public %lt;S,T> Map<T,S> getInverseMap(Map<S,T> map) {
    Map<T,S> inverseMap = new HashMap<T,S>();
    for(Entry<S,T> entry: map.entrySet()) {
        inverseMap.put(entry.getValue(), entry.getKey());
    }
    return inverseMap;
}

上面的代码可以帮助我们实现map倒转的要求,但是还有一些我们需要考虑的问题:

  1. 如何处理重复的value的情况。不考虑的话,反转的时候就会出现覆盖的情况.
  2. 如果在反转的map中增加一个新的key,倒转前的map是否需要更新一个值呢?

在这种情况下需要考虑的业务以外的内容就增加了,编写的代码也变得不那么易读了。这时我们就可以考虑使用Guava中的BiMap了。

使用BiMap

BiMap的使用也非常简单,可以先看下面一段简单示例:

BiMap<String,String> logfileMap = HashBiMap.create();

//像通常的Map那样初始化和使用
logfileMap.put("1","a.log");
logfileMap.put("2","b.log");
logfileMap.put("3","c.log");

System.out.println(logfileMap.get("1"));

BiMap<String,String> filelogMap = logfileMap.inverse();
System.out.println(filelogMap.get("c.log"));

看上去代码确实简洁了点,但是我们有些事情时需要注意的。

数据的强制唯一性

在使用BiMap时,会要求Value的唯一性。如果value重复了则会抛出现。

logfileMap.put("1","a.log");
logfileMap.put("2","a.log");//会抛出   IllegalArgumentException异常

但是如果你确实有可能会插入重复的value,那么你可以选择forcePut方法。当时需要需要注意的是前面的key也会被覆盖了。

logfileMap.put("1","a.log");
logfileMap.forcePut("2","a.log");
System.out.println(logfileMap.get("2"));//打印a.log
System.out.println(logfileMap.get("1"));//null

理解inverse方法

inverse方法会返回一个反转的BiMap,但是注意这个反转的map不是新的map对象,它实现了一种视图关联,这样你对于反转后的map的所有操作都会影响原先的map对象。

filelogMap.put("d.log","4");
System.out.println(logfileMap.get("4"));//打印d.log   

BiMap的实现类

BiMap提供了多种实现类,比如HashBiMap、ImmutableBiMap、EnumBiMap、EnumHashBiMap。

如果需要了解更多BiMap的内容可以前往查看以下资源

使用Google Guava来编写优雅的代码 – 集合3(Multimap)

前面针对Guava中的集合已经介绍了集合的创建和不可变集合以及Guava提供的新增加的集合类型Multiset,今天将继续为大家介绍Guava中的集合接口类Multimap及其一些相关的API用法。

由来

在我们日常的开发中,经常会需要使用到下面一种数据结构

Map<String,List<MyClass>> myClassListMap test2  
          = new HashMap<String,List<MyClass>>()

比如每个用户有多个手机号码或者联系地址,在这种情况下就有可能使用到以上的数据结构了。但是在使用这种数据结构时,我们需要关注的内容太多了,比如:

  • 在为某一个key的value添加元素时,我们需要先确认该value是否为空
  • 删除某一个key对应的value中某一个元素时,就需要进行遍历

通常情况我们编写的代码会像下面一段代码:

void putMyObject(String key, Object value) {  
    List<Object> myClassList = myClassListMap.get(key);  
    if(myClassList == null) {  
        myClassList = new ArrayList<object>();  
        myClassListMap.put(key,myClassList);  
    }  
    myClassList.add(value);  
}

这样编写代码的过程让我们不得不短暂的从关注的业务处理层面更多的转移到实现该数据结构的操作上,编写这样的代码也让代码显得有点难以阅读。

使用Guava Multimap

Guava对于Multimap提供了很多实现类,类似上面的功能需求,我们可以选择使用ArraylistMultimap。

首先让我们来看一段使用了Guava Multimap后的代码:

public class MutliMapTest {  
    public static void main(String... args) {  
      Multimap<String, String> myMultimap = ArrayListMultimap.create();  

      // Adding some key/value  
      myMultimap.put("Fruits", "Bannana");  
      myMultimap.put("Fruits", "Apple");  
      myMultimap.put("Fruits", "Pear");  
      myMultimap.put("Vegetables", "Carrot");  

      // Getting the size  
      int size = myMultimap.size();  
      System.out.println(size);  // 4  

      // Getting values  
      Collection<string> fruits = myMultimap.get("Fruits");  
      System.out.println(fruits); // [Bannana, Apple, Pear]  

      Collection<string> vegetables = myMultimap.get("Vegetables");  
      System.out.println(vegetables); // [Carrot]  

      // Iterating over entire Mutlimap  
      for(String value : myMultimap.values()) {  
       System.out.println(value);  
      }  

      // Removing a single value  
      myMultimap.remove("Fruits","Pear");  
      System.out.println(myMultimap.get("Fruits")); // [Bannana, Pear]  

      // Remove all values for a key  
      myMultimap.removeAll("Fruits");  
      System.out.println(myMultimap.get("Fruits")); // [] (Empty Collection!)  
    }  
} 

从上面的代码中我们可以发现Multimap提供了以下API:

  • put(K,V) :为指定的key添加一个value元素
  • putAll(K,Iterable) : 为指定的Key添加一组value元素。
  • remove(K,V) : 移除Key对应的某一个value元素。如果执行成功则返回true
  • removeAll(K) : 移除该Key所对应的所有value。
  • replaceValues(K,Iterable) : 替换Key所对应的Values。
  • values()

如果需要知道更多的API及其用法可以前往Guava的官方站点进行查看。

Multimap的实现类

实现类 Keys 的行为类似… Values的行为类似..
ArrayListMultimap HashMap ArrayList
HashMultimap HashMap HashSet
LinkedListMultimap* LinkedHashMap* LinkedList*
LinkedHashMultimap** LinkedHashMap LinkedHashSet
TreeMultimap TreeMap TreeSet
ImmutableListMultimap ImmutableMap ImmutableList
ImmutableSetMultimap ImmutableMap ImmutableSet

以上的实现类中除去不可修改的以外,其它的实现类均支持null的键和值。

更多资源

ExtJS Grid 分组中groupTextTpl的参数使用

在我们使用ExtJS的表格组件(Grid)中经常会需要使用到分组的情况,像使用业务分组,然后组内显示该业务下挂的服务列表信息。通常情况下,分组数据的方式就有两种:服务端分组数据返回或者是客户端分组。在ExtJS的API文档中给出的示例也非常简单,其代码如下:

var grid = new Ext.grid.GridPanel({
    // A groupingStore is required for a GroupingView
    store: new Ext.data.GroupingStore({
        reader: reader,
        data: xg.dummyData,
        sortInfo:{field: 'company', direction: "ASC"},
        groupField:'industry'
    }),

    columns: [
        {id:'company',header: "Company", width: 60, sortable: true, dataIndex: 'company'},
        {header: "Price", width: 20, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
        {header: "Change", width: 20, sortable: true, dataIndex: 'change', renderer: Ext.util.Format.usMoney},
        {header: "Industry", width: 20, sortable: true, dataIndex: 'industry'},
        {header: "Last Updated", width: 20, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
    ],

    view: new Ext.grid.GroupingView({
        forceFit:true,
        // custom grouping text template to display the number of items per group
        groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'
    }),

    frame:true,
    width: 700,
    height: 450,
    collapsible: true,
    animCollapse: false,
    title: 'Grouping Example',
    iconCls: 'icon-grid',
    renderTo: document.body
});

这里需要指出的时,在大多数文档和示例代码中归于分组列头的显示都是使用本示例中的代码:

groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'

很少有代码提及关于groupTextTpl的其它相关内容。

在API文档中提及groupTextTpl的介绍中提到了以下几个可以使用的属性调用:

  • group 显示分组列名的名称,如果使用emptypGroupText属性,那么当原始Group的值为空时,将会显示emptyGroupText的值。
  • gvalue 显示分组列的原始值,如果原始值为空,显示也是空。不会调用emptyGroupText。
  • text 如果配置了header属性,则会使用header属性的值加上原始分组列内的值。如果原始值为空,会调用emptyGroupText的值。
  • groupId ExtJS对每一个分组分配的独立的编号,通常其组成如下:ext-{生成器编码如gen20,gen19等}-gp-{分组列名}-{分组列值}
  • startRow 每一个组在整个Grid当前分页中的起始行的索引,索引从0开始。
  • rs 每个组包含的结果集。
  • cls 分组头样式的class名称
  • style : 分组头样式

比如在一些场景下,有业务和服务之分,服务是从属于业务的,业务和服务均会提供相应的编号和名称,这种情况下会按照业务编号分组,而希望显示的分组头内容为业务名称,而组内排序需要使用服务编号来排序。在这种情况下,我们可以这么定义:

比如业务名称和编号的列定义为appId,appName,服务名称和编号的列定义为serviceId,serviceName。

var myDataStore = new Ext.data.GroupStroe({
    autoLoad: false,
    url:'loadata.do',
    sortInfo:{field:'serviceId',direction:'DESC'}, // 这个属性必须配置,顺序可以为DESC或者ASC
    reader: reader,
    groupField:'appId'
});

//定义Grid,不显示业务名称、业务编号和服务编号
var mygrid = new Ext.grid.GridPanel  ({
    store: mydatastore,
    columns: [
        {header: '业务名称', width:45, dataindex: 'appName',hidden:true,groupable:false,dataIndex:"appName"},
        {header: '业务编号', width:55, dataindex: 'appId',hidden:true,groupable:false,dataIndex:"appId"},
        {header: '服务编号', width:70, dataindex: 'serviceId',hidden:true,groupable:false,dataIndex:"serviceId"},
        {header: '服务名称',  width:90, dataindex: 'serviceName'},
        {header: '其它内容',  width:40, dataindex: 'otherInfo'}
    ],
    stripeRows: true,
    autoSizeColumns: true,
    autoSizeGrid: true,
    title:'Your Grid Title',
    collapsible: false,
    animCollapse: false,
    height: 445,
    columnLines: true
});

下面如果我们需要在分组头显示业务名称的时候,我们就需要在GridPanel对象中定义view对象:

view: new Ext.grid.GroupingView({
    forceFit:true,
    groupTextTpl: '分组 : {text}|{[ values.rs[0].data["appName"] ]} ({[values.rs.length]} {[values.rs.length > 1 ? "Sevices" :        "Sevice"]})'
})

使用这个视图定义后,显示的分组头部文字效果就类似如下:

//比如数据形如:
//[
// {appId:'1',appName:'业务1',serviceId:'1001',serviceName:'服务1'},
// {appId:'1',appName:'业务1',serviceId:'1002',serviceName:'服务2'},
// {appId:'2',appName:'业务2',serviceId:'2001',serviceName:'服务3'},
// {appId:'2',appName:'业务2',serviceId:'2002',serviceName:'服务4'},
// {appId:'3',appName:'业务3',serviceId:'3001',serviceName:'服务5'}
//]
// 显示在表格中的头部效果如下:
// 分组: 1 | 业务1 (2 Services)
// 分组: 2 | 业务2 (2 Services)
// 分组: 3 | 业务3 (1 Service)

撰写本文的目的是因为在实际的产品开发中遇到类似的需求,为了不修改了后台的排序代码,于是进行了以上的尝试。这说明在使用ExtJS时,其API文档可能有些内容并没有提到,但是要能从其文档中慢慢发现API的规律,并予以尝试,肯定会带来不少帮助的。

官方示例中只给出了values.rs.length的示例,那么从这里面我们就能发现values.rs肯定是这个组内的所有结果集了,这样要实现我们本身的需求目的就非常简单了。

使用Google Guava来编写优雅的代码 – 集合2

根据计划,从本篇开始会挑选Guava内部新增加的集合类来进行介绍。今天首先介绍的是Multiset:把重复的元素放入集合。JDK中Set定义了不能将重复的元素放入,那么Guava提供可以放入重复元素的Set的用意或者作用是什么呢?

首先让我们来看下面一段代码:

Map<String, Integer> counts = new HashMap<String, Integer>();
for (String word : words) {
    Integer count = counts.get(word);
    if (count == null) {
        counts.put(word, 1);
    } else {
        counts.put(word, count + 1);
    }
}

上面的代码实现的功能非常简单,用于记录字符串在数组中出现的次数。这种场景在实际的开发过程还是容易经常出现的,如果使用实现Multiset接口的具体类就可以很容易实现以上的功能需求:

Multiset<String> wordsMultiset = HashMultiset.create();
wordsMultiset.addAll(words);
//接下来就可以直接使用count方法来得到某个特定元素的个数了。

Multiset主要方法

Multiset接口定义的接口主要有:

  • add(E element) :向其中添加单个元素
  • add(E element,int occurrences) : 向其中添加指定个数的元素
  • count(Object element) : 返回给定参数元素的个数
  • remove(E element) : 移除一个元素,其count值 会响应减少
  • remove(E element,int occurrences): 移除相应个数的元素
  • elementSet() : 将不同的元素放入一个Set中
  • entrySet(): 类似与Map.entrySet 返回Set<Multiset.Entry>。包含的Entry支持使用getElement()和getCount()
  • setCount(E element ,int count): 设定某一个元素的重复次数
  • setCount(E element,int oldCount,int newCount): 将符合原有重复个数的元素修改为新的重复次数
  • retainAll(Collection c) : 保留出现在给定集合参数的所有的元素 * removeAll(Collection

    c) : 去除出现给给定集合参数的所有的元素

测试代码如下 :

/**
 * Test class for Multiset
 */

@Test
public class MultisetTest {


    @Test
    public void shouldAddElementTwoTimes() throws Exception {

        // given
        Multiset<String> multiset = HashMultiset.create();

        // when
        multiset.add("nothing");
        multiset.add("nothing");

        // then
        assertThat(multiset.count("nothing")).isEqualTo(2);
        assertThat(multiset.count("something")).isEqualTo(0);
    }

    @Test
    public void shouldUserCustomAddRemoveAndSetCount() throws Exception {

        // given
        Multiset<String> multiset = HashMultiset.create();

        // when
        multiset.add("ball");
        multiset.add("ball", 10);

        // then
        assertThat(multiset.count("ball")).isEqualTo(11);


        // when
        multiset.remove("ball", 5);

        // then
        assertThat(multiset.count("ball")).isEqualTo(6);


        // when
        multiset.setCount("ball", 2);

        // then
        assertThat(multiset.count("ball")).isEqualTo(2);
    }


    @Test
    public void shouldRetainOnlySelectedKeys() throws Exception {

        // given
        Multiset<String> multiset = HashMultiset.create();

        multiset.add("ball");
        multiset.add("ball");
        multiset.add("cow");
        multiset.setCount("twelve", 12);

        // when
        multiset.retainAll(Arrays.asList("ball", "horse"));

        assertThat(multiset.count("ball")).isEqualTo(2);
        assertThat(multiset.count("twelve")).isEqualTo(0);
    }

}

Multiset 不是Map

需要注意的是Multiset不是一个Map<E,Integer>,尽管Multiset提供一部分类似的功能实现。其它值得关注的差别有:

  • Multiset中的元素的重复个数只会是正数,且最大不会超过Integer.MAX_VALUE。设定计数为0的元素将不会出现multiset中,也不会出现elementSet()和entrySet()的返回结果中。
  • multiset.size() 方法返回的是所有的元素的总和,相当于是将所有重复的个数相加。如果需要知道每个元素的个数可以使用elementSet().size()得到.(因而调用add(E)方法会是multiset.size()增加1).
  • multiset.iterator() 会循环迭代每一个出现的元素,迭代的次数与multiset.size()相同。 iterates over each occurrence of each element, so the length of the iteration is equal to multiset.size().
  • Multiset 支持添加、移除多个元素以及重新设定元素的个数。执行setCount(element,0)相当于移除multiset中所有的相同元素。
  • 调用multiset.count(elem)方法时,如果该元素不在该集中,那么返回的结果只会是0。

实现类

Guava提供了Multiset的很多实现类,每一个实现类都对应这JDK的某一个map实现。具体如下表所示。

Map 对应的 Multiset实现 支持null
HashMap [HashMultiset](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/HashMultiset.html) Yes
TreeMap [TreeMultiset](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/TreeMultiset.html) Yes (如果comparator允许)
LinkedHashMap [LinkedHashMultiset](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/LinkedHashMultiset.html) Yes
ConcurrentHashMap [ConcurrentHashMultiset](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/ConcurrentHashMultiset.html) No
ImmutableMap [ImmutableMultiset](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/ImmutableMultiset.html) No
EnumMap [EnumMultiset](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/EnumMultiset.html)

除此以外还有ForwardingMultiset和ImmutableSortedMultiset,对于某些先增的类,需要关注其注解是否包含@Beta,那么这些类的API尚未稳定,调用过程需要小心。

本文对Guava中新增加的Multiset接口和实现类进行了简单的介绍,接下来我们将会介绍新增加的Multimap接口和其实现类的一些相关使用场景和使用方法,希望大家继续关注。

使用Google Guava来编写优雅的代码 – 集合1

使用Google Guava来编写优雅的代码 – 集合1

在我们日常的Java开发工作中,经常需要使用各种数据结构,通常情况我们会使用JDK内部的集合库,也有可能使用到其它优秀的第三方库,诸如Apache common-collections等。但是如果仅仅使用JDK内部的集合库,我们可能需要编写一段相对复杂的代码来实现相对复杂的处理逻辑。在这种情况下,我会建议你使用Google Guava内置的相关的集合类和工具类,很有可能这里面就有符合你需要的API方法。

Google Guava内置的集合相关的类原本属于Google开源的Google-collections库,它对JDK内置的集合类和工具类提供丰富的扩展和增强,目前已经集成到Guava中,使用Guava内置的集合库肯定能帮助你编写出更简洁、更易读和更易维护的工作代码,使你能更好的关注需要处理的业务逻辑本身。并且这些集合类和工具都经过了单元测试,并广泛用于Google内部,或其它著名的项目中,诸如elasticsearch等。

Guava对于集合的扩充和增强主要涵盖以下几个方面:

  • 提供了不可变集合类的支持
  • 增加了新的集合类型
  • 提供了强大的集合操作的工具,诸如排序、交集、差集、并集等方法的实现

本系列文章对于集合的介绍按照以上几个方面来逐一介绍,本章节将介绍集合创建和不可修改集合类的使用介绍,随后的章节将会挑选相对使用广泛的新增的集合类进行逐一介绍,最后一个章节则会介绍Guava中提供的集合操作的方法。

集合对象创建方法

在我们的日常开发中,肯定会经常使用到创建一个集合对象的地方,这些代码看起来就像下面这样:

List<String> fields = new ArrayList<String>();
Map<String,String> nameAddressMap = new HashMap<String,String>();   

在Guava中对于JDK标准的集合类和Guava增加的集合类都提供快速创建的方法。示例代码如下:

//Guava增加的集合类
Multimap<String,String> multimap1 = new ArrayListMultimap<String,String>();
Multimap<String,String> multimap2 = ArrayListMultimap.create();

对于标准的JDK集合类,Guava提供了Lists、Sets和Maps类来实现基本标准类型的创建方法,代码如下:

List<String> myList1 = new ArrayList<String>(); //标准方式
List<String> myList2 = Lists.newArrayList();    //使用Guava

Set<String> mySet1 = new HashSet<String>(); //标准方式
Set<String> mySet2 = Sets.newHashSet();     //使用Guava

不可修改集合

在我们的日常开发中还是有需要使用到不可修改集合的需求的,比如常量,避免多线程间的竞争检查(如果你定义了一个可变集合,那么你要处处小心,防止别的线程与它之间的使用关系。)。在JDK 5以后,JDK提供了创建不可修改的集合类的方法,编写的代码大致如下:

Set<String> set = new HashSet<String>(Arrays.asList(new String[]{"RED", "GREEN"})); 
Set<String> unmodifiableSet = Collections.unmodifiableSet(set); 

这样编写的代码看起来也不错,这个不可修改Set仍然是存在问题的。如果有人不小心了,前面定义的引用set对象里的数据的话,这个不可修改集合就会被改变了。
如果你使用Guava来编写这段代码的话,就变成下面这个样子:

ImmutableSet<String> immutableSet = ImmutableSet.of("RED", "GREEN"); 

两行变一行,并且不用担心引用对象被修改的问题了,做到了真正的不可修改了,如果尝试使用immutableSet进行add方法时也会抛出UnsupportedOperationException。

可以执行下面一段代码发现JDK自带的保护方法和Guava实现的异同和问题。

HashMap<String,Integer> temp = Maps.newHashMap();
temp.put("one",1);
temp.put("two",2);

ImmutableMap<String,Integer> tempmap = ImmutableMap.copyOf(temp);
Map<String,Integer> unmodifiableSet = Collections.unmodifiableMap(temp);
temp.put("3",3);


System.out.println(tempmap.size());
System.out.println(unmodifiableSet.size());

Guava对标准的集合和其增加的集合类都提供对应的不可变类。标准的集合类的不可修改实现接口为:ImmutableList,ImmutableSet和ImmutableMap,Guava增加的集合类诸如ImmutableMultimap。

对于创建不可修改集合,Guava提供了以下集中方法来实现:

  • of 如前文示例一般,可以快速的创建不可修改集合
  • copyOf 将已有的集合对象转变为不可修改集合

    ImmutableSet<String> foobar = ImmutableSet.of("foo", "bar", "baz");
    thingamajig(foobar);
    
    void thingamajig(Collection<String> collection) {
        ImmutableList<String> defensiveCopy = ImmutableList.copyOf(collection);
        ...
    }
    
  • builder

    // use a builder...
    public Map<String,Integer> makeImmutableMap() {
        ImmutableMap.Builder<String,Integer> mapBuilder =
                new ImmutableMap.Builder<String,Integer>();
        Entry<String,Integer> entry = null;
        while((entry = getEntry()) != null) {
            mapBuilder.put(entry.getKey(), entry.getValue());
        }
        return builder.build();
    }
    

asList

所有的集合类都提供了asList方法,比如你使用了一个ImmutableSortedSet,那么你可以直接使用sortedSet.asList().get(k)得到排序后的单个元素。

如果你需要了解更详细的关于本文相关的内容可以前往以下资源查看:

© 2014 Leton

Theme by Anders NorenModified by Hailong HaoUp ↑