庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理


    写这篇文章的想法产生在昨天晚上读《面向对象分析与设计》的时候,我渐渐发现我们这个小组不知不觉地贯彻了很多非常有价值的实践经验,这些点点滴滴都对我们的最终的产品质量产生了或大或小的影响,保证我们的系统不会出现重大的故障。我想有必要将这些“隐性知识”稍微总结一下,以供参考和记录。

   从过程的连续光谱来看,我们大概处于中间位置偏左的位置,更偏向一个轻量级团队的敏捷过程,但是也包含计划驱动过程中的因素。我们的小组是自管理的,没有专门的QA和SA,我们自己去想出最好的工作方法,但是在执行中我们的计划还是相对确定的,每个季度做什么都会有一个比较明确的计划和里程碑,并且对问题领域都相对熟悉;我们的过程是迭代式,一般一个季度至少会交付一个稳定可执行的新版本,我们在文档上做的不是特别好,很多都依赖于团队成员之间的“隐性知识”;同时我们对问题的改进基本还是有一个流程和机制,会持续的跟踪问题并改进。

   下面分阶段总结下我们的一些实践经验。

一、分析和设计阶段

1、在这个阶段,我们会明确准备做什么,界定问题的边界,对功能进行一个取舍。一般在一个版本完成之后会马上开始这个过程。大家都想一想接下来做什么,经过几轮PK后确定重要紧急的事情优先做,定义下一个版本的功能列表

2、功能列表出来之后,我们会针对每个功能提出各种方案做比较,在此期间,我们会邀请更大团队范围内的专家参与方案和设计的评审,剔除不切实际以及明显有缺陷的方案,针对一些风险点提出改进建议和防范措施。

3、在设计方案出来之后,我们会分配功能的开发任务,根据每个开发人员熟悉的领域,自主领取或者被动分配任务。这个过程不是一成不变的,考虑到团队内部知识交流的必要性,也可能让不熟悉某个领域的人去做他不熟悉的事情。

二、构造阶段

1、整个系统已经有一个关键的抽象机制,针对我们的服务器有一个核心的pipeline机制,针对我们的客户端,有一个核心的发送消息流程。将所有的功能模块组织在这个关键机制周围,形成一个强有力的整体。

2、开发完成不仅仅意味着功能代码的完成,还包括测试代码:单元测试和集成测试。如果你没办法做到全面的覆盖,那就要求必须覆盖运行的关键路径和极端场景。

3、单元测试我们使用JUnit,适当使用Mock可以简化测试。但是Mock对象如果太多,也许会失去测试的价值,这里有一个权衡。

4、在整个构造过程中,我们贯彻每日构建、持续集成的原则。使用hudson做持续集成,时刻关注测试状况,有问题及时反馈给开发者。

5、有一个功能强大的集成测试框架,模拟实际环境做各种测试,它的目的是尽量在接近真实状况下去执行系统并尽早暴露问题。

6、每个功能完成之后,立即发起review,请同事和你一起复审代码。复审代码的作用不仅是发现bug,改良设计,也是一个知识交流的最佳途径。我们经常能通过代码审查发现一些设计上的缺陷,以及功能实现上的BUG。我们团队应该说是非常看重代码审查的作用。

7、使用findbugs和clover等工具,分析代码质量并改进。

8、在发布之前,做一次集中的代码review,每个人介绍下自己的功能实现代码和设计,一般我们会申请一个会议室和投影仪,并邀请团队之外的人加入review。

9、在发布之前,有一个系统的压测流程,针对每个版本更新压测方案,并预留一到两周的时间做性能压测。压测不仅能尽早暴露性能隐患,还可以发现系统在特殊情况下的一些BUG。压测除了关注系统的吞吐量、GC情况之外,还应该关注硬件的性能指标。

三、发布和总结
1、发布之前,最好让使用我们系统的用户使用新版本做一个回归测试,一方面是测试兼容性,一方面也可以及早发现BUG。

