Redis从基础命令到实战之散列类型(Hash)

从上一篇的实例中可以看出,用字符串类型存储对象有一些不足,在存储/读取时需要进行序列化/反序列化,即时只想修改一项内容,如价格,也必须修改整个键值。不仅增大开发的复杂度,也增加了不必要的性能开销。

一个更好的选择是使用散列类型,或称为Hash表。散列类型与Java中的HashMap相似,是一组键值对的集合,且支持单独对其中一个键进行增删改查操作。使用散列类型存储前面示例中的商品对象,结构如下图所示:

下面先通过示例代码来看散列类型常用的操作命令

一、常用命令

HashExample.java

 

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. import java.util.Arrays;  
  2. import java.util.HashMap;  
  3. import java.util.List;  
  4. import java.util.Map;  
  5. import java.util.Set;  
  6.   
  7. import redis.clients.jedis.Jedis;  
  8.   
  9. public class HashExample {  
  10.   
  11.     public static void main(String[] args) {  
  12.         Jedis jedis = JedisProvider.getJedis();  
  13.         jedis.flushDB();  
  14.   
  15.         // 为了避免混淆,下文中对Hash表中的键统称为field  
  16.   
  17.         String key = "goods";  
  18.   
  19.         // hset 仅当操作在hash中创建新field时返回1  
  20.         Long hset = jedis.hset(key, "id", "1");  
  21.         print("hset id 1=" + hset + "; value=" + jedis.hget(key, "id"));  
  22.   
  23.         // 如果field已存在则执行修改,并返回0  
  24.         hset = jedis.hset(key, "id", "2");  
  25.         print("hset id 2=" + hset + "; value=" + jedis.hget(key, "id"));  
  26.   
  27.         // hexists 判断field是否存在  
  28.         boolean hexists = jedis.hexists(key, "id");  
  29.         print("hexists id=" + hexists);  
  30.         hexists = jedis.hexists(key, "title");  
  31.         print("hexists title=" + hexists);  
  32.   
  33.         // hsetex 如果field不存在则添加, 已存在则不会修改值, 可用来添加要求不重复的field  
  34.         Long hsetnx = jedis.hsetnx(key, "id", "3");  
  35.         print("hsetnx id 3=" + hsetnx + "; value=" + jedis.hget(key, "id"));  
  36.         hsetnx = jedis.hsetnx(key, "title", "商品001");  
  37.         print("hsetnx title 商品001=" + hsetnx + "; value=" + jedis.hget(key, "title"));  
  38.   
  39.         // hmset 设置多个field  
  40.         Map<String, String> msets = new HashMap<>();  
  41.         msets.put("color", "red");  
  42.         msets.put("width", "100");  
  43.         msets.put("height", "80");  
  44.         String hmset = jedis.hmset(key, msets);  
  45.         print("hmset color,width,height=" + hmset);  
  46.   
  47.         // hincr 新增整数类型的键值对或增加值  
  48.         long hincr = jedis.hincrBy(key, "price", 4l);  
  49.         print("hincrBy price 4=" + hincr + "; value=" + jedis.hget(key, "price"));  
  50.   
  51.         // hlen 读取field数量  
  52.         print("hlen=" + jedis.hlen(key));  
  53.   
  54.         // hkeys 读取所有field  
  55.         Set<String> sets = jedis.hkeys(key);  
  56.         print("hkeys=" + Arrays.toString(sets.toArray()));  
  57.   
  58.         // hvals 读取所有值  
  59.         List<String> list = jedis.hvals(key);  
  60.         print("hvals=" + Arrays.toString(list.toArray()));  
  61.   
  62.         // hgetAll 读取所有键值对  
  63.         System.out.println("hgetAll 读取所有键值对");  
  64.         Map<String, String> maps = jedis.hgetAll(key);  
  65.         for (String field : maps.keySet()) {  
  66.             System.out.println("hget " + field + "=" + maps.get(field));  
  67.         }  
  68.         System.out.println("------------------------------------------------------");  
  69.         System.out.println();  
  70.   
  71.         // hdel 删除field  
  72.         Long hdel = jedis.hdel(key, "id");  
  73.         print("hdel id=" + hdel);  
  74.   
  75.         // 删除多个field  
  76.         hdel = jedis.hdel(key, "color", "width", "height");  
  77.         print("hdel color,width,height=" + hdel);  
  78.   
  79.         // hincrBy 在整数类型值上增加, 返回修改后的值  
  80.         Long hincrBy = jedis.hincrBy(key, "price", 100l);  
  81.         print("hincrBy price 100=" + hincrBy);  
  82.   
  83.         // hget 读取单个field的值  
  84.         String hget = jedis.hget(key, "title");  
  85.         print("hget title=" + hget);  
  86.   
  87.         // hmget 批量读取field的值  
  88.         jedis.hmget(key, "title", "price");  
  89.         list = jedis.hvals(key);  
  90.         print("hmget title,price=" + Arrays.toString(list.toArray()));  
  91.   
  92.         jedis.close();  
  93.     }  
  94.   
  95.     private static void print(String info) {  
  96.         System.out.println(info);  
  97.         System.out.println("------------------------------------------------------");  
  98.         System.out.println();  
  99.     }  
  100.   
  101. }  

