原型模式:如何最快速地clone一个HashMap散列表?

news2024/12/25 12:26:44

我们还像学习建造者模式一样

思考

什么是原型模式?主要解决哪些问题?

如果对象的创建成本比较大而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式(Prototype Design Pattern),简称原型模式

今天的讲解跟具体某一语言的语法机制无关,而是通过一个clone散列表的例子带你搞清楚:原型模式的应用场景,以及它的两种实现方式:深拷贝和浅拷贝。虽然原型模式的原理和代码实现非常简单,但今天举的例子还是稍微有点复杂的。

原型模式的原理与应用

什么是对象创建成本比较大呢?

对象中的数据需要经过复杂的计算才能得到(比如排序、计算哈希值),或者需要从RPC、网络、数据库文件系统等非常慢速的IO中读取,这种情况下,我们就可以利用原型模式,从其他已有对象中直接拷贝得到,而不用每次在创建新对象的时候,都重复执行这些耗时的操作。

接下来通过一个实际例子进行讲解。

假设数据库中存储了大约10万条“搜索关键词”信息,每条信息包含关键词、关键词被搜索的次数、信息最近被更新的时间等。系统A在启动的时候会加载这份数据到内存中,用于处理某些其他的业务需求,需要建立一个散列表。

例如java可以使用hashmap来实现,我们只需要将数据从数据库中读出来,这涉及到了IO读取。

不过,我们还有另外一个系统B,专门用来分析搜索日志,定期(比如间隔10分钟)批量地更新数据库中的数据,并且标记为新的数据版本。比如,在下面的示例图中,我们对v2版本的数据进行更新,得到v3版本的数据。这里我们假设只有更新和新添关键词,没有删除关键词的行为。

为了保证系统A中数据的实时性(不一定非常实时,但数据也不能太旧),系统A需要定期根据数据库中的数据,更新内存中的索引和数据。

我们应该如何实现这个需求呢?

系统A负责读取数据库中的信息,并且建立散列表,系统B需要定时更新数据库信息,系统A也需要实时刷新散列表的信息。

我们只需要在系统A中,记录当前数据的版本Va对应的更新时间Ta从数据库中捞出更新时间大于Ta的所有搜索关键词,也就是找出Va版本与最新版本数据的“差集”,然后针对差集中的每个关键词进行处理。如果它已经在散列表中存在了,我们就更新相应的搜索次数、更新时间等信息;如果它在散列表中不存在,我们就将它插入到散列表中。

public class Demo {
  private ConcurrentHashMap<String, SearchWord> currentKeywords = new ConcurrentHashMap<>();
  private long lastUpdateTime = -1;

  public void refresh() {
    // 从数据库中取出更新时间>lastUpdateTime的数据,放入到currentKeywords中
    List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);
    long maxNewUpdatedTime = lastUpdateTime;
    for (SearchWord searchWord : toBeUpdatedSearchWords) {
      if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {
        maxNewUpdatedTime = searchWord.getLastUpdateTime();
      }
      if (currentKeywords.containsKey(searchWord.getKeyword())) {
        currentKeywords.replace(searchWord.getKeyword(), searchWord);
      } else {
        currentKeywords.put(searchWord.getKeyword(), searchWord);
      }
    }

    lastUpdateTime = maxNewUpdatedTime;
  }

  private List<SearchWord> getSearchWords(long lastUpdateTime) {
    // TODO: 从数据库中取出更新时间>lastUpdateTime的数据
    return null;
  }
}

不过,现在,我们有一个特殊的要求:任何时刻,系统A中的所有数据都必须是同一个版本的,要么都是版本a,要么都是版本b,不能有的是版本a,有的是版本b。那刚刚的更新方式就不能满足这个要求了。除此之外,我们还要求:在更新内存数据的时候,系统A不能处于不可用状态,也就是不能停机更新数据(可以利用空间换时间)。

那我们该如何实现现在这个需求呢?

实际上,也不难。我们把正在使用的数据的版本定义为“服务版本”,当我们要更新内存中的数据的时候,我们并不是直接在服务版本(假设是版本a数据)上更新,而是重新创建另一个版本数据(假设是版本b数据),等新的版本数据建好之后,再一次性地将服务版本从版本a切换到版本b。这样既保证了数据一直可用,又避免了中间状态的存在。

