当前位置: 首页 > news >正文

【文档搜索引擎】在内存中构造出索引结构(下)

文章目录

  • 4.保存到磁盘中
    • 为什么要保存在磁盘中
    • 怎么保存
    • 操作步骤
      • 1. 前期准备
      • 2. 主要操作
  • 5. 将磁盘中的数据加载到内存中
  • Parser 类完整源码
  • Index 类完整源码

4.保存到磁盘中

为什么要保存在磁盘中

索引本来是存储在内存中的,为什么要将其保存在硬盘中?

  • 因为创建索引是比较耗时的

因此我们不应该在服务器启动的时候,才构建索引(启动服务器就可能会拖慢很多很多)

  • 通常的做法是:把这些耗时的操作,单独去进行执行
  • 单独执行完了之后,再让线上服务器直接加载这个构造好的索引

怎么保存

文本实质上就是字符串,我们就可以把字符串直接保存在文件中。我们就需要把内存中的索引结构变成一个“字符串”,然后写文件即可

  • 变成字符串的过程就是——序列化
  • 对应的特定结构的字符串,反向解析成一些结构化数据(类/对象/基础数据结构)——反序列化

序列化和反序列化有很多现成的通用方法,此处咱们就直接使用 JSON 格式来进行序列化/反序列化——jackson

  • 通过 Maven 仓库,引入依赖
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.18.2</version>
</dependency>

操作步骤

1. 前期准备

引入一个 jackson 里面会用到的核心对象

private ObjectMapper objectMapper = new ObjectMapper();
  • 之后就通过这个对象,完成后续的序列化和反序列化操作

创建一个文件指定存放的目录

private static final String INDEX_PATH =
"/Users/yechiel/Desktop/Byte/code_world/Gitee/java_doc_searcher";

2. 主要操作

使用两个文件,分别保存正排和倒排

  1. 先判定一下索引对应的目录是否存在,不存在就创建
  2. 然后在索引中分别创建两个文件:forwardIndexFile (正排文件)、invertedIndexFile (倒排文件)
  3. 使用 writeValue 方法,将文件进行写入
public void save(){  // 使用两个文件,分别保存正排和倒排  // 1. 先判断一下,索引对应的目录是否存在,不存在就创建  File indexPathFile = new File(INDEX_PATH);  if(!indexPathFile.exists()){  indexPathFile.mkdirs();  }  File forwardIndexFile = new File(INDEX_PATH + "fordword.txt");  File invertedIndexFile = new File(INDEX_PATH + "inverted.txt");  try {  // 第一个参数:写到哪个文件里    第二个:对哪个对象进行写入  objectMapper.writeValue(forwardIndexFile, forwardIndex);  objectMapper.writeValue(invertedIndexFile, invertedIndex);  }catch (IOException e) {  e.printStackTrace();  }  
}
  • mkdirs() 可以一次嵌套创建多级目录
  • writeValue 方法会报错,要在两个操作外面加上 try-catch。这里调用这个方法就不用我们再将文件变成字符串,然后再写入文件,这里直接进行写入就方便了很多

5. 将磁盘中的数据加载到内存中

public void load(){  System.out.println("加载索引开始!");  // 1. 设置加载索引的路径(和前面保存的路径一样)  File forwardIndexFile = new File(INDEX_PATH + "forward.txt");  File invertedIndexFile = new File(INDEX_PATH + "inverted.txt");  try{  // 第一个参数:从哪里读    第二个参数:当前读到的数据,按照什么类型进行解析  forwardIndex = objectMapper.readValue(forwardIndexFile, new TypeReference<ArrayList<DocInfo>>() {});  invertedIndex = objectMapper.readValue(invertedIndexFile, new TypeReference<HashMap<String, ArrayList<Weight>>>() {});}catch (IOException e){  e.printStackTrace();  }  System.out.println("加载索引结束!");  
}
  • readValue 就会直接读取到文件内容,并且把文件内容按照这里指定的类型进行解析
    1. 看见这个类型是 ArrayList<>,然后就预期文件里面的 jason 也是代大括号的数组
    2. 然后看到每一个元素又是 DocInfo,我们的 readValue 就期望,我们的数据里面的大括号里面的每一个字段都得和 DocInfo 是相对应的
      • 这个对应关系我们是可以保证的,因为前面存入磁盘的时候,就是用 objectMapperwriteValue() 来去把对象生成 JSON 然后保存的
      • 生成的时候就是按照每一个属性名为 key 来去存的,所以下面解析的时候也是和上面相对应的,根据得到的 JSON 中的每一个 key 的值,来去找到对应对象中的属性,然后给其赋值