二、实践练习

 

对前一篇基于字符串类型的商品管理示例改造,以散列类型存储商品,并增加单独修改标题和修改价格的接口。

首先是添加商品代码

 

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 添加一个商品 
  3.  * @param goods 
  4.  * @return 
  5.  */  
  6. public boolean addGoods(Goods goods) {  
  7.     long id = getIncrementId();  
  8.     Map<String, String> map = new HashMap<>();  
  9.     map.put("id", String.valueOf(id));  
  10.     map.put("title", goods.getTitle());  
  11.     map.put("price", String.valueOf(goods.getPrice()));  
  12.     String key = "goods:" + id;  
  13.     return jedis.hmset(key, map).equals("OK");  
  14. }  

然后增加两个单独修改属性的方法

 

 

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 修改商品标题 
  3.  * @param goods 
  4.  * @return 
  5.  */  
  6. public boolean updateTitle(long id, String title) {  
  7.     String key = "goods:" + id;  
  8.     return jedis.hset(key, "title", title) == 0;  
  9. }  
  10.   
  11. /** 
  12.  * 修改商品价格 
  13.  * @param id 
  14.  * @param price 
  15.  * @return 
  16.  */  
  17. public boolean updatePrice(long id, float price) {  
  18.     String key = "goods:" + id;  
  19.     return jedis.hset(key, "price", String.valueOf(price)) == 0;  
  20. }  

最后还需要修改读取商品列表的方法

 

 

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 读取用于分页的商品列表 
  3.  * @param pageIndex 页数 
  4.  * @param pageSize 每页显示行数 
  5.  * @return 
  6.  */  
  7. public List<Goods> getGoodsList(int pageIndex, int pageSize) {  
  8.     int totals = (int)getTotalCount();  
  9.     int from = (pageIndex - 1) * pageSize;  
  10.     if(from < 0) {  
  11.         from = 0;  
  12.     }  
  13.     else if(from > totals) {  
  14.         from = (totals / pageSize) * pageSize;  
  15.     }  
  16.     int to = from + pageSize;  
  17.     if(to > totals) {  
  18.         to = totals;  
  19.     }  
  20.     List<Goods> goodsList = new ArrayList<>();  
  21.     for(int i = from; i < to; i++) {  
  22.         String key = "goods:" + (i + 1);  
  23.         Map<String, String> maps = jedis.hgetAll(key);  
  24.         Goods goods = new Goods();  
  25.         goods.setId(NumberUtils.toLong(maps.get("id")));  
  26.         goods.setTitle(maps.get("title"));  
  27.         goods.setPrice(NumberUtils.toFloat(maps.get("price")));  
  28.         goodsList.add(goods);  
  29.     }  
  30.     return goodsList;  
  31. }  

测试代码

 

 

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public static void main(String[] args) {  
  2.     HashLession hl = new HashLession();  
  3.     hl.clear();  
  4.       
  5.     //添加一批商品  
  6.     for(int i = 0; i< 41; i++) {  
  7.         Goods goods = new Goods(0, "goods" + String.format("%05d", i), i);  
  8.         hl.addGoods(goods);  
  9.     }  
  10.     //读取商品总数  
  11.     System.out.println("商品总数: " + hl.getTotalCount());  
  12.     //修改商品价格  
  13.     for(int i = 1; i <= hl.getTotalCount(); i++) {  
  14.         hl.updatePrice(i, new Random().nextFloat());  
  15.     }  
  16.     //分页显示  
  17.     List<Goods> list = hl.getGoodsList(2, 20);  
  18.     System.out.println("第二页商品:");  
  19.     for(Goods goods : list) {  
  20.         System.out.println(goods);  
  21.     }  
  22. }  

到目前为止,此示例仍然不能支持删除商品的功能,这是因为商品总数是以一个自增数字记录的,且关联了新商品key的生成,删除商品后不能直接减小总数,进而影响到分页的计算。一个比较低效的办法遍历数据库并累加符合规则的key总数,但是更好的做法是以链表保存所有存活的id,这将在下一篇介绍。

源码下载

posted @ 2017-05-10 17:00  撒_旦  阅读(353)  评论(0)    收藏  举报