public class Demo {
  private HashMap<String, SearchWord> currentKeywords=new HashMap<>();

  public void refresh() {
    HashMap<String, SearchWord> newKeywords = new LinkedHashMap<>();

    // 从数据库中取出所有的数据,放入到newKeywords中
    List<SearchWord> toBeUpdatedSearchWords = getSearchWords();
    for (SearchWord searchWord : toBeUpdatedSearchWords) {
      newKeywords.put(searchWord.getKeyword(), searchWord);
    }

    currentKeywords = newKeywords;
  }

  private List<SearchWord> getSearchWords() {
    // TODO: 从数据库中取出所有的数据
    return null;
  }
}

存在的缺点:newKeywords构建的成本比较高。我们需要将这10万条数据从数据库中读出,然后计算哈希值,构建newKeywords。这个过程显然是比较耗时。为了提高效率,原型模式就派上用场了

我们不用将10万条数据进行全部新建,主要新建更新的或者是新插入的。

public class Demo {
  private HashMap<String, SearchWord> currentKeywords=new HashMap<>();
  private long lastUpdateTime = -1;

  public void refresh() {
    // 原型模式就这么简单,拷贝已有对象的数据,更新少量差值
    HashMap<String, SearchWord> newKeywords = (HashMap<String, SearchWord>) currentKeywords.clone();

    // 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中
    List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);
    long maxNewUpdatedTime = lastUpdateTime;
    for (SearchWord searchWord : toBeUpdatedSearchWords) {
      if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {
        maxNewUpdatedTime = searchWord.getLastUpdateTime();
      }
      if (newKeywords.containsKey(searchWord.getKeyword())) {
        SearchWord oldSearchWord = newKeywords.get(searchWord.getKeyword());
        oldSearchWord.setCount(searchWord.getCount());
        oldSearchWord.setLastUpdateTime(searchWord.getLastUpdateTime());
      } else {
        newKeywords.put(searchWord.getKeyword(), searchWord);
      }
    }

    lastUpdateTime = maxNewUpdatedTime;
    currentKeywords = newKeywords;
  }

  private List<SearchWord> getSearchWords(long lastUpdateTime) {
    // TODO: 从数据库中取出更新时间>lastUpdateTime的数据
    return null;
  }
}

上述代码是用问题的,不知道你有没有发现,我们使用的clone进行hash表的克隆,使用到clone那么你就要了解浅拷贝和深拷贝。

例如:key中存入的是搜索的关键词,value是SearchWord对象的内存地址。SearchWord对象本身存储在散列表之外的内存空间中。

浅拷贝:只会复制图中的索引,不会复制数据(SearchWord对象)本身。

深拷贝:不仅仅会复制索引,还会复制数据本身。浅拷贝得到的对象(newKeywords)跟原始对象(currentKeywords)共享数据(SearchWord对象),而深拷贝得到的是一份完完全全独立的对象。具体的对比如下图所示:

 

 要解决上述设计,我们应该使用的是深拷贝。而Object类的clone()方法执行的就是浅拷贝,我们可以通过for循环进行解决。

public class Demo {
  private HashMap<String, SearchWord> currentKeywords=new HashMap<>();
  private long lastUpdateTime = -1;

  public void refresh() {
    // Deep copy
    HashMap<String, SearchWord> newKeywords = new HashMap<>();
    for (HashMap.Entry<String, SearchWord> e : currentKeywords.entrySet()) {
      SearchWord searchWord = e.getValue();
      SearchWord newSearchWord = new SearchWord(
              searchWord.getKeyword(), searchWord.getCount(), searchWord.getLastUpdateTime());
      newKeywords.put(e.getKey(), newSearchWord);
    }

    // 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中
    List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);
    long maxNewUpdatedTime = lastUpdateTime;
    for (SearchWord searchWord : toBeUpdatedSearchWords) {
      if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {
        maxNewUpdatedTime = searchWord.getLastUpdateTime();
      }
      if (newKeywords.containsKey(searchWord.getKeyword())) {
        SearchWord oldSearchWord = newKeywords.get(searchWord.getKeyword());
        oldSearchWord.setCount(searchWord.getCount());
        oldSearchWord.setLastUpdateTime(searchWord.getLastUpdateTime());
      } else {
        newKeywords.put(searchWord.getKeyword(), searchWord);
      }
    }

