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

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

redis存hash Redis存hash类型Java_System

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

一、常用命令

HashExample.java

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. "goods";  
18.   
19. // hset 仅当操作在hash中创建新field时返回1  
20. "id", "1");  
21. "hset id 1=" + hset + "; value=" + jedis.hget(key, "id"));  
22.   
23. // 如果field已存在则执行修改,并返回0  
24. "id", "2");  
25. "hset id 2=" + hset + "; value=" + jedis.hget(key, "id"));  
26.   
27. // hexists 判断field是否存在  
28. boolean hexists = jedis.hexists(key, "id");  
29. "hexists id=" + hexists);  
30. "title");  
31. "hexists title=" + hexists);  
32.   
33. // hsetex 如果field不存在则添加, 已存在则不会修改值, 可用来添加要求不重复的field  
34. "id", "3");  
35. "hsetnx id 3=" + hsetnx + "; value=" + jedis.hget(key, "id"));  
36. "title", "商品001");  
37. "hsetnx title 商品001=" + hsetnx + "; value=" + jedis.hget(key, "title"));  
38.   
39. // hmset 设置多个field  
40. new HashMap<>();  
41. "color", "red");  
42. "width", "100");  
43. "height", "80");  
44.         String hmset = jedis.hmset(key, msets);  
45. "hmset color,width,height=" + hmset);  
46.   
47. // hincr 新增整数类型的键值对或增加值  
48. long hincr = jedis.hincrBy(key, "price", 4l);  
49. "hincrBy price 4=" + hincr + "; value=" + jedis.hget(key, "price"));  
50.   
51. // hlen 读取field数量  
52. "hlen=" + jedis.hlen(key));  
53.   
54. // hkeys 读取所有field  
55.         Set<String> sets = jedis.hkeys(key);  
56. "hkeys=" + Arrays.toString(sets.toArray()));  
57.   
58. // hvals 读取所有值  
59.         List<String> list = jedis.hvals(key);  
60. "hvals=" + Arrays.toString(list.toArray()));  
61.   
62. // hgetAll 读取所有键值对  
63. "hgetAll 读取所有键值对");  
64.         Map<String, String> maps = jedis.hgetAll(key);  
65. for (String field : maps.keySet()) {  
66. "hget " + field + "=" + maps.get(field));  
67.         }  
68. "------------------------------------------------------");  
69.         System.out.println();  
70.   
71. // hdel 删除field  
72. "id");  
73. "hdel id=" + hdel);  
74.   
75. // 删除多个field  
76. "color", "width", "height");  
77. "hdel color,width,height=" + hdel);  
78.   
79. // hincrBy 在整数类型值上增加, 返回修改后的值  
80. "price", 100l);  
81. "hincrBy price 100=" + hincrBy);  
82.   
83. // hget 读取单个field的值  
84. "title");  
85. "hget title=" + hget);  
86.   
87. // hmget 批量读取field的值  
88. "title", "price");  
89.         list = jedis.hvals(key);  
90. "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. "------------------------------------------------------");  
98.         System.out.println();  
99.     }  
100.   
101. }

二、实践练习

 

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

首先是添加商品代码

1. /** 
2.  * 添加一个商品 
3.  * @param goods 
4.  * @return 
5.  */  
6. public boolean addGoods(Goods goods) {  
7. long id = getIncrementId();  
8. new HashMap<>();  
9. "id", String.valueOf(id));  
10. "title", goods.getTitle());  
11. "price", String.valueOf(goods.getPrice()));  
12. "goods:" + id;  
13. return jedis.hmset(key, map).equals("OK");  
14. }

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

1. /** 
2.  * 修改商品标题 
3.  * @param goods 
4.  * @return 
5.  */  
6. public boolean updateTitle(long id, String title) {  
7. "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. "goods:" + id;  
19. return jedis.hset(key, "price", String.valueOf(price)) == 0;  
20. }

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

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. 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. new ArrayList<>();  
21. for(int i = from; i < to; i++) {  
22. "goods:" + (i + 1);  
23.         Map<String, String> maps = jedis.hgetAll(key);  
24. new Goods();  
25. "id")));  
26. "title"));  
27. "price")));  
28.         goodsList.add(goods);  
29.     }  
30. return goodsList;  
31. }

测试代码


1. public static void main(String[] args) {  
2. new HashLession();  
3.     hl.clear();  
4.       
5. //添加一批商品  
6. for(int i = 0; i< 41; i++) {  
7. new Goods(0, "goods" + String.format("%05d", i), i);  
8.         hl.addGoods(goods);  
9.     }  
10. //读取商品总数  
11. "商品总数: " + hl.getTotalCount());  
12. //修改商品价格  
13. for(int i = 1; i <= hl.getTotalCount(); i++) {  
14. new Random().nextFloat());  
15.     }  
16. //分页显示  
17. 2, 20);  
18. "第二页商品:");  
19. for(Goods goods : list) {  
20.         System.out.println(goods);  
21.     }  
22. }

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