1、redishash是一个string类型的field和value的映射表。
2、hash特别适合用于存储对象。相较于将对象的每个字段存成单个 string 类型。将一个对象存储在hash类 型中会占用更少的内存,并且可以更方便的存取整个对象。
3、新建一个 hash 对象时开始是用 zipmap(又称为 small hash)来存储的。
4、如果 field 或者 value 的大小超出一定限制后,redis会在内部自动将zipmap替换成正常的 hash 实现. 这个限制可以在配置文件中指定:

hash-max-zipmap-entries 64 #配置字段最多 64
hash-max-zipmap-value 512 #配置 value 最大为 512 字节

下面介绍 hash 相关命令:

hset key field value    #设置 hash field 为指定值,如果 key 不存在,则先创建

hget key field        #获取指定的 hash field

hmget key filed1....fieldN        #获取全部指定的 hash filed

hmset key filed1 value1 ... filedN valueN        #同时设置 hash 的多个 field

hincrby key field integer        #将指定的 hash filed 加上给定值

hexists key field        #测试指定 field 是否存在

hdel key field        #删除指定的 hash field

hlen key    #返回指定 hash 的 field 数量

hkeys key    #返回 hash 的所有 field

hvals key    #返回 hash 的所有 value

hgetall key        #返回 hash 的所有 filed 和 value

1、set 一样,sorted set 也是 string 类型元素的集合,不同的是每个元素都会关联一个 double 类型的 score。
2、sorted set 的实现是 skip list 和 hash table 的混合体。
3、sorted set 最经常的使用方式应该是作为索引来使用。我们可以把要排序的字段作为 score 存储,对象的 id 当元素存储。

下面是 sorted set 相关命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
zadd key score member	#添加元素到集合,元素在集合中存在则更新对应 score

zrem key member #删除指定元素,1 表示成功,如果元素不存在返回 0

zincrby key incr member #增加对应 member 的 score 值,然后移动元素并保持 skip list 保持有序。返回更新后的 score 值

zrank key member #返回指定元素在集合中的排名(下标),集合中元素是按 score 从小到大排序的

zrevrank key member #同上,但是集合中元素是按 score 从大到小排序

zrange key start end #类似 lrange 操作从集合中去指定区间的元素。返回的是有序结果

zrevrange key start end #同上,返回结果是按 score 逆序的

zrangebyscore key min max #返回集合中 score 在给定区间的元素

zcount key min max #返回集合中 score 在给定区间的数量

zcard key #返回集合中元素个数

zscore key element #返回给定元素对应的 score

zremrangebyrank key min max #删除集合中排名在给定区间的元素

zremrangebyscore key min max #删除集合中 score 在给定区间的元素

1、redis 的 set 是 string 类型的无序集合
2、set 元素最大可以包含(2的32次方-1)个元素
3、set 的是通过 hash table 实现的,所以添加,删除,查找的复杂度都是 O(1)。

相关命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
sadd key member		#添加一个 string 元素到,key 对应的 set 集合中,成功返回 1,如果元素已经在集合中,返回 0,key 对应的 set 不存在返回错误

srem key member #从 key 对应 set 中移除给定元素,成功返回 1,如果 member 在集合中不存在或者 key 不存在返回 0,如果 key 对应的不是 set 类型的值返回错误

spop key #删除并返回 key 对应 set 中随机的一个元素,如果 set 是空或者 key 不存在返回 nil

srandmember #同 spop,随机取 set 中的一个元素,但是不删除元素

smove srckey dstkey member #从 srckey 对应 set 中移除 member 并添加到 dstkey 对应 set 中,整个操作是原子的。成功返回 1,如果 member 在 srckey 中不存在返回 0,如果 key 不是 set 类型返回错误

scard key #返回 set 的元素个数,如果 set 是空或者 key 不存在返回 0

sismember key member #判断 member 是否在 set 中,存在返回 1,0 表示不存在或者 key 不存在

sinter key1 key2...keyN #返回所有给定 key 的交集

sinterstore dstkey key1...keyN #同 sinter,但是会同时将交集存到 dstkey 下

sunion key1 key2...keyN #返回所有给定 key 的并集

sunionstore dstkey key1...keyN #同 sunion,并同时保存并集到 dstkey 下

sdiff key1 key2...keyN #返回所有给定 key 的差集

sdiffstore dstkey key1...keyN #同 sdiff,并同时保存差集到 dstkey 下

smembers key #返回 key 对应 set 的所有元素,结果是无序的 

1、redis 的 list 类型其实就是一个每个子元素都是 string 类型的双向链表,所以[lr]push 和[lr]pop 命 令的算法时间复杂度都是 O(1)。
2、list 会记录链表的长度,所以 llen 操作也是 O(1)。
3、链表的最大长度是(2的32次方-1)
4、 list 的 pop 操作还有阻塞版本的。当我们[lr]pop 一个 list对象是,如果list是空,或者不存在,会立即返回nil。但是阻塞版本的b[lr]pop可以则可以阻塞, 当然可以加超时时间,超时后也会返回 nil。阻塞版本的pop主要是为了避免轮询。