    lastUpdateTime = maxNewUpdatedTime;
    currentKeywords = newKeywords;
  }

  private List<SearchWord> getSearchWords(long lastUpdateTime) {
    // TODO: 从数据库中取出更新时间>lastUpdateTime的数据
    return null;
  }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/727316.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

5 类型转换

类型转换是变量与变量之间的&#xff0c;变量与常量之间是赋值。 5.1 自动类型转换 小转大。如下图所示&#xff0c;注意&#xff0c;byte不能自动转为char类型&#xff0c;因为类型不匹配&#xff0c;但是可以通过强转来转。 代码如下&#xff1a; byte a 10;int b a; 5.2…

测试流程实战(1)

目录&#xff1a; 测试流程梳理业务架构分析实战测试用例管理实战Bug 录入与管理实战如何写 Bug 报告编写 Bug 报告 1.测试流程梳理 2.业务架构分析实战 使用 plantuml 完成登录流程时序图plantuml 官网&#xff1a;使用简单的文字描述画UML图的开源工具。plantuml 在线绘图…

【HDC.Cloud 2023】华为开发者大会2023来了!这份PaaS参会指南请查收!

了不起的开发者们&#xff0c;我们来啦&#xff01; 7月7日&#xff0c;华为开发者大会2023 ( Cloud )将拉开帷幕 PaaS诚邀您参加这场不容错过的年度开发者盛会&#xff0c;让我们一起开启探索之旅。 我们将为开发者们提供PaaS生态资源工具、学习成长、分享交流、生态实践等…

Docker 安装Flowable-ui

查询镜像 docker search flowable-ui 拉取镜像 docker pull flowable/flowable-ui 使用默认数据库&#xff08;默认H2数据库&#xff09; docker run --name flowable-ui \ -p 8080:8080 \ -d --restartalways \ flowable/flowable-ui 使用MySQL数据库 docker run --name…

Windows环境Jmeter调优

在windows环境下搭建jmeter的压测实验环境&#xff0c;需要对操作系统默认的一些个参数进行设置&#xff0c;以提高并发能力。特别是作为压力机的时候。 Socket 编程时&#xff0c;单机最多可以建立多少个 TCP 连接&#xff0c;受到操作系统的影响。 Windows 下单机的TCP连接数…

simulink while/if/switch case

目录 while if Switch case while 循环设置100次 if Switch case 子模块可以用法和if一样

vue3混入mixins

Vue中混入的作用是分发组建中可复用的功能 新建mixins文件夹&#xff0c;新建mixins.ts文件 import { ref } from vue;export default function () {const num ref(0);const fav ref(false);const fvbtn () > {num.value 1;fav.value true;setTimeout(() > {fav.va…

六西格玛在服务业的案例:如何通过过程改进提高客户满意度?

六西格玛是一种质量管理方法&#xff0c;旨在通过减少缺陷和提高效率用以改善业务流程。在服务行业&#xff0c;六西格玛可以帮助企业提高客户满意度&#xff0c;缩短服务周期&#xff0c;降低成本。下面张驰咨询给大家分享一个服务行业的六西格玛案例。 1、背景介绍 这家服务…

Docker学习笔记26

Docker stack应用&#xff1a; 1&#xff09;Docker 层级关系中的最高层次——stack&#xff0c;一个stack就是一组有关联的服务的组合&#xff0c;可以一起编排&#xff0c;一起管理。 早期&#xff0c;使用service来发布服务。但是service每次只能发布一个service。 yaml可…

(Windows版)PostgreSQL - TimescaleDB插件的2种安装方法