2、我们的发布流程:线下、beta、线上。每个阶段通常都持续一到两周,才会进行到下一阶段。并且是从相对不重要的系统,到关键系统的顺序进行发布。

3、发布之后,通过日志、运行时监控、用户反馈等方式收集系统运行状况,发现BUG,修正BUG,补充测试,测试通过,重新发布。

4、每个版本发布后,需要总结下本次发布过程中遇到的所有BUG以及经验教训,并提出可能的改进建议。

5、需要一个跟踪线上问题的BUG跟踪系统,可以用JIRA之类的trace软件。跟踪不仅是记录,最好列出解决的时间点,在哪个版本确定解决,甚至确定交给谁去解决,并持续跟进。

posted @ 2010-12-30 11:01 dennis 阅读(4248) | 评论 (9)编辑 收藏


    Xmemcached在元旦左右准备发1.3这个版本,这个版本新增加的一个关键特性就是所谓的failure模式。关于这个,可以看下memcached官方文档的解释
《Failure,or Failover》

    展开来说,在某个memcached节点挂掉或者由于其他故障连接断开的时候,大部分客户端的默认策略都是failover的,也就是会查找下一个可用的memcached节点继续使用,挂掉或者连接不上的节点的数据会转移到其他节点上,路由的策略可以是Round Robin,也可以是一致性哈希。这样的模式在节点意外故障挂掉的情况下运行的很好,但是memached节点也完全可能因为一个意外的事故而短暂挂掉,比如你不小心弄掉了网线又马上接上去,比如机房交换机突然停电又立即恢复了,假设在故障前,用户A正要更新数据到节点A,节点A意外断开,那么这些数据就更新到下一个有效节点B,但是节点A又马上恢复,这时候用户又从节点A去读数据,读到却是更新前的老数据了(新数据更新到B节点去了),这种情况对用户来说就非常困惑,你告诉我更新成功,但是看到却还是更新前的数据。

   怎么解决呢?一个简单的方案就是所谓failure模式,当某个节点挂掉的时候,不会从节点列表移除,请求也不会转移到下一个有效节点,而是直接将请求置为失败,就刚才的场景来说,在用户更新数据到节点A的时候,节点A意外断开,那么用户的这次更新请求不会转移到节点B,而是直接告诉用户更新失败,用户再次查询数据则绕过节点A直接查询后端存储。这种模式很适合这种节点短暂不可用的状况,请求会穿透缓存到后端,但是避免了新旧数据的问题。
    Xmemcached 1.3将支持failure模式,只要你设置下failureMode为true即可,简单示例:

           XMemcachedClientBuilder builder =……
               
//设置使用failure模式
           builder.setFailureMode(true);
      在此模式下,某个节点挂掉的情况下,往这个节点的请求都将直接抛出MemcachedException的异常。

      不仅如此,xmemcached 1.3还将引入standby node的概念,你可以设置某个memached节点的备份节点,当这个节点挂掉的时候会将请求转发给这个备份节点,不会简单地抛出异常,也不会转发给其他节点。要使用standby node,必须首先设置使用failure mode,一个例子:

XMemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil
                .getAddressMap(
"192.168.1.99:11211,192.168.1.100:11211 192.168.1.101:11211,192.168.1.102:11211"));
builder.setFailureMode(
true);

     可以看到,新的服务器字符串格式变化为host:port,host:port host:port,host:port的格式,以空格隔开的是两个节点组成的一个分组,以逗号隔开的是主节点和备份节点,以上面的例子来说,我们设置客户端使用的节点是192.168.1.99和192.168.1.101,其中99对应的备份节点是100,而101的备份节点是102。并且我们需要设置使用failure mode为true。
   
     Failure mode加上standby节点可以比较好的解决新旧数据的问题,并且也可以防止请求穿透缓存到DB,但是主备两个节点之间的数据同步,xmemcached不准备帮你做,我的建议是可以使用repcached这个patch做复制。
    有的朋友可能希望,在使用备份节点之前先flush掉备份节点的数据,防止使用到老的数据,请求还是可以穿透缓存去DB查找,并存储到备份节点,我仔细考虑了这个方案,衡量之下还是不准备做自动flush,主要是并发上很难处理,并且flush数据这个事情可以手工来搞,根据我的经验,做的太透明太自动不一定是好事。你可以在主节点恢复之后,手工flush下备份节点的数据。


    目前,xmemcached 1.3已经整装待发,对这些特性有兴趣的朋友可以先从svn下载源码尝鲜,有任何改进的建议请发邮件给我。我的邮件地址在博客的右上角。





  