list相关命令:

lpush key string

key 对应 list 的头部添加字符串元素,返回 1 表示成功,0 表示 key 存在且不是 list 类型

rpush key string 

同上,在尾部添加

llen key

返回 key 对应 list 的长度,key 不存在返回 0,如果 key 对应类型不是 list 返回错误

lrange key start end 

返回指定区间内的元素,下标从 0 开始,负值表示从后面计算,-1 表示倒数第一个元素 ,key 不存在返回空列表

ltrim key start end

截取 list,保留指定区间内元素,成功返回 1,key 不存在返回错误

lset key index value

设置 list 中指定下标的元素值,成功返回 1,key 或者下标不存在返回错误

lrem key count value

从 key 对应 list 中删除count个和value相同的元素。count 为 0 时候删除全部。返回被移除元素的数量

lpop key

从 list 的头部删除元素,并返回删除元素。如果 key 对应 list 不存在或者是空返回 nil,如果 key 对应值不是 list 返回错误

rpop key

同上,但是从尾部删除

blpop key1...keyN timeout

从左到右扫描返回对第一个非空 list 进行 lpop 操作并返回,比如 blpop list1 list2 list3 0 ,如果 list 不存在,list2,list3 都是非空则对 list2 做 lpop 并返回从 list2 中删除的元素。如果所有的 list 都是空或不存在,则会阻塞 timeout 秒,timeout 为 0 表示一直阻塞。当阻塞时,如果有client 对 key1…keyN 中的任意 key 进行 push 操作,则第一在这个 key 上被阻塞的 client 会立即返回。如果超时发生,则返回 nil。有点像 unix 的 select 或者 poll。

brpop key1...keyN timeout

同 blpop,一个是从头部删除一个是从尾部删除。

rpoplpush srckey destkey

从 srckey 对应 list 的尾部移除元素并添加到 destkey 对应 list 的头部,最后返回被移除的元素值,整个操作是原子的.如果 srckey 是空或者不存在返回 nil。

总结:
1、关于lpush和rpush
当key不存在时则创建key并添加
2、关于lrem
count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。
count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。
count = 0 : 移除表中所有与 value 相等的值。
因为不存在的 key 被视作空表(empty list),所以当 key 不存在时, LREM 命令总是返回 0

1、string 是 redis 最基本的类型
2、string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如 jpg 图片或者序列化的对象
3、内部实现来看其实 string 可以看作 byte 数组,最大 上限是 1G 字节
4、sting可以被部分命令当做int处理,如incr

string类型的定义:

1
struct sdshdr {
long len; 		//数组中剩余可用字节数
long free; 		//buf数组的长度
char buf[]		//用于存贮实际的字符串内容的数组;
};

string相关命令如下

set key value 

设置 key 对应的值为 string 类型的 value,返回 1 表示成功,0 失败

setnx key value

同上,但如果 key 已经存在,返回 0 。nx 是 not exist 的意思

get key

获取 key 对应的 string 值,如果 key 不存在返回 nil

getset key value

原子的设置 key 的值,并返回 key 的旧值。如果 key 不存在返回 nil

mget key1 key2 ... keyN

一次获取多个 key 的值,如果对应 key 不存在,则对应返回 nil

mset key1 value1 ...keyN value N

一次设置多个 key 的值,成功返回1,表示所有的值都设置了;失败返回0,表示没有任何值被设置

msetnx key1 value1 ...keyN valueN

同上,但是不会覆盖已经存在的 key

incr key

对 key 的值做加加操作,并返回新的值。
注意:incr 一个不是 int 的 value 会返回错误;incr一个不存在的 key,则设置 value 为 1

decr

同上,但是做的是减减操作,decr 一个不存在 key,则设置 key 为-1

incrby key integer

同 incr,加指定值 ,key 不存在时候会设置 key,并认为原来的 value 是 0

decrby key integer

同 decr,减指定值。
decrby完全是为了可读性,我们完全可以通过 incrby 一个负
值来实现同样效果,反之一样。

append key value

给指定 key 的字符串值追加 value,返回新字符串值的长度。

substr key start end

返回截取过的 key 的字符串值。
注意该命令并不修改 key 的值,下标是从 0 开始的

总结:
1、关于mget,如果某个key不存在,只有不存在的key返回nil,其他key任正常返回,例子:

1
2
3
redis> flushdb OK
redis> dbsize (integer) 0
redis> set k1 a OK redis> set k2 b OK redis> mget k1 k2 k3
1. "a" 2. "b" 3. (nil)

