从上一篇的实例中可以看出,用字符串类型存储对象有一些不足,在存储/读取时需要进行序列化/反序列化,即时只想修改一项内容,如价格,也必须修改整个键值。不仅增大开发的复杂度,也增加了不必要的性能开销。
一个更好的选择是使用散列类型,或称为Hash表。散列类型与Java中的HashMap相似,是一组键值对的集合,且支持单独对其中一个键进行增删改查操作。使用散列类型存储前面示例中的商品对象,结构如下图所示:
下面先通过示例代码来看散列类型常用的操作命令
一、常用命令
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,这将在下一篇介绍。