这里需要将这个这个结构的字符串,转换成一个 ArrayList<DocInfo> 类型的对象,jakson 专门提供了一个辅助工具类—— TypeReference<>

  • 这是一个带有泛型参数的类,我们通过这个类的泛型参数,来指定我们实际要转换的类型
forwardIndex = objectMapper.readValue
(forwardIndexFile, new TypeReference<ArrayList<DocInfo>>() {});
  • 这里相当于创建了一个匿名内部类的实例(后面 new 的部分)
    • 创建一个匿名内部类,这个类实现了 TypeReference
    • 同时再创建一个这个匿名内部类的实例
    • 创建这个实例的最主要目的,就是为了把 ArrayList<DocInfo> 这个类型信息,告诉 readValue 方法

java 中,并不能直接把一个类型作为方法的参数,而是必须得传一个具体的对象,正因为这个语法限制,我们就必须得绕一个弯。通过一个专门的泛型类,再搭配泛型参数,才能完成这个过程

Parser 类完整源码

package com.glg.javadoc_searcher;import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;public class Parser {// 先指定一个加载文档的路径private static final String INPUT_PATH = "/Users/yechiel/Desktop/Byte/code_world/docs";// 创建一个 Index 实例private Index index = new Index();public void run(){// 整个 Parser 的入口// 1. 根据指定的路径,枚举出该路径中所有的文件(HTML),这个过程需要把所有子目录中的文件都获取到ArrayList<File> fileList = new ArrayList<>();enumFile(INPUT_PATH, fileList);/*for(File file : fileList){System.out.println(file);}System.out.println(fileList.size());
*/// 2. 针对上面罗列出的文件路径,打开路径,读取文件内容,进行解析,并构建索引for(File f : fileList) {// 通过这个方法来解析单个 HTML 文件System.out.println("开始解析: "+ f.getAbsolutePath());parseHTML(f);}// 3. 把在内存中构造好的索引数据结构,保存到指定的文件中index.save();}private void parseHTML(File f) {// 1. 解析出 HTML 的标题String title = parseTitle(f);// 2. 解析出 HTML 对应的 URLString url = parseUrl(f);// 3. 解析出 HTML 对应的正文(有了正文才有后续的描述)String content = parseContent(f);// 4. 将解析出来的这些信息,加入到索引当中index.addDoc(title,url,content);}// 用来解析 HTML 里面的标题信息private String parseTitle(File f) {String name = f.getName();return name.substring(0, name.length() - ".html".length());}// 用来解析 HTML 里面的 URL 信息private String parseUrl(File f) {String part1 = "https://docs.oracle.com/javase/8/docs/";String part2 = f.getAbsolutePath().substring(INPUT_PATH.length());return part1 + part2;}// 用来解析 HTML 里面的正文信息public String parseContent(File f) {//先按照一个字符一个字符的方式来读取,以 < 和 > 来控制拷贝数据的开关StringBuilder content = new StringBuilder();try {FileReader fileReader = new FileReader(f);// 加上一个是否要进行拷贝的开关boolean isCopy = true;// 还得准备一个保存结果的 StringBuilder//StringBuilder content = new StringBuilder();while (true) {// 注意:此处的 read() 返回值是 int,不是 char// 按理说,应该是依次读一个字符,返回 char 就够了呀?// 此处使用 int 作为返回值,主要是为了表示一些非法情况// 比如说读到了文件末尾,继续读,就会返回 -1// 我们就可以根据返回的 -1 判断读完了int ret= fileReader.read();if(ret == -1) {// 表示文件读完了break;}// 这个结果不是 -1,那么就是一个合法的字符了char c = (char)ret;if(isCopy){// 开关打开的状态,遇到普通字符就应该拷贝到 StringBuilder 中if(c == '<'){// 关闭开关isCopy = false;continue;}if(c == '\n' || c == '\r'){// 为了去掉换行,把换行/回车替换成空格c = ' ';}// 其他字符,直接进行拷贝即可,把结果拷贝到最终的 StringBuilder 中content.append(c);}else {// 开关关闭的状态,暂时不拷贝,直到遇到 >if(c == '>'){isCopy = true;}}}fileReader.close();} catch (IOException e) {e.printStackTrace();}return content.toString();}// 第一个参数表示我们从哪个参数开始进行递归遍历// 第二个参数表示递归得到的结果private void enumFile(String inputPath, ArrayList<File> fileList) {File rootPath = new File(inputPath);// 把当前目录中,所包含的目录名全部获取到// listFiles 能够获取到 rootPath 当前目录下所包含的文件/目录(一层目录,不会进入子文件)File[] files = rootPath.listFiles();for(File f : files) {// 此时我们就根据当前 f 的类型,来决定是否要进行递归// 若 f 是一个普通文件,就把 f 加入到 fileList 结果中// 若 f 是一个目录,就递归调用 enumFile 方法,来进一步地获取子目录中的内容if(f.isDirectory()) {enumFile(f.getAbsolutePath(),fileList);}else {if (f.getAbsolutePath().endsWith(".html"))fileList.add(f);}}}public static void main(String[] args) {// 通过 main 方法,来实现整个制作索引的过程Parser parser = new Parser();parser.run();}
}