2、关于mset和msetnx
mset会覆盖已存在的key
msetnx只要有一个key已存在就返回0,表示没有任何值被设置
3、关于incrby、decrby
当value不是一个int类型时会返回失败

redis本质上一个 key-value数据库,关于key注意以下几点:
1、其中key也是字符串类型,但是key中不能包括边界字符。
2、由于key不是二进制安全(binary safe)的字符串,所以像”my key”和”mykey\n”这样包含空格和换行的 key 是不允许的。
3、redis内部并不限制使用binary字符,这是redis协议限制的。”\r\n”在协议格式中会作为特殊字符。

关于key的一个格式约定介绍下, object-type:id:field。比如 user:1000:password,blog:xxidxx:title。
key的长度最好不要太长,占内存,而且查找时候比短key慢。不过也不推荐过短的key,比如 u:1000:pwd,这的,可读性太差。

下面是key相关命令:

exits key    

测试指定 key 是否存在,返回 1 表示存在,0 不存在

del key1 key2...keyN    

删除给定 key,返回删除 key 的数目,0 表示给定 key 都不存在

type key

返回给定 key 的 value 类型。返回 none表示不存在,string字符类型,list链表类型,set无序集合类型…

keys pattern    

返回匹配指定模式的所有 key,下面给个例子

randomkey

返回从当前数据库中随机选择的一个 key,如果当前数据库是空的,返回空串

rename oldkey newkey

原子的重命名一个 key,如果 newkey已存在,将会被覆盖,返回 1 表示成功,0 失败。可能是oldkey不存在或者oldkey和newkey相同。

renamenx oldkey newkey

同上,但是如果 newkey 存在返回失败(0)

dbsize

返回当前数据库的key的数量

expire key seconds

为key指定过期时间,单位是秒,返回1成功,0表示key已经设置过过期时间,或者key不存在

ttl key

返回设置过过期时间的 key 的剩余过期秒数 -1 表示 key 不存在或者没有设置过过期时间

select db-index

通过索引选择数据库,默认连接的数据库是0,默认数据库数是16个。返回1表示切换连接成功,0表示失败

move key db-index 

将key从当前数据库移动到指定数据库。返回1成功。0如果key不存在,或者已经在指定数据库中

flushdb

删除当前数据库中所有 key,此方法不会失败。慎用

flushall

删除所有数据库中的所有 key,此方法不会失败。更加慎用

总结:
1、关于select db-index,redis默认分割成16个数据库,默认连接到索引为0的数据库,每个数据库都是动态大小的,一开始非常小。分库作用只是提供一个简单的命名空间(namespace),以便将不同用途的数据分开而已,在同一个服务器进行分库不会带来任何效率上的提升。
2、关于move key db-index,只要目标数据库中已存在指定的key,不管value为多少都会返回失败。
3、关于keys pattern,例子:

1
2
redis> set test dsf OK
redis> set tast dsaf OK
redis> set tist adff OK
redis> keys t*
1. "tist"
2. "tast"
3. "test"
redis> keys t[ia]st
1. "tist" 2. "tast" redis> keys t?st 1. "tist" 2. "tast" 3. "test"

http://redis.io/download
下载最新的Redis版本,我下载的是3.0.3
下载后将文件解压,通过进入文件夹

cd redis-3.0.3

执行

make install

Redis就安装好了,下面是启动指令:

#启动 加上‘&’使redis能在后台运行
src/redis-service &
#关闭
src/redis-cli shutdown

测试

localhost:redis-3.0.3 ywq$ src/redis-cli 
127.0.0.1:6379> set foo test
OK
127.0.0.1:6379> get foo
"test"

一、什么是RTTI
RTTI就是Run-Time Type Information的缩写,Java使用Class对象来实现RTTI。

二、如何实现RTTI
每当编写并且编译一个新类时,就会产生一个Class对象(保存在同名的.class文件中),获取Class对象有以下几种方式:

1、Class.forName(“className”);
2、类字面常量:eg: User.class;
不光可用于普通类,也可用于接口、数字、基本数据类型。
特殊的,基本数据类型的类字面常亮等价于对应包装类的TYPE字段(eg:int.class和Integer.TYPE等价);
3、如果已经有了一个对象的引用(非Class对象),可以通过调用该对象的.getClass()方法获取;

使用类的准备工作实际包括3个步骤:
1、加载:这是由类加载器执行的,该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非必须的),并从这些字节码中创建一个Class对象;
2、链接:在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将创建这个类对其他类的所有引用;
3、初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块;

通过Class.forName(“className”)方式创建Class对象的引用时会初始化Class对象,而.class方式不会初始化Class对象,具体什么时候初始化可参考:satic块

