excel读书心得

上周因为要处理一些数据的统计分析,没有自己写脚本分析,而是采用了excel进行处理。 不过,我以前没用过excel的统计分析功能,于是找了本书来学习一下,现学现用。这本书的书名叫«你早该这么学excel»。

我想很多人学excel,都感觉需要好多技巧,外边大多数的书籍也是教技巧的。可惜这本书没介绍什么技巧,只是介绍了处理数据的一些原则。 反正,我感觉效果挺好的,根据这本书学习了一下,也能完成我想做的事情,并且显得有模有样,这就行了。

书里边介绍了作者伍昊的三大表理论。其中天下第一表,就是原始数据表,首要就是表格规范,数据格式化,无统计信息,表格列要齐全。 所有的统计都是根据原始数据表,并通过透视表来做的。当然,对于稳定的数据,他使用参数表的概念,采用自动生成方式。

这套原则很简单,并且有效。推荐大家看看,特别是经常接触excel的童鞋。

DES加解密总结

DES是广泛使用的分组对称加密算法,它要求待加密数据要8位对齐,所以在数据不足8位时候会出现padding的情况, 所以有可能因为padding不同,而出现加解密结果不一样,这种情况在异构系统间的数据通信特别容易出现,例如java和cpp系统之间的通信。 如果在开发过程中,遇到不同语言加解密结果不一样的情况,应该关注一下补齐方式。 关于补齐方式,请参考http://en.wikipedia.org/wiki/Padding_%28cryptography%29

先说说java这里,java api已经集成了DES加解密算法,如果不指定补齐方式的话,默认的补齐方式是使用PKCS5Padding, 这种情况下,长度不足8字节的部分,填充“0x01”—“0x08”,如不 足1字节,则填充1个“0x01”,如不足2字节,则填充2个“0x02”,以此类推。更具 体的描述,参考What Is PKCS5Padding? 如果我们指定补齐方式,如使用DES/ECB/NoPadding的话(其中ECB是加密的模式,也是默认的加密方式),就需要自己手动补齐,例如补空格字符。 现在的代码为了和后台的加解密结果一致,采用的是手动补齐的方式,即NoPadding方式。

如果cpp实现的padding方式和java的不一致,就可能导致加解密失败。在我们的系统里边,cpp实现的padding是补\0的。 这个字符在显示的时候就直接被当成结束符了,无需特殊处理。所以,如果使用java加密,cpp解密的话, java这边也要使用补\0的做法,如果使用java解密的话,就只能使用trim进行处理了。

以我们这边使用的两个DES相关的接口为例,用户鉴权接口在配置里边有使用trim进行处理,而wlan密码认证接口没有使用trim处理。 因此按现在使用补空格的方法,在用户鉴权接口是能成功的,但在wlan密码认证接口里边是会导致认证失败的。

如果需要更多关于加密补齐的资料,可以参考Using Padding in Encryption

那些年,我读过的技术书

这几天发烧很反复,反而有点时间用来整理一下思路。想了很多以前的事情,回忆的感觉是很复杂的。 突然想写写接触的技术书,现在工作的时间占据了大多数,看书的时间并不多, 但是每年看的书是有点越来越多的感觉,只是很少有特别深刻的,大概是因为平时的读书习惯就是不求甚解,又或者是这种方式很适合我。

C程序设计,谭浩强的教材估计是人手一册了。这也是我第一次接触编程语言,虽然我还是没学懂怎么写代码。 回头来看,这本书的确写得不怎样,比起那本c语言传世之作差太远了,里边错误也不少。

数据结构(用面向对象方法与C++语言描述),黄色皮的这本,应该是第一版。 说起这本书,我当年也是没学明白,主要是我太懒了,还有就是这语言真有点复杂。 这门课程每一两周就有一个课程设计,要编程的那种。我基本都是抄袭的,有一次我下决心想自己解决, 结果就是整了一通宵,双向链表编译都没通过。不过还好老师手下留情,最后还是62分勉强通过。 这门课程是到了毕业以后花3个月自学的,算是自己迄今为止看得最认真的一本书了,简直可以用改变我的职业生涯来形容。

计算机网络,第四版,这也是大学的教材,纯英文版本,这本书是认识网络的通用书籍,是本很好的书, 不过,并不是我从上面学到多少,我也是毕业以后才回去读的。我对这本书很有印象,主要是因为有一次我在图书馆, 看到我同学的书已经翻烂了,而我的书还是新的,要知道这是一本大几百页的英文书。顿时觉着这个世界很可怕, 别说自己不努力,即使你再努力,总有比你聪明的人比你还努力。难怪人家成绩如此的好。

