Map集合应用场景-模拟缓存案例
通过学习Map集合,认识到Map集合的特性,根据这种特性,在实际开发处理一些数据时,能够合理地使用.
以下是通过模拟真实开发中保证数据安全,将数据存储到磁盘上(缓存),在每一次使用的时候都可以通过某种技术从磁盘中读取数据到内存中,然后进行使用.磁盘数据的读取速度是远远的小于内存的,因此为了提高数据的读取效率,可以将读取到的数据存储到内存中,然后先从内存中读取数据,如果内存中有数据直接进行返回;如果内存中没有数据再次从磁盘上读取数据,并且将磁盘上读取到的数据再次存储到内存中,供下次进行数据的读取。这样就大大的提高了程序的读取数据效率(从磁盘上读取数据的次数大大降低了),这种思想就是缓存思想。整体的数据读取流程如下图所示:
在设计缓存的时候往往会使用到Map集合进行数据的存储,因为存储在内存中的数据需要设置一个名字,后期就可以根据这个名字获取到对应的数据。这个Map的键的类型是字符串,值的类型可以有多种:String、List 、Set、HashMap …
问题背景:
现在本地有一个excel文件,里面存储了一些商品信息,包括商品条码,商品名称,商品价格,现在要求将这些商品信息存储到一个List集合中,模拟磁盘中的数据,当输入一个商品的条码以后,进行第一次查询,并将查询到的结果存储到对应的Map集合中,模拟内存中的数据,当第二次查询的时候,先判断Map集合中是否存在该元素,如果存在则返回商品信息.
定义商品对象Sku:
package com.itheima.cache.domain;
import com.alibaba.excel.annotation.ExcelProperty;
public class Sku {
@ExcelProperty("商品条码")
private String sn ; // 商品条码
@ExcelProperty("商品名称")
private String name ; // 商品名称
@ExcelProperty("商品价格")
private Integer price ; // 价格(分)
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
@Override
public String toString() {
return "Sku{" +
"sn='" + sn + '\'' +
", name='" + name + '\'' +
", price=" + price +
'}';
}
}
读取表格数据并存储到List集合中:
package com.itheima.cache.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.itheima.cache.domain.Sku;
import java.util.List;
public class SkuAnalysisEventListener extends AnalysisEventListener<Sku> {
// 定义List集合成员变量,来存储读取到的数据
private List<Sku> skuList ;
public SkuAnalysisEventListener(List<Sku> skuList) {
this.skuList = skuList ;
}
@Override
public void invoke(Sku sku, AnalysisContext context) {
skuList.add(sku) ;
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
System.out.println("磁盘excel数据读取完毕...");
}
}
定义缓存工具类:
package com.itheima.cache.utils;
import com.itheima.cache.inter.HashOperations;
import com.itheima.cache.inter.ValueOperations;
import com.itheima.cache.inter.impl.DefaultHashOperations;
import com.itheima.cache.inter.impl.DefaultValueOperations;
// 缓存工具类
public class RedisTemplate {
// 操作值为String类型缓存工具类
private final ValueOperations valueOperations = new DefaultValueOperations() ;
// 操作值为HashMap类型缓存工具类
private final HashOperations hashOperations = new DefaultHashOperations() ;
// 获取操作值为String类型缓存工具类对象
public ValueOperations opsForValue() {
return valueOperations ;
}
// 获取操作值为HashMap类型缓存工具类
public HashOperations opsForHash() {
return hashOperations ;
}
}
定义不同类型的缓存工具类接口:
package com.itheima.cache.inter;
// Map的值是HashMap类型的缓存工具类接口
public interface HashOperations {
// 存储数据
public abstract void put(String key , String hashKey , String value) ;
// 获取数据
public abstract String get(String key , String hashKey ) ;
}
package com.itheima.cache.inter;
// Map的值是String类型的缓存工具类接口
public interface ValueOperations {
// 存储数据
public abstract void set(String key , String value) ;
// 获取数据
public abstract String get(String key) ;
}
创建以上类型的接口实现类,重写方法逻辑:
package com.itheima.cache.inter.impl;
import com.itheima.cache.inter.HashOperations;
import java.util.HashMap;
// Map的值是HashMap类型的缓存工具类接口实现类
public class DefaultHashOperations implements HashOperations {
// 补全代码
// 创建一个HashMap用来存储数据
private final HashMap<String , HashMap<String , String>> cacheMap = new HashMap<String , HashMap<String , String>>() ;
@Override
public void put(String key, String hashKey, String value) {
// 补全代码
// 根据键找到存储数据的HashMap集合对象
HashMap<String, String> valuHashMap = cacheMap.get(key);
// 如果存储值的HashMap集合对象为空,此时创建一个HashMap集合对象存储到cacheMap中
if(valuHashMap == null) {
valuHashMap = new HashMap<>() ;
}
// 将要存储的数据存储到值所对应的HashMap中
valuHashMap.put(hashKey , value) ;
// 将valeHashMap再次存储到cacheMap中
cacheMap.put(key , valuHashMap) ;
}
@Override
public String get(String key, String hashKey) {
// 补全代码
// 根据键找到存储数据的HashMap集合对象
HashMap<String, String> valuHashMap = cacheMap.get(key);
// 如果存储值的HashMap集合对象为空 , 直接返回null
if(valuHashMap == null) {
return null ;
}
// 从valuHashMap根据键找到对应的值进行返回
return valuHashMap.get(hashKey);
}
}
package com.itheima.cache.inter.impl;
import com.itheima.cache.inter.ValueOperations;
import java.util.HashMap;
// Map的值是String类型的缓存工具类接口实现
public class DefaultValueOperations implements ValueOperations {
// 补全代码
// 创建一个HashMap用来存储数据
private final HashMap<String , String> cacheMap = new HashMap<>() ;
@Override
public void set(String key, String value) {
// 补全代码
// 将数据存储到HashMap集合中
cacheMap.put(key , value) ;
}
@Override
public String get(String key) {
// 补全代码
// 从HashMap集合中获取数据
return cacheMap.get(key) ;
}
}
定义测试类:
package com.itheima.cache.entry;
import com.alibaba.excel.EasyExcel;
import com.itheima.cache.domain.Sku;
import com.itheima.cache.listener.SkuAnalysisEventListener;
import com.itheima.cache.utils.RedisTemplate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class Entry {
// 定义缓存工具类成员变量
private static final RedisTemplate redisTemplate = new RedisTemplate() ;
public static void main(String[] args) {
// 调用getSkuBySn根据条码获取商品数据并在控制台进行输出
for(int x = 0 ; x < 3 ; x++) {
long fistTime = new Date().getTime();
Sku sku = getSkuBySn("100000003145001000042");
System.out.println(sku);
long lastTime = new Date().getTime();
System.out.println("读取数据消耗了" + (lastTime - fistTime) + "ms");
}
}
// 根据商品的条码获取商品数据
public static Sku getSkuBySn(String sn) {
// 从缓存中获取数据
String name = redisTemplate.opsForHash().get(sn, "name");
if(name != null) {
// 说明缓存中存在数据,在获取商品的价格
String priceValue = redisTemplate.opsForHash().get(sn, "price");
Integer price = Integer.parseInt(priceValue) ;
// 创建一个Sku对象封装数据
Sku sku = new Sku() ;
sku.setSn(sn);
sku.setName(name);
sku.setPrice(price);
// 返回缓存中的数据
System.out.println("从缓存中查询数据..........");
return sku ;
}
// 创建一个ArrayList集合对象,来存储读取到的excel/sku01.xlxs中的数据
List<Sku> skus = new ArrayList<Sku>();
// 读取数据到skus集合对象中
EasyExcel.read("excel/sku01.xlsx" , Sku.class , new SkuAnalysisEventListener(skus)).sheet(0).doRead();
// 遍历上述集合获取对应的商品数据进行返回
Sku result = null ;
for(Sku sku : skus) {
if(sku.getSn().equals(sn)) {
result = sku ;
break;
}
}
// 如果可以查询到数据将数据存储到缓存中
if(result != null) {
redisTemplate.opsForHash().put(sn , "name" , result.getName());
redisTemplate.opsForHash().put(sn , "price" , String.valueOf(result.getPrice()));
}
// 返回result
return result ;
}
}
程序运行结果如下:
总结:
代码通过模拟计算机缓存数据的过程可知,缓存对于系统运行效率的提高有非常大的帮助,在这个案例中,细心的小伙伴会发现,当Map集合中嵌套了一个Map集合时,外层Map集合的键值相同也不会覆盖掉内层Map集合,由此我们可以得出结论,当Map集合中嵌套了一个Map集合,那么内层Map集合的值是被外层Map集合的键和内层Map集合的键同时绑定的,那么当外层键相同时,内层键不同,内层Map集合也不会被覆盖,这同样满足外层Map集合键值不重复的规则!