三、Class对象的常用方法
getName 获取全限定名(即带包名的类名)
getSimpleName() 获取不含包名的类名
getCononicalName() 获取全限定名
getInterfaces() 返回Class对象,他们表示表示调用该方法的Class对象中的全部接口
isInterface() 告诉你这个Class对象是否表示某个接口
newInstance() 实例化一个对象

调用newInstance时,如果使用泛型时,用?通配、super通配或不使用泛型将返回Object类型的值,如果用extends通配或指定具体类型能返回具体类型的值,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.....
Class c1 = User.class;
Class<?> c2 = User.class;
Class<? super User> c3 = User.class; //super通配父类,但不知道是具体哪个父类,所以向上转型到Object
Class<User> c4 = User.class;
Class<? extends User> c5 = User.class;
try {
User nu1 = (User)c1.newInstance(); //返回值为Object类型,需要强制类型转换
User nu2 = (User)c2.newInstance();
User nu3 = (User)c3.newInstance(); //直接返回User类型,不需要强制类型转换
User nu4 = c4.newInstance();
User nu5 = c5.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}

四、instanceof
两种方式直接上示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class User{}

public class Administrator exnteds User{}

public class Test{
public static void main(String[] args){
Administrator admin = new Administrator();

//方式一
if(admn instanceof User)
System.out.println("admin is instance of User");
//方式二
Class c = User.class;
if(c.isInstanceof(admin))
System.out.println("admin is also instance of User");
}
}

注意instanceof不仅可用于类,还可以用于接口,当对象为null时返回false

晚上看Spring-Data-Redis,别人的demo里通过对匿名内部类的回调实现set、get方法,仔细想以下,发现自己对匿名内部类掌握不是很好,就又翻了下Thinking in Java。

匿名内部类的格式:

1
2
3
new Contents(){
...
}

我对匿名内部类主要有以下几个疑问:

一、匿名内部类实现的时什么类型的对象,可以实现接口吗?

Contents()可以是调用的一个类的构造器,此时括号中可以根据构造器传入参数,将创建一个继承自Contents的匿名类对象;

Contents也可以是一个接口,将创建一个实现Contents接口的匿名类对象,此时匿名类中只有一个隐式的无参构造器,所以括号中不能传入参数;

二、如何引用匿名内部类?

回答了问题一其实就能解决这个问题了,new表达式返回的引用会被自动向上转型为对Contents的引用

三、如何实现类似构造器的效果?

通过实例初始化,就像这样:

1
2
3
4
new Contents(){
{print("instance init");}
.....
}



使用匿名内部类还需要注意以下问题:

匿名内部类中可以直接调用外部定义的对象,但编译器会要求该对象的引用必须是final的,例如:
1
2
3
4
5
6
public Contents getContents(final String param){
return new Contents(){
String label = param;
.....
}
}

学习redis时,看到一致性哈希。


一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似。一致性哈希修正了CARP使用的简 单哈希算法带来的问题,使得分布式哈希(DHT)可以在P2P环境中真正得到应用。一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义:

1、平衡性(Balance):平衡性是指哈希的结果能够尽可能分布到所有的node中去,这样可以使得所有的node都得到利用。很多哈希算法都能够满足这一条件(即hash的结果应该平均分配到各个node, 这样从算法上就解决了负载均衡问题)。

2、单调性(Monotonicity):单调性是指如果已经有一些内容通过哈希分派到了相应的node中,又有新的node加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到原有的或者新的node中去,而不会被映射到旧的node集合中的其他node(即在新增或者删减节点时, 同一个key访问到的值总是一样的)。

3、分散性(Spread):在分布式环境中,终端有可能看不到所有的node,而是只能看到其中的一部分。当终端希望通过哈希过程将内容映射到node上时,由于不同终端所见的node集合有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓冲区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同node中去,降低了系统存储的效率。分散性的定义就是上述情况发生的严重程度。好的哈希算法应能够尽量避免不一致的情况发生,也就是尽量降低分散性(即数据应该分散的存放在 分布式集群中的各个节点(节点自己可以有备份), 不必要每个节点都存储所有的数据)。

4、负载(Load):负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的node中,那么对于一个特定的node而言,也可能被不同的用户映射为不同的内容。导致同一个node被大量访问。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低node的负荷(即尽量将访问分散到不同node)。


为什么要一致性哈希?(why)

不使用一致性哈希时(不使用哈希或使用普通哈希),增减node几乎所有key的映射要重新维护。


使用一致性哈希时,key映射到固定数量的虚拟节点上,然后将虚拟节点的 哈希值mod(虚拟节点总数/节点数),来选取node,例如一个key哈希后值为28,虚拟节点40个,节点有5个,节点编号从1开始,则应该存放在node4 (28mod(40/5)+1=4),key值为39时则存放在node1,如下图所示:



使用一致性哈希后,用节点将虚拟节点等分作为分割点,每次增减节点时只需要迁移每个分割点移动范围内的数据。