Linux系统管理技术手册,这是我毕业以后买的一本大部头英文书。我对linux有个比较系统的认识也源于这本书。 不知有没有人在装系统的时候不小心把win也删除了没有?我就试过,后来在一次面试中被人当笑话了。 虽然工作的时候也有接触这linux,但那一年春节放假的时候,我觉得我应该认真学习一下,而且我们还是有十几天假期的。 于是到书店把这本书扛回去了,硬是在春节的时候看完。当然,只是看书很多是没概念的,于是我把家里的系统格了换成linux,公司也装上双系统,强迫自己适应。我对以前的公司环境还是心存感激的,很free的感觉。

设计模式,四人帮的大作,我买了有好几年了,一直来来去去的翻,始终没有通读,而我也读过不少模式方面的书, 但这本书一直保留在我的在读列表中,总感觉没读懂。直到最近这两年,有些开窍了,有时虽然不能想到利用哪种模式, 或者是哪种模式,但直觉告诉我,就应该这么写。我想,我逐渐从这些设计模式上找到设计原则的味道了。

其他书籍?没有第一时间在我脑子里边出现,即使我认同是一本好书,但也就仅仅是一本好书罢了。 相对来说,我很少看java的专题书,我跟偏爱通用类。而数据库,即使毕业后一两年, 我都是对数据库技术有点不屑的感觉,所以学得很少,直到最近这两年我才接触得多一些。

娱乐而已,勿当真。

redis简报

REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统, 与memcached相比,redis支持更丰富的数据结构,特点是高性能、持久存储,适应高并发的应用场景。它起步较晚,发展迅速, 目前已被许多大型机构采用,比如Twitter、Github、新浪微博等。

Redis安装

redis的安装没有其他依赖,非常简单。操作如下:

wget http://download.redis.io/releases/redis-2.6.16.tar.gz
tar zxvf redis-2.6.16.tar.gz
cd redis-2.6.16/
make

这样就会在src目录下生成以下几个可执行文件:redis-benchmark、redis-check-aof、redis-check-dump、redis-cli、redis-server。 这几个文件加上redis.conf就是redis的最终可用包了。可以考虑把这几个文件拷贝到你希望的地方。例如:

mkdir -p /usr/local/redis/bin
mkdir -p /usr/local/redis/etc
cp redis.conf /usr/local/redis/etc
cd src
cp redis-benchmark redis-check-aof redis-check-dump redis-cli redis-server /usr/local/redis/bin

现在就可以启动redis了。

cd /usr/local/redis
bin/redis-server etc/redis.conf

注意,默认复制过去的redis.conf文件的daemonize参数为no,所以redis不会在后台运行,可以修改为yes则为后台运行redis。 另外配置文件中规定了pid文件,log文件和数据文件的地址,如果有需要先修改,默认log信息定向到stdout. 这时候就可以打开终端进行测试了,默认的监听端口是6379,所以用telnet进行连接如下:

# telnet localhost 6379
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
SET hello world
+OK
GET hello
$5
world
quit
+OK
Connection closed by foreign host.

或者使用redis-cli客户端:

# redis-cli
redis 127.0.0.1:6379> SET hello world
OK
redis 127.0.0.1:6379> GET hello
"world"

redis数据结构

redis相对memcached来说,支持更加丰富的数据结构,正如作者所说的,redis是一个数据结构服务器(data structures server), redis的所有功能就是将数据以其固有的几种结构保存,并提供给用户操作这几种结构的接口。redis目前支持以下几种数据类型:

  • String类型

字符串是最简单的类型,和memcached支持的类型是一样的,但是在功能上更加丰富。

redis 127.0.0.1:6379> SET name "Redis 2.6.16"
OK
redis 127.0.0.1:6379> GET name
"Redis 2.6.16"

另外,它还支持批量读写:

redis 127.0.0.1:6379> MSET age 30 sex Male
OK
redis 127.0.0.1:6379> MGET age sex
1) "30"
2) "Male"

还可以当成数字来使用,并支持对数字的加减操作:

redis 127.0.0.1:6379> INCR age
(integer) 31
redis 127.0.0.1:6379> INCRBY age 2
(integer) 33
redis 127.0.0.1:6379> GET age
"33"
redis 127.0.0.1:6379> DECR age
(integer) 32
redis 127.0.0.1:6379> DECRBY age 2
(integer) 30
redis 127.0.0.1:6379> GET age
"30"

还支持对字符串进行部分修改或获取操作

redis 127.0.0.1:6379> STRLEN name
(integer) 12
redis 127.0.0.1:6379> GETRANGE name 0 4
"Redis"
redis 127.0.0.1:6379> APPEND name ", NoSQL"
(integer) 19
redis 127.0.0.1:6379> GET name
"Redis 2.6.16, NoSQL"
  • List类型