posted @ 2010-12-28 10:47 dennis 阅读(4620) | 评论 (5)编辑 收藏

    最近没更新blog,因为我忙着写程序,玩android。入手了一台moto xt701,下单后悔来想换里程碑,但是京东打包太快,客服不让换,这个很奇怪,我想用更多钱买东西反而被拒绝,看来京东处理太快也不一定是优点

    买了几本android相关的书,下决心好好学习一下,在读完一本加半本的情况下决定开始做个练习程序,最后的结果就是为乐天气这个小软件。为乐天气不仅仅是个烂大街的天气预报软件,它从google weather api抓取天气状况信息并显示在桌面widget,还提供了两个我个人很需要但还没有在其他天气软件上看到的功能:恶劣天气告警和气温变化告警——当有雨雪天气或者气温变化超过一定幅度的时候主动通知我,这对我这个常常不知道带伞并且家里有小孩的人比较有用。来几张运行在xt701上的截图:






  

    写程序总共花了3天,程序虽小,但基本上覆盖了android提供的一些基本机制:Activity显示组件、service负责信息抓取、桌面widget、通过intent在组件之间交互、handler处理界面更新、国际化和资源管理、利用preferences保存配置以及使用Application保存全局数据等等。Android开发给我的感觉是,入门还是相当容易的,如果熟悉Java甚至J2ME,那么学习android的入门成本还是很低的,因此从长期来看,做这一行的一般应用门槛不高,也会像现在的Java市场一样吸引大量开发者。如果说对独立开发者特别有价值的方向,应该还是游戏方向,做游戏不仅仅是技术,更多还是创意和推广,另外想在android做出效果非常出色的游戏,还需要去学习OpenGL和数学算法之类,需要熟悉c/c++,本质上跟传统的游戏开发没有太大区别,这个门槛就相对高一些。

   我将这个小程序发到了国内的几个market,从下载情况来看,尽管都非常少,但是91助手的应用汇还是最多,其次是安卓市场,再后面是爱米软件商店,从后台体验上来说,最好的还是eoeAndroid社区的优智市场,不过人气貌似不旺。
    从我接触移动开发的这一周来看,我很兴奋,原来现在这个行业已经这么火热,有太多新鲜的东西我没有尝试过,有太多很有创意的小应用小游戏存在,有大量的开发者早就在从事这个激动人心的领域,我太out了,希望现在关注还来得及。

posted @ 2010-12-11 19:35 dennis 阅读(2632) | 评论 (2)编辑 收藏


    HandlerSocket是日本人 akira higuchi 写的一个MySql的插件,通过这个插件,你可以直接跟MySql后端的存储引擎做key-value式的交互,省去了MySql上层的SQL解释、打开关闭表、创建查询计划等CPU消耗型的开销,按照作者给出的数据可以在数据全部在内存的情况下可以达到75W的QPS查询。具体信息可以看这篇Blog,中文介绍可以看这篇文章《HandlerSocket in action》。

    这个东西为什么让我很激动呢?首先性能是程序员的G点,一听高性能你不由地激动,其次,这也解决了缓存跟数据库的一致性问题,因为缓存就在数据库里面,第三,这个东西不仅仅是NoSQL,简单的CRUD你可以通过HandlerSocket,但是复杂的查询你仍然可以走MySql,完全符合我们应用的场景,并且从实际测试来看,性能确实非常优秀。但是呢,这个东西的代价也少不了,例如没有权限检查(未来可能添加);不能启用MySql的查询缓存,否则会导致数据的不一致;协议设计也不合理,使用\t做分隔符,使用\n做换行符,那么你插入或者更新的字段数据就不能含有这些字符,否则行为将不如预期。

   HandlerSocket有一个日本人的java客户端实现,我去尝试了下,结果发现这玩意完全不具实用性,封装的层次非常原始。因此我自己写了个新的客户端,这就是本文要介绍的HandlerSocket Client for Java,简称hs4j,项目放在了googlecode,代码的网络层复用xmemcached,重新实现了协议和上层接口,目前的状态完全可用,也希望有需要的朋友参与测试。

   项目地址:http://code.google.com/p/hs4j/

    HS4J的使用很简单,所有的操作都通过HSClient这个接口进行,如我们创建一个客户端对象:

import com.google.code.hs4j.HSClient;
import com.google.code.hs4j.impl.HSClientImpl;

   HSClient hsClient 
= new HSClientImpl(new InetSocketAddress(9999));

   假设HandlerSocket运行在本地的9999端口,默认的9998是只读的,9999才是允许读和写。HSClient是线程安全的。

   在执行操作前需要先open index:
import com.google.code.hs4j.IndexSession;

      IndexSession session 
= hsClient.openIndexSession(db, table,
                                
"PRIMARY", columns);

   其中db是数据库名,table是表名,"PRIMARY"表示使用主键索引,columns是一个字符串数组代表你要查询的字段名称。这里没有指定indexid,默认会产生一个indexid,你也可以指定indexid,返回表示一次open-index会话对象,IndexSession同样是线程安全的。
IndexSession session = hsClient.openIndexSession(indexid,db, table,
                                
"PRIMARY", columns);

   查询操作通过find方法:
import java.sql.ResultSet;

                
final String[] keys = { "dennis""killme2008@gmail.com" };
                ResultSet rs 
= session.find(keys);
                
while(rs.next()){
                   String name
=rs.getString(1);
                   String mail
=rs.getString(2);
                }

   find返回的是java.sql.ResultSet,你完全可以像使用jdbc那样去操作结果集。当然我的简单实现并不符合JDBC规范,只实现了最常见的一些方法,如getStrng、getLong等。find(keys)方法默认使用的op是"="。其他重载方法可以设置其他类型的op,统一封装为枚举类型FindOperator。

   更新操作:
import com.google.code.hs4j.FindOperator;

   
int result=session.update(keys, new String[] { "1""dennis",
                                
"test@163.com""109" }, FindOperator.EQ);

   keys表示索引的字段列表对应的值数组,通过FindOperator.EQ比较这些值和索引,第二个参数values表示要更新的字段值,这些值跟你在open-index的时候传入的columns一一对应,最后返回作用的记录数。

    删除操作:
   int result= session.delete(new String[] { "dennis" },
                                FindOperator.EQ)

    HS4J同样支持连接池,可以在构建客户端的时候传入连接池大小:
  //100-connections pool
   HSClient hsClient = new HSClientImpl(new InetSocketAddress(9999),100);

   在open index的时候,会在连接池里所有的连接上都open。并且在连接因为意外情况(如网络错误)断开的时候,HS4J会自动重连,并在重连成功的情况下自动发送已经open的index,保证应用的操作不受重连影响。

    因为HS4J是我在两天内写就的一个东西,可能还有不少隐藏的bug,并且HandlerSocket本身也是个新东西,如果有什么问题或者改进建议,随时欢迎告诉我,多谢。


  
  

posted @ 2010-11-30 13:51 dennis 阅读(9014) | 评论 (4)编辑 收藏


     上周在内部做的一个Java NIO框架的实现技巧和陷阱的分享,对编写NIO网络框架有兴趣的朋友可能有点帮助,上传slideshare.net一直出错,直接提供下载吧。
    
     下载地址:Nio Trick and Trap.pdf.zip