一&#xff1a;下载pgsql相对应的timescaledb插件包 下载地址&#xff1a;https://github.com/timescale/timescaledb/releases/tag/2.10.1 二&#xff1a;开始安装 注意&#xff1a;在安装前&#xff0c;先关闭PostgreSQL 服务 方法一 1.【控制面板\系统和安全\管理工具\…

手把手教-单片机和w5500模块基于rt-thread中wiznet软件包的使用

一、开发环境 硬件&#xff1a;stm32f407野火开发板&#xff0c;w5500模块 软件&#xff1a;rt-thread操作系统&#xff0c;wiznet软件包&#xff0c;基于正点原子stm32f407的bsp包&#xff08;需要根据实际修改系统时钟&#xff09; 引脚连接方式&#xff1a; 单片机引脚&…

镀金积分球——激光红外功率测量

中波红外激光作为干扰光源&#xff0c;可对红外导引头及红外观瞄设备实施压制式干扰&#xff0c;激光器远场功率密度分布是评价其作战效能的重要指标。通用的激光功率测量设备多基于近场&#xff0c;并且因接收口径有限&#xff0c;只能检测到很小特定区域内的激光功率&#xf…

专心当个工程师,也挺好~

正文 大家好&#xff0c;我是bug菌~ 今天主要是分享一些职场上的小小领悟吧&#xff1a; 1 双向思维 一个人有他的缺点&#xff0c;也必定存在其优点&#xff0c;不要动不动就局限的认为这个人这也不行&#xff0c;那也不行&#xff0c;再说当初你咋就把他招进来了呢&#xff1…

【Redis 】Redis 的脑裂现象和解决方案

文章目录 Redis 中的脑裂是什么&#xff1f;脑裂有什么影响&#xff1f;数据丢失一定是发生了脑裂吗&#xff1f;如何解决脑裂问题&#xff1f; Redis 中的脑裂是什么&#xff1f; 从名字分析&#xff0c;脑裂现象就是大脑裂开了&#xff0c;一个人如果有两个大脑&#xff0c;…

Golang每日一练(leetDay0116) 路径交叉、回文对

目录 335. 路径交叉 Self-crossing &#x1f31f;&#x1f31f;&#x1f31f; 336. 回文对 Palindrome Pairs &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/…

【PCIE】链路训练的TS训练序列解释

TS码流解释 TS1和TS2有序集合是PCIe&#xff08;Peripheral Component Interconnect Express&#xff09;协议中使用的一种特殊数据包格式。这些有序集合用于在PCIe链路的训练阶段进行通信和控制。 TS1&#xff08;Training Sequence 1&#xff09;有序集合&#xff1a;TS1有…

Django实现简单的音乐播放器 2

在《Django实现简单的音乐播放器 1》前期准备的基础上开始开发。 效果&#xff1a; 目录 项目视图 创建视图方法 路由加载视图 加载模板 创建首页html文件 加载静态资源文件 加载静态文件 使用方法 启动服务器 加载数据表 创建表模型 生成表迁移 执行创建表 插入…

CRM中的多业务场景管理是什么?有哪些功能?

为了适应不同的客户需求和市场变化&#xff0c;很多企业开展了多个业务场景&#xff08;产品线、销售渠道等&#xff09;。这些多业务场景给企业的客户管理带来了很大的挑战&#xff0c;如何有效地管理多种业务是企业急需解决的问题。下面说说&#xff0c;多业务场景下的CRM管理…

在Window仿Linux终端命令学习Python

在Window仿Linux终端命令学习Python Echox 命令 -- 主要带颜色输出ls 命令findx 命令~~待续~~ python3 Echox 命令 – 主要带颜色输出 查看 python输出颜色&#xff08;终端控制台&#xff09; ls 命令 1、getopt 模块参数解析 &#xff1a; Python 命令行参数 2、os.getcwd…

怎么免费视频压缩器?这 9 个最佳免费视频压缩器教你轻松学会!

视频有多种格式、不同的大小和不同的压缩级别。但是&#xff0c;您可以使用最好的视频压缩器来&#xff1a; 减小文件大小提高压缩质量更好地服务您的观众 我们将列出九个领先的视频压缩软件精选&#xff0c;您今天可以免费使用它们来增强您的视频。 9 个顶级免费视频压缩软件…