Redis能够把数据存储成一个链表,并能对这个链表进行操作:

redis 127.0.0.1:6379> LPUSH language Java
(integer) 1
redis 127.0.0.1:6379> LPUSH language C++
(integer) 2
redis 127.0.0.1:6379> RPUSH language C
(integer) 3
redis 127.0.0.1:6379> LLEN language
(integer) 3
redis 127.0.0.1:6379> LRANGE language 0 2
1) "C++"
2) "Java"
3) "C"
redis 127.0.0.1:6379> LPOP language
"C++"
redis 127.0.0.1:6379> LLEN language
(integer) 2
redis 127.0.0.1:6379> LREM language 1 Java
(integer) 1
redis 127.0.0.1:6379> LLEN language
(integer) 1

Redis也支持很多修改操作

redis 127.0.0.1:6379> LRANGE language 0 2
1) "C"
redis 127.0.0.1:6379> LINSERT language BEFORE C C++
(integer) 2
redis 127.0.0.1:6379> LINSERT language BEFORE C Java
(integer) 3
redis 127.0.0.1:6379> LLEN language
(integer) 3
redis 127.0.0.1:6379> LRANGE language 0 2
1) "C++"
2) "Java"
3) "C"
redis 127.0.0.1:6379> LTRIM language 2 -1
OK
redis 127.0.0.1:6379> LLEN language
(integer) 1
redis 127.0.0.1:6379> LRANGE language 0 2
1) "C"
  • Sets类型

Redis能够将一系列不重复的值存储成一个集合,并支持修改和集合关系操作。

redis 127.0.0.1:6379> SADD system Win
(integer) 1
redis 127.0.0.1:6379> SADD system Linux
(integer) 1
redis 127.0.0.1:6379> SADD system Mac
(integer) 1
redis 127.0.0.1:6379> SADD system Linux
(integer) 0
redis 127.0.0.1:6379> SMEMBERS system
1) "Win"
2) "Mac"
3) "Linux"

Sets结构也支持相应的修改操作

redis 127.0.0.1:6379> SREM system Win
(integer) 1
redis 127.0.0.1:6379> SMEMBERS system
1) "Mac"
2) "Linux"
redis 127.0.0.1:6379> SADD system Win
(integer) 1
redis 127.0.0.1:6379> SMEMBERS system
1) "Mac"
2) "Win"
3) "Linux"

Redis还支持对集合的子交并补等操作

redis 127.0.0.1:6379> SADD phone Android
(integer) 1
redis 127.0.0.1:6379> SADD phone Iphone
(integer) 1
redis 127.0.0.1:6379> SADD phone Win
(integer) 1
redis 127.0.0.1:6379> SMEMBERS phone
1) "Win"
2) "Iphone"
3) "Android"
redis 127.0.0.1:6379> SINTER system phone
1) "Win"
redis 127.0.0.1:6379> SUNION system phone
1) "Win"
2) "Iphone"
3) "Mac"
4) "Linux"
5) "Android"
redis 127.0.0.1:6379> SDIFF system phone
1) "Mac"
2) "Linux"
  • Sorted Sets类型

Sorted Sets和Sets结构非常相似,不同的是Sorted Sets中的数据会有一个score属性,并会在写入时就按这个score排好序。

redis 127.0.0.1:6379> ZADD days 0 mon
(integer) 1
redis 127.0.0.1:6379> ZADD days 1 tue
(integer) 1
redis 127.0.0.1:6379> ZADD days 2 wed
(integer) 1
redis 127.0.0.1:6379> ZADD days 3 thu
(integer) 1
redis 127.0.0.1:6379> ZADD days 4 fri
(integer) 1
redis 127.0.0.1:6379> ZADD days 5 sat
(integer) 1
redis 127.0.0.1:6379> ZADD days 6 sun
(integer) 1
redis 127.0.0.1:6379> ZCARD days
(integer) 7
redis 127.0.0.1:6379> ZRANGE days 0 6
1) "mon"
2) "tue"
3) "wed"
4) "thu"
5) "fri"
6) "sat"
7) "sun"
redis 127.0.0.1:6379> ZSCORE days sat
"5"
redis 127.0.0.1:6379> ZCOUNT days 3 6
(integer) 4
redis 127.0.0.1:6379> ZRANGEBYSCORE days 3 6
1) "thu"
2) "fri"
3) "sat"
4) "sun"
  • Hash类型

Redis能够存储多个键值对的数据