posted @ 2010-11-22 18:22 dennis 阅读(14292) | 评论 (19)编辑 收藏


    前段时间对kilim的当前版本做了一些改进,集中在nio调度器这一块。Kilim新版本引入了nio调度器,可以跟非阻塞IO结合在一起,从这个版本开始,kilim才真正具有实用性。协程只有跟非阻塞IO结合起来才能发挥威力啊。但是Kilim的默认的nio调度器还只是使用一个nio worker做调度,这跟现有的NIO框架采用多个nio worker来提升效率比较起来相对落伍。我改进了NioSelectorScheduler,引入了类似Netty3的boss和woker的概念,boss负责连接接入,而worker负责连接的IO读写,并且默认设置worker数目为CPU个数的两倍。经过我的测试,改进后的NIO调度器的效率远远超过了现有的调度器,有兴趣可以用netty的benchmark跑一下example里的EchoServer

    Kilim默认还提供了一个简易Http Server框架,但是没有提供Http Client的实现,我的另一个改进是提供了一个简易的Http Client实现,也是利用Ragel做协议解析,一个简单的使用例子如下:
package kilim.examples;

import kilim.Pausable;
import kilim.Task;
import kilim.http.HttpClient;
import kilim.http.HttpResponse;


public class SimpleHttpClient {
    
static class SimpleTask extends Task {
        @Override
        
public void execute() throws Pausable, Exception {
            HttpClient client 
= new HttpClient();

            HttpResponse resp 
= client.get("http://www.google.com.hk/");
            System.out.println(resp.status());
            System.out.println(resp.content());
        }
    }


    
public static void main(String[] args) {
        SimpleTask task 
= new SimpleTask();
        task.start();
    }

}


    这个简陋的HttpClient目前只支持GET/POST,同时支持Http chunk编码(得益于kilim原有代码),做一些简单的HTTP调用已经足够。我尝试在一个项目里使用这个HttpClient去替代java默认的HttpURLConnection,效率有部分提升,但是同时由于大量协程存在占用了很大部分的内存,给GC也带来了不小的压力。

    我的代码直接从kilim的主干fork出来,有兴趣可以直接git clone下来玩玩,地址  https://github.com/killme2008/kilim

posted @ 2010-11-19 18:40 dennis 阅读(3939) | 评论 (3)编辑 收藏

 
    一直有这么个想法,列一下我个人认为在学习和使用Java过程中可以推荐一读的书籍,给初学者或者想深入的朋友一些建议,帮助成长。推荐的的都是我自己读过,也会推荐一些朋友读过并且口碑不错的书籍。

一、基础类
1、《Thinking in java》,入门第一位是建立正确的概念。
2、《Core Java》,我没系统读过,这本书更贴近实践,更多API的介绍,同样,更新也更频繁。

二、进阶类
1、《Effective Java》,在熟悉语法、API之后,你需要知道最佳实践和陷阱,没有比这本更好的。
2、《Java Puzzlers》,通过谜题介绍一些你可能没有注意到的边角料,作为趣味读物也不错
3、《深入Java虚拟机》,翻译一般,但不可不读,最好结合最新的JVM规范来读。

三、特定领域
1、网络编程:
(1)
O'Reilly的《Java nio》,很多人都推荐,我个人觉的一般,基本上只是个API更详细的说明文档,O'reilly的java系列很多都是这样。
(2)我更推荐这本《Fundamental networking in java》,由浅入深教你怎么做java网络编程,并且介绍很多背景知识,甚至介绍了各种最佳实践、网络编程模型以及Java socket在不同平台之间的差异等等。

2、并发编程:
(1)《Java Concurrency in Practic》,并发领域必读经典。
(2)《Java并发编程:设计原则与模式》,同样是Doug lea的作品。
(3) 《java threads》,入门读物。

3、web编程,这块我许久未接触了,就不推荐了,有兴趣的朋友可以补充下。

四、模式与设计

