欢迎投稿

今日深度:

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

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


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

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


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

一、常用命令

HashExample.java

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import redis.clients.jedis.Jedis;

public class HashExample {

	public static void main(String[] args) {
		Jedis jedis = JedisProvider.getJedis();
		jedis.flushDB();

		// 为了避免混淆,下文中对Hash表中的键统称为field

		String key = "goods";

		// hset 仅当操作在hash中创建新field时返回1
		Long hset = jedis.hset(key, "id", "1");
		print("hset id 1=" + hset + "; value=" + jedis.hget(key, "id"));

		// 如果field已存在则执行修改,并返回0
		hset = jedis.hset(key, "id", "2");
		print("hset id 2=" + hset + "; value=" + jedis.hget(key, "id"));

		// hexists 判断field是否存在
		boolean hexists = jedis.hexists(key, "id");
		print("hexists id=" + hexists);
		hexists = jedis.hexists(key, "title");
		print("hexists title=" + hexists);

		// hsetex 如果field不存在则添加, 已存在则不会修改值, 可用来添加要求不重复的field
		Long hsetnx = jedis.hsetnx(key, "id", "3");
		print("hsetnx id 3=" + hsetnx + "; value=" + jedis.hget(key, "id"));
		hsetnx = jedis.hsetnx(key, "title", "商品001");
		print("hsetnx title 商品001=" + hsetnx + "; value=" + jedis.hget(key, "title"));

		// hmset 设置多个field
		Map<String, String> msets = new HashMap<>();
		msets.put("color", "red");
		msets.put("width", "100");
		msets.put("height", "80");
		String hmset = jedis.hmset(key, msets);
		print("hmset color,width,height=" + hmset);

		// hincr 新增整数类型的键值对或增加值
		long hincr = jedis.hincrBy(key, "price", 4l);
		print("hincrBy price 4=" + hincr + "; value=" + jedis.hget(key, "price"));

		// hlen 读取field数量
		print("hlen=" + jedis.hlen(key));

		// hkeys 读取所有field
		Set<String> sets = jedis.hkeys(key);
		print("hkeys=" + Arrays.toString(sets.toArray()));

		// hvals 读取所有值
		List<String> list = jedis.hvals(key);
		print("hvals=" + Arrays.toString(list.toArray()));

		// hgetAll 读取所有键值对
		System.out.println("hgetAll 读取所有键值对");
		Map<String, String> maps = jedis.hgetAll(key);
		for (String field : maps.keySet()) {
			System.out.println("hget " + field + "=" + maps.get(field));
		}
		System.out.println("------------------------------------------------------");
		System.out.println();

		// hdel 删除field
		Long hdel = jedis.hdel(key, "id");
		print("hdel id=" + hdel);

		// 删除多个field
		hdel = jedis.hdel(key, "color", "width", "height");
		print("hdel color,width,height=" + hdel);

		// hincrBy 在整数类型值上增加, 返回修改后的值
		Long hincrBy = jedis.hincrBy(key, "price", 100l);
		print("hincrBy price 100=" + hincrBy);

		// hget 读取单个field的值
		String hget = jedis.hget(key, "title");
		print("hget title=" + hget);

		// hmget 批量读取field的值
		jedis.hmget(key, "title", "price");
		list = jedis.hvals(key);
		print("hmget title,price=" + Arrays.toString(list.toArray()));

		jedis.close();
	}

	private static void print(String info) {
		System.out.println(info);
		System.out.println("------------------------------------------------------");
		System.out.println();
	}

}
二、实践练习

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

首先是添加商品代码

	/**
	 * 添加一个商品
	 * @param goods
	 * @return
	 */
	public boolean addGoods(Goods goods) {
		long id = getIncrementId();
		Map<String, String> map = new HashMap<>();
		map.put("id", String.valueOf(id));
		map.put("title", goods.getTitle());
		map.put("price", String.valueOf(goods.getPrice()));
		String key = "goods:" + id;
		return jedis.hmset(key, map).equals("OK");
	}
然后增加两个单独修改属性的方法

	/**
	 * 修改商品标题
	 * @param goods
	 * @return
	 */
	public boolean updateTitle(long id, String title) {
		String key = "goods:" + id;
		return jedis.hset(key, "title", title) == 0;
	}
	
	/**
	 * 修改商品价格
	 * @param id
	 * @param price
	 * @return
	 */
	public boolean updatePrice(long id, float price) {
		String key = "goods:" + id;
		return jedis.hset(key, "price", String.valueOf(price)) == 0;
	}
最后还需要修改读取商品列表的方法

	/**
	 * 读取用于分页的商品列表
	 * @param pageIndex 页数
	 * @param pageSize 每页显示行数
	 * @return
	 */
	public List<Goods> getGoodsList(int pageIndex, int pageSize) {
		int totals = (int)getTotalCount();
		int from = (pageIndex - 1) * pageSize;
		if(from < 0) {
			from = 0;
		}
		else if(from > totals) {
			from = (totals / pageSize) * pageSize;
		}
		int to = from + pageSize;
		if(to > totals) {
			to = totals;
		}
		List<Goods> goodsList = new ArrayList<>();
		for(int i = from; i < to; i++) {
			String key = "goods:" + (i + 1);
			Map<String, String> maps = jedis.hgetAll(key);
			Goods goods = new Goods();
			goods.setId(NumberUtils.toLong(maps.get("id")));
			goods.setTitle(maps.get("title"));
			goods.setPrice(NumberUtils.toFloat(maps.get("price")));
			goodsList.add(goods);
		}
		return goodsList;
	}
测试代码

	public static void main(String[] args) {
		HashLession hl = new HashLession();
		hl.clear();
		
		//添加一批商品
		for(int i = 0; i< 41; i++) {
			Goods goods = new Goods(0, "goods" + String.format("%05d", i), i);
			hl.addGoods(goods);
		}
		//读取商品总数
		System.out.println("商品总数: " + hl.getTotalCount());
		//修改商品价格
		for(int i = 1; i <= hl.getTotalCount(); i++) {
			hl.updatePrice(i, new Random().nextFloat());
		}
		//分页显示
		List<Goods> list = hl.getGoodsList(2, 20);
		System.out.println("第二页商品:");
		for(Goods goods : list) {
			System.out.println(goods);
		}
	}
到目前为止,此示例仍然不能支持删除商品的功能,这是因为商品总数是以一个自增数字记录的,且关联了新商品key的生成,删除商品后不能直接减小总数,进而影响到分页的计算。一个比较低效的办法遍历数据库并累加符合规则的key总数,但是更好的做法是以链表保存所有存活的id,这将在下一篇介绍。

源码下载

Redis从基础命令到实战之字符串类型
Redis从基础命令到实战之列表类型(List)
Redis从基础命令到实战之集合类型(Set)
Redis从基础命令到实战之有序集合类型(SortedSet)

www.htsjk.Com true http://www.htsjk.com/redis/35384.html NewsArticle Redis从基础命令到实战之散列类型(Hash),redishash 从上一篇的实例中可以看出,用字符串类型存储对象有一些不足,在存储/读取时需要进行序列化/反序列化,即时只想修改一项内容,如...
相关文章
    暂无相关文章
评论暂时关闭