Index 类完整源码

package com.glg.javadoc_searcher;import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;// 通过这个类,在内存中构造索引结构
public class Index {private static final String INDEX_PATH = "/Users/yechiel/Desktop/Byte/code_world/Gitee/java_doc_searcher/";private ObjectMapper objectMapper = new ObjectMapper();// 使用数组下标表示 docIdprivate ArrayList<DocInfo> forwardIndex = new ArrayList<>();// 使用一个 哈希表 来表示倒排索引// key 就是词     value 就是一簇和这个词相关的文章private HashMap<String, ArrayList<Weight>> invertedIndex = new HashMap<>();// 这个类要提供的方法// 1. 给定一个 docId,在正排索引中,查询文档的详细信息public DocInfo getDocInfo(int docId){return forwardIndex.get(docId);}// 2. 给定一个词,在倒排索引中,查询哪些文档和这个词关联// 仔细思考这里的返回值,单纯的返回一个整数的 List 是否可行呢?这样不太好(返回整数是因为 List 里面存的是文档 id)// 词和文档之间是存在一定的“相关性”的(文档和词的相关性有强有弱),不是单一的依次排列// 所以我们再创建一个 Weight 类来处理 文档id 和 文档与词 的相关性权重public List<Weight> getInverted(String term){return invertedIndex.get(term);}// 3. 往索引中新增一个文档public void addDoc(String title, String url, String content){// 新增文档操作,需要同时给正排索引和倒排索引新增信息// 构建正排索引DocInfo docInfo = buildForward(title, url, content);// 构建倒排索引buildInverted(docInfo);}// 实现倒排索引private void buildInverted(DocInfo docInfo) {// 直接使用内部类,词频统计class WordCnt {public int titleCount;public int contentCount;}// 通过一个内部类,将两个数据装到一起了,变成一个 HashMap,更方便遍历// 这个数据结构用来统计词频HashMap<String, WordCnt> wordCntHashMap = new HashMap<>();// 3.1 针对文档标题进行分词List<Term> terms = ToAnalysis.parse(docInfo.getTitle()).getTerms();// 3.2 遍历分词结果,统计每个词出现的次数for(Term term : terms){// 先判断一下 term 是否存在String word = term.getName();WordCnt wordCnt = wordCntHashMap.get(word);if(wordCnt == null) {// 如果不存在,就创建一个新的键值对,插入进去,titleCount 设为 1WordCnt newWordCnt = new WordCnt();newWordCnt.titleCount = 1;newWordCnt.contentCount = 0;wordCntHashMap.put(word, newWordCnt);}// 如果存在,就找到之前的值,然后把对应的 titleCount + 1wordCnt.titleCount++;}// 3.3 针对正文页进行分词terms = ToAnalysis.parse(docInfo.getContent()).getTerms();// 3.4 遍历分词结果,统计每个词出现的次数for(Term term : terms) {String word = term.getName();WordCnt wordCnt = wordCntHashMap.get(word);if(wordCnt == null) {WordCnt newWordCnt = new WordCnt();newWordCnt.titleCount = 0;newWordCnt.contentCount = 1;wordCntHashMap.put(word, newWordCnt);}else{wordCnt.contentCount++;}}// 3.5 把上面的结果汇总到一个 HashMap 里面//    最终文档的权重,就设定成标题中出现的次数 * 10 + 正文中出现的次数// 3.6 遍历刚才这个 HashMap,依次来更新倒排索引中的结构// 将 Map 转换成 Set 进行遍历(Map 不能直接进行遍历)for(Map.Entry<String, WordCnt> entry : wordCntHashMap.entrySet()) {// 先根据这里的词,去倒排索引中查一查// 倒排索引中的一个值——倒排拉链List<Weight> invertedList = invertedIndex.get(entry.getKey());// 判断是不是存在的(空的)if(invertedList == null) {// 如果为空,就插入一个新的键值对ArrayList<Weight> newInvertedList = new ArrayList<>();// 把新的文档(当前的 DocInfo)构造成 Weight 对象,插入进来Weight weight = new Weight();weight.setDocId(docInfo.getDocId());// 权重计算公式:标题中出现的次数 * 10 + 正文中出现的次数weight.setWeight(entry.getValue().titleCount * 10 + entry.getValue().contentCount);newInvertedList.add(weight);invertedIndex.put(entry.getKey(), newInvertedList);}else{// 如果非空,就把当前这个文档,构造出一个 Weight 对象,插入到倒排拉链的后面Weight weight = new Weight();weight.setDocId(docInfo.getDocId());// 权重计算公式:标题中出现的次数 * 10 + 正文中出现的次数weight.setWeight(entry.getValue().titleCount * 10 + entry.getValue().contentCount);invertedList.add(weight);}}}private DocInfo buildForward(String title, String url, String content) {DocInfo docInfo = new DocInfo();docInfo.setDocId(forwardIndex.size());docInfo.setTitle(title);docInfo.setUrl(url);docInfo.setContent(content);forwardIndex.add(docInfo);return docInfo;}// 4. 把内存中的索引结构保存到磁盘中public void save(){long beg = System.currentTimeMillis();// 使用两个文件,分贝保存正排和倒排System.out.println("保存索引开始!");// 先判断一下,索引对应的目录是否存在,不存在就创建File indexPathFile = new File(INDEX_PATH);if(!indexPathFile.exists()){indexPathFile.mkdirs();}File forwardIndexFile = new File(INDEX_PATH + "fordword.txt");File invertedIndexFile = new File(INDEX_PATH + "inverted.txt");try {// 第一个参数:写到哪个文件里    第二个:对哪个对象进行写入objectMapper.writeValue(forwardIndexFile, forwardIndex);objectMapper.writeValue(invertedIndexFile, invertedIndex);}catch (IOException e) {e.printStackTrace();}long end = System.currentTimeMillis();System.out.println("保存索引完成!消耗时间为:" + (end - beg) + "ms");}// 5. 把磁盘中的索引数据加载到内存中public void load(){long beg = System.currentTimeMillis();System.out.println("加载索引开始!");// 设置加载索引的路径(和前面保存的路径一样)File forwardIndexFile = new File(INDEX_PATH + "forward.txt");File invertedIndexFile = new File(INDEX_PATH + "inverted.txt");try{// 第一个参数:从哪里读    第二个参数:当前读到的数据,按照什么类型进行解析forwardIndex = objectMapper.readValue(forwardIndexFile, new TypeReference<ArrayList<DocInfo>>() {});invertedIndex = objectMapper.readValue(invertedIndexFile, new TypeReference<HashMap<String, ArrayList<Weight>>>() {});}catch (IOException e){e.printStackTrace();}long end = System.currentTimeMillis();System.out.println("加载索引结束!消耗时间为:" + (end - beg) + "ms");}}

相关文章:

【文档搜索引擎】在内存中构造出索引结构(下)

文章目录 4.保存到磁盘中为什么要保存在磁盘中怎么保存操作步骤1. 前期准备2. 主要操作 5. 将磁盘中的数据加载到内存中Parser 类完整源码Index 类完整源码 4.保存到磁盘中 为什么要保存在磁盘中 索引本来是存储在内存中的&#xff0c;为什么要将其保存在硬盘中&#xff1f; …...

2024年《网络安全事件应急指南》

在这个信息技术日新月异的时代&#xff0c;网络攻击手段的复杂性与日俱增&#xff0c;安全威胁层出不穷&#xff0c;给企事业单位的安全防护能力带 来了前所未有的挑战。深信服安全应急响应中心&#xff08;以下简称“应急响应中心”&#xff09;编写了《网络安全事件应急指南》…...

前端的知识(部分)

11 前端的编写步骤 第一步:在HTML的页面中声明方法 第二步:在<script>中定义一个函数,其中声明一个data来为需要的数据 赋值一个初始值 第三步:编写这个方法实现对应的功能...

OPC UA、MQTT 和 HTTP性能分析及使用场景推荐

在选择适合的服务性能协议时&#xff0c;OPC UA、MQTT 和 HTTP 每种都有其独特的优势和适用场景&#xff0c;因此最佳选择取决于具体的应用需求和技术环境。以下是基于不同维度对比这三种协议的分析&#xff1a; 通信效率 OPC UA&#xff1a;通常用于车间环境&#xff0c;提供…...

并发修改导致MVCC脏写问题

并发修改导致MVCC脏写问题 一、概要 1.1 业务场景 数据库表结构设计&#xff1a; 一个主档数据&#xff0c;通过一个字段&#xff0c;逗号分隔的方式去关联其他明细信息的id。 如主档数据A&#xff0c;有3条明细数据与A关联&#xff0c;其id分别是1,2,3&#xff0c;那么其存…...

跌倒数据集,5345张图片, 使用yolo,coco json,voc xml格式进行标注,平均识别率99.5%以上

跌倒数据集&#xff0c;5345张图片&#xff0c; 使用yolo&#xff0c;coco json&#xff0c;voc xml格式进行标注&#xff0c;平均识别率99.5%以上 &#xff0c;可用于某些场景下识别人是否跌倒或摔倒并进行告警。 数据集分割 训练组99&#xff05; 5313图片 有效集0&am…...

Java转C之CMake

对于一位从 Java 转到 C 或 C 的工程师&#xff0c;理解 CMake 和其指令非常重要&#xff0c;因为 CMake 是目前 C/C 项目中最常用的构建工具。CMake 本质上是一个跨平台的自动化构建系统&#xff0c;它通过 CMakeLists.txt 文件来管理和配置项目的构建过程。在学习 CMake 的过…...

如何自己创建database.js文件来初始化本地sqlite数据库

如何自己创建database.js文件来初始化本地sqlite数据库&#xff01;下面是一个案例展示&#xff0c;帮助大家&#xff0c;快速的视线&#xff0c;本地sqlite数据库信息初始化。 为了使用 database.js 文件初始化 SQLite 数据库并存储解签内容&#xff0c;你需要按以下步骤操作。…...

【汇编语言】内中断(三) —— 中断探险:从do0到特殊响应的奇妙旅程

文章目录 前言1. do01.1 do0程序1.2 存放字符串&#xff0c;得到完整的程序1.3 分析初步完成的程序1.4 正确的完整程序1.5 分析正确的完整程序 2. 设置中断向量3. 单步中断3.1 什么是单步中断&#xff1f;3.2 CPU为什么要提供单步中断3.2.1 思考一下Debug功能3.2.2 Debug是如何…...

0006.基于SpringBoot+element付费问答系统

适合初学同学练手项目&#xff0c;部署简单&#xff0c;代码简洁清晰&#xff1b; 愿世界和平再无bug 一、系统架构 前端&#xff1a;vue| elementui 后端&#xff1a;springboot | mybatis-plus 环境&#xff1a;jdk1.8 | mysql | maven 二、登录角色 1.管理员 2.用户 …...

SpringBoot feign基于HttpStatus重试

场景 基于springboot开发的项目&#xff0c;对接第三方&#xff0c;第三方的接口有限流策略&#xff0c;某个时间段内有调用频率限制&#xff0c;返回的状态码HttpStatus不是200&#xff0c;而HttpStatus是429。现基于HttpStatus我们发起的重试。 技术点 springbootfeign fe…...

【记录49】vue2 vue-office在线预览 docx、pdf、excel文档

vue2 在线预览 docx、pdf、excel文档 docx npm install vue-office/docx vue-demi0.14.6 指定版本 npm install vue-office/docx vue-demi <template><VueOfficeDocx :src"pdf" style"height: 100vh;" rendere"rendereHandler" error&…...

正则表达式中^的用法

正则表达式中^的用法 1.用法一: 限定开头 文档上给出了解释是匹配输入的开始&#xff0c;如果多行标示被设置成了true&#xff0c;同时会匹配后面紧跟的字符 比如 /^A/会匹配"An e"中的A&#xff0c;但是不会匹配"ab A"中的A 比如(\s|^)表示空字符串或字…...

WPF 关于界面UI菜单权限(或者任意控件的显示权限)的简单管理--只是简单简单简单简单

1.定义你的User类 public class User{public User(){ID ObjectId.NewObjectId().ToString();}public string? ID { get; set; }public string? Account { get; set; }public string? Password { get; set; }public string? PasswordMD5 { get; set; }public AccountType?…...

Https身份鉴权(小迪网络安全笔记~

附&#xff1a;完整笔记目录~ ps&#xff1a;本人小白&#xff0c;笔记均在个人理解基础上整理&#xff0c;若有错误欢迎指正&#xff01; 5.2 Https&身份鉴权 引子&#xff1a;上一篇主要对Http数据包结构、内容做了介绍&#xff0c;本篇则聊聊Https、身份鉴权等技术。 …...

AngularJS 输入验证

AngularJS 输入验证 AngularJS 是一个强大的 JavaScript 框架,它允许开发者构建动态的、高性能的 Web 应用程序。在处理用户输入时,确保数据的准确性和完整性至关重要。AngularJS 提供了一套内置的输入验证机制,可以帮助开发者轻松地实现这一目标。 为什么需要输入验证? …...

【网络安全】WIFI WPA/WPA2协议:深入解析与实践

WIFI WPA/WPA2协议&#xff1a;深入解析与实践 1. WPA/WPA2 协议 1.1 监听 Wi-Fi 流量 解析 WPA/WPA2 的第一步是监听 Wi-Fi 流量&#xff0c;捕获设备与接入点之间的 4 次握手数据。然而&#xff0c;设备通常不会频繁连接或重新连接&#xff0c;为了加速过程&#xff0c;攻…...

前端使用xlsx-js-style导出Excel,带样式,并处理合并单元格边框显示不全和动态插入表头解决

一、在学习之前&#xff0c;先给出一些学习/下载地址&#xff1a; xlsx-js-style下载地址 https://github.com/gitbrent/xlsx-js-style 或者 https://www.npmjs.com/package/xlsx-js-style SheetJS中文教程&#xff1a; https://xlsx.nodejs.cn/docs/csf/cell 二、先看样…...

自动化工具ansible部署和实践

1 介绍和部署 1.1 介绍 ansible的功能 我爱你在当今的IT自动化领域&#xff0c;Ansible无疑是一个无法被忽视的重要角色。其便利性和高效性受到了广大开发者和系统管理员的一致好评&#xff0c;成为了配置管理和应用部署的首选工具。然而&#xff0c;对于一些初学者来说&#…...

无人机推流直播平台EasyDSS视频技术如何助力冬季森林防火

冬季天干物燥&#xff0c;大风天气频繁&#xff0c;是森林火灾的高发期。相比传统的人力巡查&#xff0c;无人机具有更高的灵敏度和准确性&#xff0c;尤其在夜间或浓雾天气中&#xff0c;依然能有效地监测潜在火源。 无人机可以提供高空视角和实时图像传输&#xff0c;帮助巡…...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介 前面两期文章我们介绍了I2S的读取和写入&#xff0c;一个是通过INMP441麦克风模块采集音频&#xff0c;一个是通过PCM5102A模块播放音频&#xff0c;那如果我们将两者结合起来&#xff0c;将麦克风采集到的音频通过PCM5102A播放&#xff0c;是不是就可以做一个扩音器了呢…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

图解JavaScript原型:原型链及其分析 | JavaScript图解

​​ 忽略该图的细节&#xff08;如内存地址值没有用二进制&#xff09; 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么&#xff1a;保存在堆中一块区域&#xff0c;同时在栈中有一块区域保存其在堆中的地址&#xff08;也就是我们通常说的该变量指向谁&…...