簡介
Jedis Client是Redis官網推薦的一個面向java用戶端,庫檔案實作了對各類API進行封裝調用。
Jedis源碼工程位址:
https://github.com/xetorthio/jedis使用
想要使用Jedis必須加載jar包或者添加maven依賴,jar包可以自己上網下載下傳,我的是Maven項目,是以在pom.xml中增加如下語句:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
簡單的Jedis執行個體
在加載Jedis JAR包之後,我們可以直接使用建立一個Jedis執行個體的方法,來建立一個到Redis的連接配接,并進行操作,以下是一個簡單的jedis執行個體:
public void setup() {
//連接配接redis伺服器
jedis = new Jedis("172.24.4.183", 6379);
// jedis.auth("redis");//驗證密碼,如果需要驗證的話
}
/**
* 鍵操作
*/
public void testKey() throws InterruptedException{
System.out.println("清空資料:"+jedis.flushDB());
System.out.println("判斷某個鍵是否存在:"+jedis.exists("username"));
System.out.println("新增<'username','xmr'>的鍵值對:"+jedis.set("username", "xmr"));
System.out.println(jedis.exists("username"));
System.out.println("新增<'password','password'>的鍵值對:"+jedis.set("password", "123"));
System.out.print("系統中所有的鍵如下:");
Set<String> keys = jedis.keys("*");
System.out.println(keys);
System.out.println("删除鍵password:"+jedis.del("password"));
System.out.println("判斷鍵password是否存在:"+jedis.exists("password"));
System.out.println("設定鍵username的過期時間為5s:"+jedis.expire("username", 8));
TimeUnit.SECONDS.sleep(2);
System.out.println("檢視鍵username的剩餘生存時間:"+jedis.ttl("username"));
System.out.println("移除鍵username的生存時間:"+jedis.persist("username"));
System.out.println("檢視鍵username的剩餘生存時間:"+jedis.ttl("username"));
System.out.println("檢視鍵username所存儲的值的類型:"+jedis.type("username"));
}
輸出結果:
字元串操作
public void testString() throws InterruptedException {
jedis.flushDB();
System.out.println("===========增加資料===========");
System.out.println(jedis.set("key1", "value1"));
System.out.println(jedis.set("key2", "value2"));
System.out.println(jedis.set("key3", "value3"));
System.out.println("删除鍵key2:" + jedis.del("key2"));
System.out.println("擷取鍵key2:" + jedis.get("key2"));
System.out.println("修改key1:" + jedis.set("key1", "1"));
System.out.println("擷取key1的值:" + jedis.get("key1"));
System.out.println("在key3後面加入值:" + jedis.append("key3", "End"));
System.out.println("key3的值:" + jedis.get("key3"));
System.out.println("增加多個鍵值對:" + jedis.mset("key01", "value01", "key02", "value02", "key03", "value03"));
System.out.println("擷取多個鍵值對:" + jedis.mget("key01", "key02", "key03"));
System.out.println("擷取多個鍵值對:" + jedis.mget("key01", "key02", "key03", "key04"));
System.out.println("删除多個鍵值對:" + jedis.del(new String[]{"key01", "key02"}));
System.out.println("擷取多個鍵值對:" + jedis.mget("key01", "key02", "key03"));
jedis.flushDB();
System.out.println("===========新增鍵值對,防止覆寫原先值==============");
System.out.println(jedis.setnx("key1", "value1"));
System.out.println(jedis.setnx("key2", "value2"));
System.out.println(jedis.setnx("key2", "value2-new"));
System.out.println(jedis.get("key1"));
System.out.println(jedis.get("key2"));
System.out.println("===========新增鍵值對并設定有效時間=============");
System.out.println(jedis.setex("key3", 2, "value3"));
System.out.println(jedis.get("key3"));
TimeUnit.SECONDS.sleep(3);
System.out.println(jedis.get("key3"));
System.out.println("===========擷取原值,更新為新值==========");//GETSET is an atomic set this value and return the old value command.
System.out.println(jedis.getSet("key2", "key2GetSet"));
System.out.println(jedis.get("key2"));
System.out.println("獲得key2的值的字串:" + jedis.getrange("key2", 2, 4));
}
哈希操作
/**
* redis操作Hash
*/
public void testHash() {
jedis.flushDB();
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
map.put("key4", "value4");
jedis.hmset("hash", map);
jedis.hset("hash", "key5", "value5");
System.out.println("散列hash的所有鍵值對為:" + jedis.hgetAll("hash"));//return Map<String,String>
System.out.println("散列hash的所有鍵為:" + jedis.hkeys("hash"));//return Set<String>
System.out.println("散列hash的所有值為:" + jedis.hvals("hash"));//return List<String>
System.out.println("将key6儲存的值加上一個整數,如果key6不存在則添加key6:" + jedis.hincrBy("hash", "key6", 6));
System.out.println("散列hash的所有鍵值對為:" + jedis.hgetAll("hash"));
System.out.println("将key6儲存的值加上一個整數,如果key6不存在則添加key6:" + jedis.hincrBy("hash", "key6", 3));
System.out.println("散列hash的所有鍵值對為:" + jedis.hgetAll("hash"));
System.out.println("删除一個或者多個鍵值對:" + jedis.hdel("hash", "key2"));
System.out.println("散列hash的所有鍵值對為:" + jedis.hgetAll("hash"));
System.out.println("散列hash中鍵值對的個數:" + jedis.hlen("hash"));
System.out.println("判斷hash中是否存在key2:" + jedis.hexists("hash", "key2"));
System.out.println("判斷hash中是否存在key3:" + jedis.hexists("hash", "key3"));
System.out.println("擷取hash中的值:" + jedis.hmget("hash", "key3"));
System.out.println("擷取hash中的值:" + jedis.hmget("hash", "key3", "key4"));
}
清單操作
代碼如下
public void testList() {
jedis.flushDB();
System.out.println("===========添加一個list===========");
jedis.lpush("lists", "ArrayList", "Vector", "Stack", "HashMap", "WeakHashMap", "LinkedHashMap");
jedis.lpush("lists", "HashSet");
jedis.lpush("lists", "TreeSet");
jedis.lpush("lists", "TreeMap");
System.out.println("lists的内容:" + jedis.lrange("lists", 0, -1));//-1代表倒數第一個元素,-2代表倒數第二個元素
System.out.println("lists區間0-3的元素:" + jedis.lrange("lists", 0, 3));
System.out.println("===============================");
// 删除清單指定的值 ,第二個參數為删除的個數(有重複時),後add進去的值先被删,類似于出棧
System.out.println("删除指定元素個數:" + jedis.lrem("lists", 2, "HashMap"));
System.out.println("lists的内容:" + jedis.lrange("lists", 0, -1));
System.out.println("删除下表0-3區間之外的元素:" + jedis.ltrim("lists", 0, 3));
System.out.println("lists的内容:" + jedis.lrange("lists", 0, -1));
System.out.println("lists清單出棧(左端):" + jedis.lpop("lists"));
System.out.println("lists的内容:" + jedis.lrange("lists", 0, -1));
System.out.println("lists添加元素,從清單右端,與lpush相對應:" + jedis.rpush("lists", "EnumMap"));
System.out.println("lists的内容:" + jedis.lrange("lists", 0, -1));
System.out.println("lists清單出棧(右端):" + jedis.rpop("lists"));
System.out.println("lists的内容:" + jedis.lrange("lists", 0, -1));
System.out.println("修改lists指定下标1的内容:" + jedis.lset("lists", 1, "LinkedArrayList"));
System.out.println("lists的内容:" + jedis.lrange("lists", 0, -1));
System.out.println("===============================");
System.out.println("lists的長度:" + jedis.llen("lists"));
System.out.println("擷取lists下标為2的元素:" + jedis.lindex("lists", 2));
System.out.println("===============================");
jedis.lpush("sortedList", "3", "6", "2", "0", "7", "4");
System.out.println("sortedList排序前:" + jedis.lrange("sortedList", 0, -1));
System.out.println(jedis.sort("sortedList"));
System.out.println("sortedList排序後:" + jedis.lrange("sortedList", 0, -1));
}
集合(Set)操作
public void testSet() {
jedis.flushDB();
System.out.println("============向集合中添加元素============");
System.out.println(jedis.sadd("eleSet", "e1", "e2", "e4", "e3", "e0", "e8", "e7", "e5"));
System.out.println(jedis.sadd("eleSet", "e6"));
System.out.println("eleSet的所有元素為:" + jedis.smembers("eleSet"));
System.out.println("删除一個元素e0:" + jedis.srem("eleSet", "e0"));
System.out.println("eleSet的所有元素為:" + jedis.smembers("eleSet"));
System.out.println("删除兩個元素e7和e6:" + jedis.srem("eleSet", "e7", "e6"));
System.out.println("eleSet的所有元素為:" + jedis.smembers("eleSet"));
System.out.println("随機的移除集合中的一個元素:" + jedis.spop("eleSet"));
System.out.println("eleSet的所有元素為:" + jedis.smembers("eleSet"));
System.out.println("eleSet中包含元素的個數:" + jedis.scard("eleSet"));
System.out.println("e1是否在eleSet中:" + jedis.sismember("eleSet", "e1"));
System.out.println("=================================");
System.out.println(jedis.sadd("eleSet1", "e1", "e2", "e4", "e3", "e0", "e8", "e7", "e5"));
System.out.println(jedis.sadd("eleSet2", "e1", "e2", "e4", "e3", "e0", "e8"));
System.out.println("将eleSet1中删除e1并存入eleSet3中:" + jedis.smove("eleSet1", "eleSet3", "e1"));
System.out.println("eleSet1中的元素:" + jedis.smembers("eleSet1"));
System.out.println("eleSet3中的元素:" + jedis.smembers("eleSet3"));
System.out.println("============集合運算=================");
System.out.println("eleSet1中的元素:" + jedis.smembers("eleSet1"));
System.out.println("eleSet2中的元素:" + jedis.smembers("eleSet2"));
System.out.println("eleSet1和eleSet2的交集:" + jedis.sinter("eleSet1", "eleSet2"));
System.out.println("eleSet1和eleSet2的并集:" + jedis.sunion("eleSet1", "eleSet2"));
System.out.println("eleSet1和eleSet2的差集:" + jedis.sdiff("eleSet1", "eleSet2"));//eleSet1中有,eleSet2中沒有
}
============向集合中添加元素============
8
1
eleSet的所有元素為:[e0, e5, e3, e8, e7, e2, e1, e4, e6]
删除一個元素e0:1
eleSet的所有元素為:[e5, e3, e8, e7, e2, e1, e4, e6]
删除兩個元素e7和e6:2
eleSet的所有元素為:[e1, e4, e3, e5, e2, e8]
随機的移除集合中的一個元素:e1
eleSet的所有元素為:[e3, e5, e2, e8, e4]
eleSet中包含元素的個數:5
e1是否在eleSet中:false
=================================
8
6
将eleSet1中删除e1并存入eleSet3中:1
eleSet1中的元素:[e0, e5, e3, e8, e7, e2, e4]
eleSet3中的元素:[e1]
============集合運算=================
eleSet1中的元素:[e0, e5, e3, e8, e7, e2, e4]
eleSet2中的元素:[e3, e1, e4, e0, e8, e2]
eleSet1和eleSet2的交集:[e3, e4, e0, e8, e2]
eleSet1和eleSet2的并集:[e2, e1, e4, e0, e3, e5, e7, e8]
eleSet1和eleSet2的差集:[e5, e7]
有序集合
public void testSortedSet(){
jedis.flushDB();
Map<String,Double> map = new HashMap<>();
map.put("key2",1.5);
map.put("key3",1.6);
map.put("key4",1.9);
System.out.println(jedis.zadd("zset", 3,"key1"));
System.out.println(jedis.zadd("zset",map));
System.out.println("zset中的所有元素:"+jedis.zrangeByScore("zset", 0,100));
System.out.println("zset中key2的分值:"+jedis.zscore("zset", "key2"));
System.out.println("zset中key2的排名:"+jedis.zrank("zset", "key2"));
System.out.println("删除zset中的元素key3:"+jedis.zrem("zset", "key3"));
System.out.println("zset中的所有元素:"+jedis.zrange("zset", 0, -1));
System.out.println("zset中元素的個數:"+jedis.zcard("zset"));
System.out.println("zset中分值在1-4之間的元素的個數:"+jedis.zcount("zset", 1, 4));
System.out.println("key2的分值加上5:"+jedis.zincrby("zset", 5, "key2"));
System.out.println("key3的分值加上4:"+jedis.zincrby("zset", 4, "key3"));
System.out.println("zset中的所有元素:"+jedis.zrange("zset", 0, -1));
}
1
3
zset中的所有元素:[key2, key3, key4, key1]
zset中key2的分值:1.5
zset中key2的排名:0
删除zset中的元素key3:1
zset中的所有元素:[key2, key4, key1]
zset中元素的個數:3
zset中分值在1-4之間的元素的個數:3
key2的分值加上5:6.5
key3的分值加上4:4.0
zset中的所有元素:[key4, key1, key3, key2]
排序sort
public void testSort(){
jedis.flushDB();
jedis.lpush("collections", "ArrayList", "Vector", "Stack", "HashMap", "WeakHashMap", "LinkedHashMap");
System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));
SortingParams sortingParameters = new SortingParams();
System.out.println(jedis.sort("collections",sortingParameters.alpha()));
System.out.println("===============================");
jedis.lpush("sortedList", "3","6","2","0","7","4");
System.out.println("sortedList排序前:"+jedis.lrange("sortedList", 0, -1));
System.out.println("升序:"+jedis.sort("sortedList", sortingParameters.asc()));
System.out.println("升序:"+jedis.sort("sortedList", sortingParameters.desc()));
System.out.println("===============================");
jedis.lpush("userlist", "33");
jedis.lpush("userlist", "22");
jedis.lpush("userlist", "55");
jedis.lpush("userlist", "11");
jedis.hset("user:66", "name", "66");
jedis.hset("user:55", "name", "55");
jedis.hset("user:33", "name", "33");
jedis.hset("user:22", "name", "79");
jedis.hset("user:11", "name", "24");
jedis.hset("user:11", "add", "beijing");
jedis.hset("user:22", "add", "shanghai");
jedis.hset("user:33", "add", "guangzhou");
jedis.hset("user:55", "add", "chongqing");
jedis.hset("user:66", "add", "xi'an");
sortingParameters = new SortingParams();
sortingParameters.get("user:*->name");
sortingParameters.get("user:*->add");
System.out.println(jedis.sort("userlist",sortingParameters));
}
collections的内容:[LinkedHashMap, WeakHashMap, HashMap, Stack, Vector, ArrayList]
[ArrayList, HashMap, LinkedHashMap, Stack, Vector, WeakHashMap]
===============================
sortedList排序前:[4, 7, 0, 2, 6, 3]
升序:[0, 2, 3, 4, 6, 7]
升序:[7, 6, 4, 3, 2, 0]
===============================
[24, beijing, 79, shanghai, 33, guangzhou, 55, chongqing]
JedisPool應用
雖然我們可以簡單地建立Jedis使用,但每次操作的時候,都建立連接配接,很耗費性能。解決方法就是從一個連接配接池中取出連接配接對象,用完還回去。使用連接配接池的方案還能解決很多同步性問題。在Jedis中,管理Redis連接配接的類是JedisPool。要想使用JedisPool需要添加jar包或依賴庫,在pom.xml中添加
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.2</version>
</dependency>
- 普通連接配接池連接配接
實作方式有兩種,一種是通過配置檔案(properties檔案),我的檔案名是jedisPool.properties:
#最大配置設定的對象數
redis.pool.maxTotal=1024
#最大能夠保持idel狀态的對象數
redis.pool.maxIdle=200
#當池内沒有傳回對象時,最大等待時間
redis.pool.maxWait=1000
#當調用borrow Object方法時,是否進行有效性檢查
redis.pool.testOnBorrow=true
#當調用return Object方法時,是否進行有效性檢查
redis.pool.testOnReturn=true
#IP
redis.ip=172.24.4.183
#Port
redis.port=6379
java類如下:
public class JedisUtil {
private static Jedis jedis;
private static JedisPool jedisPool = null;
/**
* 初始化Redis連接配接池
*/
static {
ResourceBundle bundle = ResourceBundle.getBundle("jedisPool");
if (bundle == null) {
throw new IllegalArgumentException(
"[jedisPool.properties] is not found!");
}
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(Integer.valueOf(bundle
.getString("redis.pool.maxTotal")));
config.setMaxIdle(Integer.valueOf(bundle
.getString("redis.pool.maxIdle")));
config.setMaxWaitMillis(Long.valueOf(bundle.getString("redis.pool.maxWait")));
config.setTestOnBorrow(Boolean.valueOf(bundle
.getString("redis.pool.testOnBorrow")));
config.setTestOnReturn(Boolean.valueOf(bundle
.getString("redis.pool.testOnReturn")));
jedisPool = new JedisPool(config, bundle.getString("redis.ip"),
Integer.valueOf(bundle.getString("redis.port")));
// 從池中擷取一個Jedis對象
jedis = jedisPool .getResource();
}
public void add(String sn) {
jedis.sadd("snSet", sn);
jedisPool.destroy();
}
public void remove(String sn) {
jedis.srem("snSet", sn);
}
public boolean isExist(String sn) {
Set<String> snSet = jedis.smembers("snSet");
return snSet.contains(sn);
}
public static void main(String[] args) {
String keys = "name";
// 删資料
jedis.del(keys);
// 存資料
jedis.set(keys, "snowolf");
// 取資料
String value = jedis.get(keys);
System.out.println(value);
// 釋放對象池
// jedisPool.returnResource(jedis);
}
}
而直接通過代碼實作的話,其實也是一個原理:
public class JedisUtil1 {
private static JedisUtil1 instance = null;
private Jedis jedis;
private static JedisPool jedisPool = null;
//Redis伺服器IP
private static String HOST = "172.24.4.183";
//Redis的端口号
private static int PORT = 6379;
//可用連接配接執行個體的最大數目,預設值為8;
//如果指派為-1,則表示不限制;如果pool已經配置設定了maxActive個jedis執行個體,則此時pool的狀态為exhausted(耗盡)。
private static int MAX_ACTIVE = 1024;
//控制一個pool最多有多少個狀态為idle(空閑的)的jedis執行個體,預設值也是8。
private static int MAX_IDLE = 200;
private static int TIMEOUT = 10000;
//在borrow一個jedis執行個體時,是否提前進行validate操作;如果為true,則得到的jedis執行個體均是可用的;
private static boolean TEST_ON_BORROW = true;
/**
* 初始化Redis連接配接池
*/
static {
try {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(MAX_ACTIVE);
config.setMaxIdle(MAX_IDLE);
config.setMaxWaitMillis(TIMEOUT);
config.setTestOnBorrow(TEST_ON_BORROW);
jedisPool = new JedisPool(config, HOST, PORT);
} catch (Exception e) {
e.printStackTrace();
}
}
- Sentinel連接配接池連接配接
Sentinel連接配接池用于應對Redis的Sentinel的主從切換機制,能夠正确在伺服器當機導緻伺服器切換時得到正确的伺服器連接配接,當伺服器采用該部署政策的時候推薦使用該連接配接池進行操作
private static Jedis jedis;
private static JedisSentinelPool jedisSentinelPool = null;
/**
* 初始化Redis連接配接池
*/
static {
ResourceBundle bundle = ResourceBundle.getBundle("jedisSentinePool");
if (bundle == null) {
throw new IllegalArgumentException(
"[jedisSentinePool.properties] is not found!");
}
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(Integer.valueOf(bundle
.getString("redis.pool.maxTotal")));
config.setMaxIdle(Integer.valueOf(bundle
.getString("redis.pool.maxIdle")));
config.setMaxWaitMillis(Long.valueOf(bundle.getString("redis.pool.maxWait")));
config.setTestOnBorrow(Boolean.valueOf(bundle
.getString("redis.pool.testOnBorrow")));
config.setTestOnReturn(Boolean.valueOf(bundle
.getString("redis.pool.testOnReturn")));
//監聽器清單
Set<String> sentinels = new HashSet<>();
/**
*
//監聽器1
sentinels.add(new HostAndPort("172.24.4.183", 26379).toString());
//監聽器2
sentinels.add(new HostAndPort("172.24.4.184", 26379).toString());
//實際使用的時候在properties裡配置即可:redis.sentinel.hostandports=172.24.4.183:26379, 172.24.4.184:26379
//然後使用 bundle.getString("redis.sentinel.hostandports");擷取位址
*/
//mastername是伺服器上的master的名字,在master伺服器的sentinel.conf中配置
String masterName = bundle.getString("redis.sentinel.masterName");
sentinels.add(bundle.getString("redis.sentinel.hostandports"));
//初始化連接配接池
jedisSentinelPool = new JedisSentinelPool(masterName,
sentinels, config);
// 從池中擷取一個Jedis對象
jedis = jedisSentinelPool.getResource();
}
- ShardedJedisPool連接配接池分片連接配接
Memcached完全基于分布式叢集,而Redis是Master-Slave,Redis在容災處理方面可以通過伺服器端配置Master-Slave模式來實作。如果想把Reids做成叢集模式,無外乎多做幾套Master-Slave,每套Master-Slave完成各自的容災處理,通過Client工具來實作一緻性哈希分布存儲,即key分片存儲。
shared一緻性哈希采用以下方案:
- Redis伺服器節點劃分:将每台伺服器節點采用hash算法劃分為160個虛拟節點(可以配置劃分權重)
- 将劃分虛拟節點采用TreeMap存儲
- 對每個Redis伺服器的實體連接配接采用LinkedHashMap存儲
- 對Key or KeyTag 采用同樣的hash算法,然後從TreeMap擷取大于等于鍵hash值得節點,取最鄰近節點存儲;當key的hash值大于虛拟節點hash值得最大值時,存入第一個虛拟節點
sharded采用的hash算法:MD5 和 MurmurHash兩種;預設采用64位的MurmurHash算法;有興趣的可以研究下~
保留前面的JedisPoolConfig,新增兩個Redis的IP(redis1.ip,redis2.ip),完成兩個JedisShardInfo執行個體,并将其丢進List中:
private static ShardedJedis jedis;
private static ShardedJedisPool shardedJedisPool = null;
/**
* 初始化Redis連接配接池
*/
static {
ResourceBundle bundle = ResourceBundle.getBundle("sharedJedisPool");
if (bundle == null) {
throw new IllegalArgumentException(
"[jedisPool.properties] is not found!");
}
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(Integer.valueOf(bundle
.getString("redis.pool.maxTotal")));
config.setMaxIdle(Integer.valueOf(bundle
.getString("redis.pool.maxIdle")));
config.setMaxWaitMillis(Long.valueOf(bundle.getString("redis.pool.maxWait")));
config.setTestOnBorrow(Boolean.valueOf(bundle
.getString("redis.pool.testOnBorrow")));
config.setTestOnReturn(Boolean.valueOf(bundle
.getString("redis.pool.testOnReturn")));
JedisShardInfo jedisShardInfo1 = new JedisShardInfo(
bundle.getString("redis1.ip"), Integer.valueOf(bundle.getString("redis.port")));
JedisShardInfo jedisShardInfo2 = new JedisShardInfo(
bundle.getString("redis2.ip"), Integer.valueOf(bundle.getString("redis.port")));
List<JedisShardInfo> list = new LinkedList<>();
list.add(jedisShardInfo1);
list.add(jedisShardInfo2);
shardedJedisPool = new ShardedJedisPool(config,list);
// 從池中擷取一個Jedis對象
jedis = shardedJedisPool.getResource();
}
參考:
http://www.boyunjian.com/javadoc/org.apache.servicemix.bundles/org.apache.servicemix.bundles.jedis/2.1.0_1/_/redis/clients/jedis/JedisShardInfo.html https://yq.aliyun.com/articles/236384 http://blog.csdn.net/dslztx/article/details/46743053 http://flyingsnail.blog.51cto.com/5341669/1371650 http://www.tuicool.com/articles/vaqABb http://www.importnew.com/19321.html http://www.cnblogs.com/liuling/p/2014-4-19-04.html http://www.cnblogs.com/libaoting/p/4418007.html http://blog.csdn.net/fachang/article/details/7984123 http://blog.csdn.net/aubdiy/article/details/53511410 http://blog.csdn.net/moxiaomomo/article/details/17588483