1、《设计模式》,GOF的经典。
2、《设计模式精解》,应该有最新版,个人认为更适合入门。
3、《Head first设计模式》,更轻松的入门读物。
4、《企业应用架构模式》
5、《分析模式——可复用对象模型》
6、《面向模式的软件体系结构》,国内貌似翻译了3卷,绝对经典,可惜翻译较差。
7、《重构——改善既有代码设计》,想写好代码必读。
8、《重构与模式》,给我印象很深的 xml构建的例子,在我的代码里应用到了。

五、方法论
1、《敏捷软件开发》
2、《测试驱动开发》,你不一定要TDD,但是你一定要学会做单元测试。
3、《Agile Java》,也可以作为java入门读物。
4、《快速软件开发》
5、《面向对象分析与设计》,OO设计必读。
6、《Unix编程艺术》,打开你的眼界。

六、Java之外
0、《代码大全》,编程的百科全书,必读。
1、《unix网络编程》,学习网络编程必读书。
2、《C++网络编程》上下两卷,介绍ACE的,但是其中对各种模式运用的介绍非常值的一读。
3、《Joel说软件》,编程文化
4、《人月神话》、《人件》
5、《卓有成效的程序员》,给我很大启发的一本书。
6、《程序员修炼之道》
7、《计算机程序的构造与解释》,必读
8、《算法导论》,可以作为参考书
9、《深入理解计算机系统》
10、《编译原理》龙书,最新版用java解释,我没有读完,顺便提下。




posted @ 2010-11-11 11:13 dennis 阅读(20896) | 评论 (12)编辑 收藏


    我最近在实现一个基于Kilim的HttpClient,在处理响应body特别大的情形下遇到了kilim的一个BUG,有必要记录下。
    问题是这样,Kilim将连接封装为EndPoint对象,EndPoint有个方法fill用于从管道读数据到缓冲区,并且可以指定希望至少读到多少个字节(atLeastN)才返回。那么在进入此方法的时候会判断缓冲区是否有足够空间容纳atLeastN个字节,如果没有,则创建一个更大的缓冲区,并将“老”的缓冲区的数据拷贝到新缓冲区,这部分代码是这样实现:
public ByteBuffer fill(ByteBuffer buf, int atleastN) throws IOException, Pausable {
        
if (buf.remaining() < atleastN) {
            ByteBuffer newbb 
= ByteBuffer.allocate(Math.max(buf.capacity() * 3 / 2, buf.position() + atleastN));
            buf.rewind();
            newbb.put(buf);
            buf 
= newbb;
        }
        ……
}

    后面的代码我省略了,这个BUG就出现在这段代码里。这段代码的逻辑很简单,先是创建一个新的更大的缓冲区,然后将老的缓冲区的数据put到新的缓冲区,在put之前调用rewind方法将老的缓冲区的position设置为0。查看rewind干了什么:
 public final Buffer rewind() {
    position 
= 0;
    mark 
= -1;
    
return this;
    }

    仅仅是将position设置为0,并让mark失效。position指向下一个读或者写的位置,这里在写入到新缓冲区之前确实需要将position设置为0,以便写入从老的缓冲区第一个位置开始。问题是什么?问题是position仅仅指定了下一个读取数据的位置,却没有指定有效数据的大小,换句话说,没有指定老的缓冲区的limit。因此这里造成的后果是老的缓冲区整个被写入到新的老缓冲区,包括有效数据和无效数据,默认情况下缓冲区的limit等于capacity。

   这个bug可以通过下面程序看出来:
        ByteBuffer old = ByteBuffer.allocate(8);
        old.putInt(
99);
        ByteBuffer newBuf 
= ByteBuffer.allocate(16);
        old.rewind();
        newBuf.put(old);
        newBuf.putInt(
100);

        newBuf.flip();
        System.out.println(newBuf.remaining());
        System.out.println(newBuf.getInt());
        System.out.println(newBuf.getInt());
        System.out.println(newBuf.getInt());

    先往old写入一个整数99,然后创建newBuf并写入old数据,并再写入一个整数100,最后从newBuf读数据。本来我们预期只应该读到两个整数99和100,但是中间却插入一个0,输出如下:

12
99
0
100

    12表示缓冲区可读的数据,本来应该是8个字节,却多了4个字节的无效数据。

     这个BUG解决很简单,将rewind修改为flip方法即可,flip不仅将position设置为0,也将limit设置为当前位置:
  public final Buffer flip() {
    limit 
= position;
    position 
= 0;
    mark 
= -1;
    
return this;
    }


    修改上面的测试程序,符合我们的预期了:
        ByteBuffer old = ByteBuffer.allocate(8);
        old.putInt(
99);
        ByteBuffer newBuf 
= ByteBuffer.allocate(16);
        old.flip();
        newBuf.put(old);
        newBuf.putInt(
100);

        newBuf.flip();
        System.out.println(newBuf.remaining());
        System.out.println(newBuf.getInt());
        System.out.println(newBuf.getInt());;

    输出:
8
99
100

    总结,使用rewind的前提是limit已经正确设置,例如你将buffer写入成功并想记录这个buffer,可以使用rewind:
while (buffer.hasRemaining()) //发送数据
    networkChannel.write(buffer);
buffer.rewind(); 
// 重置buffer,准备写入日志管道
while (buffer.hasRemaining()) // 写入日志
    loggerChannel.write(buffer);

   而flip用于缓冲区发送或者读取之前,也就是将缓冲区设置为等待传出状态。

posted @ 2010-11-03 23:22 dennis 阅读(2801) | 评论 (2)编辑 收藏

    这某文就是这篇文章啦,咱也不废话,不遮掩。几点感想:

1、首先,这篇文章很多的“我听说”、“据说“、我和……聊脱,他们都表示很郁闷“、“我就听说过一些……”诸如此类的道听途说,我觉的这不是“工程师”该说的话,请不要用可能,好像,听说这样的字眼,请给我数据,给我地点,给我程序。

2、其次,文中作者提到的yahoo的优秀点,在淘宝我都发现了传承,我估计是学习yahoo中国的,比如发布流程、比如知识库、比如很有特色的技术大学培训、比如鼓励创建自动化工具等等,我不知道作者有没有在阿里集团的子公司待过,或者来淘宝待过,如果来过呆过,我想不会没看见。

3、第三,Google是商业公司,百度是商业公司,阿里更是商业公司,没有销售人员的付出,工程师的劳动成果何以体现?你的薪水从哪里来?工程师文化或者销售文化,这不重要的,重要的是你能否认同,能否感受到尊重,不能可以用脚投票。

4、个人感觉,阿里是非常富有理想主义的公司,点滴改变着我们的生活,不知不觉,淘宝、支付宝已经改变了很多人的生活。在我看来,这些都是很伟大的创造,伟大的“技术”,创造的社会效益显而易见。

5、脱离商业的技术不存在,计算炮弹轨迹的需求诞生了计算机,美国国防部催生了互联网,网络购物的需要诞生了淘宝,在不健全的信用社会网络交易的需要诞生了支付宝。

6、关于个人崇拜,你是成年人,你有自己的价值观,你有自己的世界观,如果你那么容易被人忽悠,那也是活该。

7、如果哪天公司免费发淘公仔,我也去抢啊,哦,抢过一回口碑卡。

8、以偏概全,会掩盖了很多人的努力工作。
 



posted @ 2010-11-02 13:25 dennis 阅读(2040) | 评论 (6)编辑 收藏

   
    javaeye的一个帖子介绍一道面试题,取数组的最大元素和前n个大元素,取最大元素很简单,遍历即可。取前N大元素,可以利用排序,最简单的实现:

    public static int[] findTopNValues(int[] anyOldOrderValues, int n) {
        Arrays.sort(anyOldOrderValues);
        
int[] result = new int[n];
        System.arraycopy(anyOldOrderValues, anyOldOrderValues.length 
- n,
                result, 
0, n);
        
return result;
    }
   
     Arrays.sort(int[])使用的是快排,平均的时间复杂度是O( n lg(n)),在一般情况下已经足够好。那么有没有平均情况下O(n)复杂度的算法?这个还是有的,这道题目其实是选择算法的变形,选择一个数组中的第n大元素,可以采用类似快排的方式划分数组,然后只要在一个子段做递归查找就可以,平均状况下可以做到O(n)的时间复杂度,对于这道题来说稍微变形下算法即可解决:

    /**
     * 求数组的前n个元素
     * 
     * 
@param anyOldOrderValues
     * 
@param n
     * 
@return
     
*/
    