redis 127.0.0.1:6379> HMSET student name Tom age 12 sex Male
OK
redis 127.0.0.1:6379> HKEYS student
1) "name"
2) "age"
3) "sex"
redis 127.0.0.1:6379> HVALS student
1) "Tom"
2) "12"
3) "Male"
redis 127.0.0.1:6379> HGETALL student
1) "name"
2) "Tom"
3) "age"
4) "12"
5) "sex"
6) "Male"
redis 127.0.0.1:6379> HDEL student sex
(integer) 1
redis 127.0.0.1:6379> HGETALL student
1) "name"
2) "Tom"
3) "age"
4) "12"

Redis能够支持Hash的批量修改和获取

redis 127.0.0.1:6379> HMSET kid name Akshi age 2 sex Female
OK
redis 127.0.0.1:6379> HMGET kid name age sex
1) "Akshi"
2) "2"
3) "Female"

在这些数据结构的基础上,跟memcached一样,Redis也支持设置数据过期时间,并支持一些简单的组合型的命令。

设置数据过期时间

redis 127.0.0.1:6379> SET name "John Doe"
OK
redis 127.0.0.1:6379> EXISTS name
(integer) 1
redis 127.0.0.1:6379> EXPIRE name 5
(integer) 1

5秒后再查看

redis 127.0.0.1:6379> EXISTS name
(integer) 0
redis 127.0.0.1:6379> GET name
(nil)

简单的组合型的命令。通过MULTI和EXEC,将几个命令组合起来执行

redis 127.0.0.1:6379> SET counter 0
OK
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> INCR counter
QUEUED
redis 127.0.0.1:6379> INCR counter
QUEUED
redis 127.0.0.1:6379> INCR counter
QUEUED
redis 127.0.0.1:6379> EXEC
1) (integer) 1
2) (integer) 2
3) (integer) 3
redis 127.0.0.1:6379> GET counter
"3"

你还可以用DICARD命令来中断执行中的命令序列

redis 127.0.0.1:6379> SET newcounter 0
OK
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> INCR newcounter
QUEUED
redis 127.0.0.1:6379> INCR newcounter
QUEUED
redis 127.0.0.1:6379> INCR newcounter
QUEUED
redis 127.0.0.1:6379> DISCARD
OK
redis 127.0.0.1:6379> GET newcounter
"0"

JUnit参数化介绍与实践

junit在4.11版本实现了参数化功能,基于这个功能,相当于可以动态生成测试用例的。先看看官方例子:

@RunWith(Parameterized.class)
public class FibonacciTest {

    @Parameters(name = "{index}: fib({0})={1}")
    public static Iterable<Object[]> data() {
        return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
                { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
    }

    private int input;
    private int expected;

    public FibonacciTest(int input, int expected) {
        this.input = input;
        this.expected = expected;
    }

    @Test
    public void test() {
        assertEquals(expected, Fibonacci.compute(input));
    }
}

使用要点如下:

  1. 类名用@RunWith(Parameterized.class)进行注解
  2. 需要一个用于准备参数的方法,可以返回列表,但是注意列表中的元素需要是数组,对应于构造方法中的参数顺序。相当于junit会自动根据参数调用构造方法,然后再执行测试用例。这个准备参数的方法需要用@Parameters进行注解。
  3. 可以通过设置@Parameters中的name属性自定义展示的测试名称。其中{index}表示当前参数在准备好的参数列表中的索引位置,{0},{1}…表示当前用例中的第x个参数值。以上面为例,第一个用例名称就是0::fib(0)=0

我在最近的工程中,就使用了这一特性。我准备了两个目录,一个用来配置请求报文,另外一个用来配置响应报文。 就是想通过测试用来来比较实际的响应报文和期望的响应报文是否规则匹配。

一开始我使用了在一个测试用例中做这个事情,虽然用例可以跑,但几十个报文是作为一个用例来运行的, 不能直观的展现出具体报文的处理情况。

后来我采用参数化的办法,在准备参数的时候,读取这两个目录生成对应的报文列表,再由测试用例根据请求报文进行业务处理, 得到实际响应报文后,与期望的响应报文进行规则匹配。整个架子的代码写完之后,只需要配置报文就可以增加测试用例了, 更重要的是,可以直观的展现出具体报文的处理情况。示例代码如下:

@RunWith(Parameterized.class)
public class IntegrationTest {
    public IntegrationTest(String name) {
        this.name = name;
    }

    @Parameterized.Parameters(name = "{index}: {0}")
    public static Collection<String[]> data() {
        List<String[]> testData = new ArrayList<String[]>();

        ClassLoader loader = IntegrationTest.class.getClassLoader();
        String reqpath = loader.getResource("req").getPath();
        String[] files = new File(reqpath).list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith("_req.xml");
            }
        });

        for (String file : files) {
            int ldx = file.lastIndexOf("_req.xml");
            testData.add(new String[]{file.substring(0, ldx)});
        }
        return testData;
    }
    
    @Test
    public void clientTest() {
        //TODO with this.name
    }
}