public static int[] findTopNValues(int[] anyOldOrderValues, int n) {
        
int[] result = new int[n];
        findTopNValues(anyOldOrderValues, 
0, anyOldOrderValues.length - 1, n,
                n, result);
        
return result;
    }

    
public static final void findTopNValues(int[] a, int p, int r, int i,
            
int n, int[] result) {
        
// 全部取到,直接返回
        if (i == 0)
            
return;
        
// 只剩一个元素,拷贝到目标数组
        if (p == r) {
            System.arraycopy(a, p, result, n 
- i, i);
            
return;
        }
        
int len = r - p + 1;
        
if (i > len || i < 0)
            
throw new IllegalArgumentException();
        
// if (len < 7) {
        
// Arrays.sort(a, p, r+1);
        
// System.arraycopy(a, r - i+1 , result, n - i, i);
        
// return;
        
// }

        
// 划分
        int q = medPartition(a, p, r);
        
// 计算右子段长度
        int k = r - q + 1;
        
// 右子段长度恰好等于i
        if (i == k) {
            
// 拷贝右子段到结果数组,返回
            System.arraycopy(a, q, result, n - i, i);
            
return;
        } 
else if (k > i) {
            
// 右子段比i长,递归到右子段求前i个元素
            findTopNValues(a, q + 1, r, i, n, result);
        } 
else {
            
// 右子段比i短,拷贝右子段到结果数组,递归左子段求前i-k个元素
            System.arraycopy(a, q, result, n - i, k);
            findTopNValues(a, p, q 
- 1, i - k, n, result);
        }
    }

    
public static int medPartition(int x[], int p, int r) {
        
int len = r - p + 1;
        
int m = p + (len >> 1);
        
if (len > 7) {
            
int l = p;
            
int n = r;
            
if (len > 40) { // Big arrays, pseudomedian of 9
                int s = len / 8;
                l 
= med3(x, l, l + s, l + 2 * s);
                m 
= med3(x, m - s, m, m + s);
                n 
= med3(x, n - 2 * s, n - s, n);
            }
            m 
= med3(x, l, m, n); // Mid-size, med of 3
        }
        
if (m != r) {
            
int temp = x[m];
            x[m] 
= x[r];
            x[r] 
= temp;
        }
        
return partition(x, p, r);
    }

    
private static int med3(int x[], int a, int b, int c) {
        
return x[a] < x[b] ? (x[b] < x[c] ? b : x[a] < x[c] ? c : a)
                : x[b] 
> x[c] ? b : x[a] > x[c] ? c : a;
    }

    
public static void swap(int[] a, int i, int j) {
        
int temp = a[i];
        a[i] 
= a[j];
        a[j] 
= temp;
    }

    
public static int partition(int a[], int p, int r) {
        
int x = a[r];
        
int m = p - 1;
        
int j = r;
        
while (true) {
            
do {
                j
--;
            } 
while (j>=p&&a[j] > x);
            
do {
                m
++;
            } 
while (a[m] < x);
            
            
if (j < m)
                
break;
            swap(a, m, j);
        }
        swap(a, r, j 
+ 1);
        
return j + 1;
    }
 选择算法还有最坏情况下O(n)复杂度的实现,有兴趣可以读算法导论和维基百科。题外,我测试了下这两个实现,在我的机器上大概有2倍多的差距,还是很明显。

posted @ 2010-10-28 10:53 dennis 阅读(3151) | 评论 (8)编辑 收藏

仅列出标题
共56页: First 上一页 4 5 6 7 8 9 10 11 12 下一页 Last