自己动手写数据库:基于哈希的静态索引设计

数据库设计中有一项至关重要的技术难点,那就是给定特定条件进行查询时,我们需要保证速度尽可能快。假设我们有一个 STUDENT 表,表中包含学生名字,年龄,专业等字段,当我们要查询给定年龄数值的记录,如果我们能把所有记录以年龄字段排序,那么通过二分查找,我们就能快速定位满足条件的记录。如果表中包含N=1,000,000 条记录,通过二分查找就能通过大概 logN = 20 次即可,但是要遍历所有记录来找,就得查询一百万次。

但根据某个字段来排序记录,当查询以另外字段查询时就无法得到相应加速。因此如何通过合适算法,让数据进行相应组织,使得查询根据不同字段进行时都能得到相应加速是数据库设计的核心难题。

在不改变表结构的情况下,要能根据不同字段加快查询速度,就需要索引制度。索引本质是一个文件,其中的数据是对给定表的记录分布进行描述和说明。索引文件中的数据以一条条记录的方式存在,一条记录包含两个字段,一个字段叫 datarid,它是一个指针,指向某条特定数据所在的位置,另一个字段叫 dataval,它可以是建立索引的字段的值,如果我们要想针对两个字段 age, name 进行索引,那么索引文件钟就包含两种记录,一种记录的 dataval 对应 age 的值,另一种记录的 dataval 存储 name 的值,然后记录根据 dataval 的值排序,于是我们要根据 age 查询时,我们先通过折半查找在第一种记录钟查询到 VALUE 为给定查询值的记录,然后通过 datarid 字段获取给定记录在硬盘的位置,另外需要注意的是,索引信息也是存储在文件中,获取索引信息也需要访问磁盘,因此我们需要使用好的所有算法尽可能减少在查询索引信息时对磁盘区块的读取。

使用索引文件创建索引数据来记录每条记录的位置还有一个问题,那就是记录会删除和修改,一旦相关记录删除和修改后,索引中的数据就需要进行相应变动,这里我们就需要 B 树,B+树等相关算法的支持。

还需要注意的是,一旦能快速定位所需记录的位置,我们就能定位记录所在的区块从而大大减少对硬盘的访问次数。但也有例外的情况,当建立索引的字段取值重复性很小时,索引的效率就好,如果索引字段取值的重复性很高,那么效率反而有可能会降低。

我们把索引建立的基本流程展示如下:
请添加图片描述
1,当解释执行索引建立的 SQL 语句时,例如 create majoridIDX on student (majorid),
create nameIDX on student (name), 启动索引建立流程
2,索引流程首先创建专门存储索引信息的表 idxcat,其字段为 indexname, tablename, fildname,这些字段分别用于存储索引的名称,建立索引的表名和字段名称。
3,选择索引算法,这里我们先使用前面说的哈希索引。我们使用一个哈希函数hash,假设他能将输入的数据哈希到 0-1000 这范围内的整数值, 假设字段 majorid 的取值 20和 100 经过该函数后输出结果相同为 39,那么代码将创建一个名为 majoroid#39 的记录表来存储这两条记录的访问信息(blockid 和 offset),上图,该表的字段为 dataval,block, id 分别用于存储记录对应索引字段的值,记录所在的 blockid 和 offset 也就是偏移。

在上面例子中由于 majorid 为 20 和 100 的记录都哈希到数值 39,因此他们这两条记录的存储信息都存储在表 majorid#39 这个表中,记录中字段为 name="jim"的记录,由于"jim"哈希的结果为 69,因此该记录的存储信息放置在表 name#69 中。

哈希索引的效率取决于所寻求哈希函数的取值范围,假设函数哈希结果取值范围为 0-N,那么对于一个包含 M 条记录的的表,他对应记录的存储信息将放置在 M/N 个哈希记录表中。哈希索引法在理论上能将记录的查询时间从 M 优化到 M/N。
4,在执行 select 语句进行记录查询时,首先在索引表 idxcat 中查询给定表和对应字段是否已经建立了索引,如果建立了,那么直接从对应的查询记录表中获得记录的存储信息,然后直接从硬盘读取记录数据。

我们看对应代码实现,索引管理器的实现依然放在 metadata_manager 路径下,创建一个名为index_manager.go 的文件,增加代码如下:

package metadata_manager

//索引管理器需要使用到后面才讲到的SQL查询和索引算法知识,所以先放一放
import (
	"query"
	rm "record_manager"
	"tx"
)

type IndexManager struct {
	layout  *rm.Layout
	tblMgr  *TableManager
	statMgr *StatManager
}

func NewIndexManager(isNew bool, tblMgr *TableManager, statMgr *StatManager, tx *tx.Transation) *IndexManager {
	if isNew {
		//索引元数据表包含三个字段,索引名,对应的表名,被索引的字段名
		sch := rm.NewSchema()
		sch.AddStringField("indexname", MAX_NAME)
		sch.AddStringField("tablename", MAX_NAME)
		sch.AddStringField("fieldname", MAX_NAME)
		tblMgr.CreateTable("idxcat", sch, tx)
	}

	idxMgr := &IndexManager{
		tblMgr:  tblMgr,
		statMgr: statMgr,
		layout:  tblMgr.GetLayout("idxcat", tx),
	}

	return idxMgr
}

func (i *IndexManager) CreateIndex(idxName string, tblName string, fldName string, tx *tx.Transation) {
	//创建索引时就为其在idxcat索引元数据表中加入一条记录
	ts := query.NewTableScan(tx, "idxcat", i.layout)
	ts.BeforeFirst()
	ts.Insert()
	ts.SetString("indexname", idxName)
	ts.SetString("tablename", tblName)
	ts.SetString("fieldname", fldName)

	ts.Close()
}

func (i *IndexManager) GetIndexInfo(tblName string, tx *tx.Transation) map[string]*IndexInfo {
	result := make(map[string]*IndexInfo)
	ts := query.NewTableScan(tx, "idxcat", i.layout)
	ts.BeforeFirst()
	for ts.Next() {
		if ts.GetString("tablename") == tblName {
			idxName := ts.GetString("indexname")
			fldName := ts.GetString("fieldname")
			tblLayout := i.tblMgr.GetLayout(tblName, tx)
			tblSi := i.statMgr.GetStatInfo(tblName, tblLayout, tx)
			schema, ok := (tblLayout.Schema()).(*rm.Schema)
			if ok != true {
				panic("convert schema interface error")
			}
			ii := NewIndexInfo(idxName, fldName, schema, tx, tblSi)
			result[fldName] = ii
		}
	}

	ts.Close()

	return result
}

IndexManager 的作用是创建索引记录表 idxcat,该表记录有哪些索引建立在哪些表上,idxcat 表包含三个字段,分别是 indexname, tablename 和 fildname。由于为了支持能够灵活的选取不同的索引算法,在代码上我们增加了一个中间件叫 IndexInfo,由它来负责创建所需要的索引算法对象。由于我们可能对不同的字段采取不同的索引算法,因此 GetIndexInfo 返回了一个 map 对象,该字典的 key 对应索引字段的名称,value 对应 IndexInfo 对象,我们看看后者的实现:

package metadata_manager

import (
	rm "record_manager"
	"tx"
)

type IndexInfo struct {
	idxName   string
	fldName   string
	tblSchema *rm.Schema
	tx        *tx.Transation
	idxLayout *rm.Layout
	si        *StatInfo
}

func NewIndexInfo(idxName string, fldName string, tblSchema *rm.Schema,
	tx *tx.Transation, si *StatInfo) *IndexInfo {
	idxInfo := &IndexInfo{
		idxName:   idxName,
		fldName:   fldName,
		tx:        tx,
		tblSchema: tblSchema,
		si:        si,
		idxLayout: nil,
	}

	idxInfo.idxLayout = idxInfo.CreateIdxLayout()

	return idxInfo
}

func (i *IndexInfo) Open() Index {
	//在这里 构建不同的哈希算法对象 s
	return NewHashIndex(i.tx, i.idxName, i.idxLayout)
}

func (i *IndexInfo) BlocksAccessed() int {
	rpb := int(i.tx.BlockSize()) / i.idxLayout.SlotSize()
	numBlocks := i.si.RecordsOutput() / rpb
	return HashIndexSearchCost(numBlocks, rpb)
}

func (i *IndexInfo) RecordsOutput() int {
	return i.si.RecordsOutput() / i.si.DistinctValues(i.fldName)
}

func (i *IndexInfo) DistinctValues(fldName string) int {
	if i.fldName == fldName {
		return 1
	}

	return i.si.DistinctValues(fldName)
}

func (i *IndexInfo) CreateIdxLayout() *rm.Layout {
	sch := rm.NewSchema()
	sch.AddIntField("block")
	sch.AddIntField("id")
	if i.tblSchema.Type(i.fldName) == rm.INTEGER {
		sch.AddIntField("dataval")
	} else {
		fldLen := i.tblSchema.Length(i.fldName)
		sch.AddStringField("dataval", fldLen)
	}

	return rm.NewLayoutWithSchema(sch)
}

我们注意看 IndexInfo 的实现中有一个接口例如 DistincValues 等跟我们以前实现的 StateInfo 一样,他负责返回当前索引算法效率的相关信息,对于哈希索引而言,很多效率指标都是固定的,搜索的效率就是 M/N,其中 M 是表中记录的条数,N 就是索引函数取值的范围。下面我们看哈希索引的实现,新建 hash_index.go 文件,输入代码如下:

package metadata_manager

import (
	"fmt"
	"query"
	rm "record_manager"
	"tx"
)

const (
	NUM_BUCKETS = 100
)

type HashIndex struct {
	tx        *tx.Transation
	idxName   string
	layout    *rm.Layout
	searchKey *query.Constant
	ts        *query.TableScan
}

func NewHashIndex(tx *tx.Transation, idxName string, layout *rm.Layout) *HashIndex {
	return &HashIndex{
		tx:      tx,
		idxName: idxName,
		layout:  layout,
		ts:      nil,
	}
}

func (h *HashIndex) BeforeFirst(searchKey *query.Constant) {
	h.Close()
	h.searchKey = searchKey
	bucket := searchKey.HashCode() % NUM_BUCKETS
	//构造索引记录对应的表名称
	tblName := fmt.Sprintf("%s#%d", h.idxName, bucket)
	h.ts = query.NewTableScan(h.tx, tblName, h.layout)
}

func (h *HashIndex) Next() bool {
	for h.ts.Next() {
		if h.ts.GetVal("dataval").Equals(h.searchKey) {
			return true
		}
	}

	return false
}

func (h *HashIndex) GetDataRID() *rm.RID {
	//返回记录所在的区块信息
	blkNum := h.ts.GetInt("block")
	id := h.ts.GetInt("id")
	return rm.NewRID(blkNum, id)
}

func (h *HashIndex) Insert(val *query.Constant, rid *rm.RID) {
	h.BeforeFirst(val)
	h.ts.Insert()
	h.ts.SetInt("block", rid.BlockNumber())
	h.ts.SetInt("id", rid.Slot())
	h.ts.SetVal("dataval", val)
}

func (h *HashIndex) Delete(val *query.Constant, rid *rm.RID) {
	h.BeforeFirst(val)
	for h.Next() {
		if h.GetDataRID().Equals(rid) {
			h.ts.Delete()
			return
		}
	}
}

func (h *HashIndex) Close() {
	if h.ts != nil {
		h.ts.Close()
	}
}

func HashIndexSearchCost(numBlocks int, rpb int) int {
	return numBlocks / NUM_BUCKETS
}

HashIndex 对象会根据索引字段的名称和哈希计算结果来新建一个存储被索引记录区块信息的表,如果索引字段为 majorid,记录对应 majorid 字段的取值为 20,哈希计算结果为 39,他就会创建名为 majorid#39 的表,表中包含三个字段分别是 dataval, block, id,dataval 存储哈希字段的取值,block 对应记录所在的区块号,id 对应偏移,当我们想要读取给定记录时,只要从该表中拿出 block 和 id 的值,就能直接读取磁盘上给定位置的数据。

在MetaDataManager 的实现中,我们需要在他的实现中增加索引管理器的创建,对应代码如下:

package metadata_manager

import (
	"query"
	rm "record_manager"
	"tx"
)

type MetaDataManager struct {
	tblMgr   *TableManager
	viewMgr  *ViewManager
	statMgr  *StatManager
	constant *query.Constant
	//索引管理器以后再处理
	idxMgr *IndexManager
}

func NewMetaDataManager(isNew bool, tx *tx.Transation) *MetaDataManager {
	metaDataMgr := &MetaDataManager{
		tblMgr:   NewTableManager(isNew, tx),
		constant: nil,
	}

	metaDataMgr.viewMgr = NewViewManager(isNew, metaDataMgr.tblMgr, tx)
	metaDataMgr.statMgr = NewStatManager(metaDataMgr.tblMgr, tx)
	metaDataMgr.idxMgr = NewIndexManager(isNew, metaDataMgr.tblMgr,
		metaDataMgr.statMgr, tx)

	return metaDataMgr
}

func (m *MetaDataManager) CreateTable(tblName string, sch *rm.Schema, tx *tx.Transation) {
	m.tblMgr.CreateTable(tblName, sch, tx)
}

func (m *MetaDataManager) CreateView(viewName string, viewDef string, tx *tx.Transation) {
	m.viewMgr.CreateView(viewName, viewDef, tx)
}

func (m *MetaDataManager) GetLayout(tblName string, tx *tx.Transation) *rm.Layout {
	return m.tblMgr.GetLayout(tblName, tx)
}

func (m *MetaDataManager) GetViewDef(viewName string, tx *tx.Transation) string {
	return m.viewMgr.GetViewDef(viewName, tx)
}

func (m *MetaDataManager) GetStatInfo(tblName string, layout *rm.Layout, tx *tx.Transation) *StatInfo {
	return m.statMgr.GetStatInfo(tblName, layout, tx)
}

func (m *MetaDataManager) CreateIndex(idxName string, tblName string, fldName string, tx *tx.Transation) {
	m.idxMgr.CreateIndex(idxName, tblName, fldName, tx)
}

func (m *MetaDataManager) GetIndexInfo(tblName string, tx *tx.Transation) map[string]*IndexInfo {
	return m.idxMgr.GetIndexInfo(tblName, tx)
}

最后我们在 main.go 中将上面代码调用起来看看运行效果:

package main

import (
	bmg "buffer_manager"
	fm "file_manager"
	"fmt"
	lm "log_manager"
	metadata_manager "metadata_management"
	"parser"
	"planner"
	"query"
	"tx"
)
func PrintStudentTable(tx *tx.Transation, mdm *metadata_manager.MetaDataManager) {
	queryStr := "select name, majorid, gradyear from STUDENT"
	p := parser.NewSQLParser(queryStr)
	queryData := p.Query()
	test_planner := planner.CreateBasicQueryPlanner(mdm)
	test_plan := test_planner.CreatePlan(queryData, tx)
	test_interface := (test_plan.Open())
	test_scan, _ := test_interface.(query.Scan)
	for test_scan.Next() {
		fmt.Printf("name: %s, majorid: %d, gradyear: %d\n",
			test_scan.GetString("name"), test_scan.GetInt("majorid"),
			test_scan.GetInt("gradyear"))
	}
}
func TestIndex() {
	file_manager, _ := fm.NewFileManager("student", 4096)
	log_manager, _ := lm.NewLogManager(file_manager, "logfile.log")
	buffer_manager := bmg.NewBufferManager(file_manager, log_manager, 3)
	tx := tx.NewTransation(file_manager, log_manager, buffer_manager)
	fmt.Printf("file manager is new: %v\n", file_manager.IsNew())
	mdm := metadata_manager.NewMetaDataManager(file_manager.IsNew(), tx)

	//创建 student 表,并插入一些记录
	updatePlanner := planner.NewBasicUpdatePlanner(mdm)
	createTableSql := "create table STUDENT (name varchar(16), majorid int, gradyear int)"
	p := parser.NewSQLParser(createTableSql)
	tableData := p.UpdateCmd().(*parser.CreateTableData)
	updatePlanner.ExecuteCreateTable(tableData, tx)

	insertSQL := "insert into STUDENT (name, majorid, gradyear) values(\"tylor\", 30, 2020)"
	p = parser.NewSQLParser(insertSQL)
	insertData := p.UpdateCmd().(*parser.InsertData)
	updatePlanner.ExecuteInsert(insertData, tx)
	insertSQL = "insert into STUDENT (name, majorid, gradyear) values(\"tom\", 35, 2023)"
	p = parser.NewSQLParser(insertSQL)
	insertData = p.UpdateCmd().(*parser.InsertData)
	updatePlanner.ExecuteInsert(insertData, tx)

	fmt.Println("table after insert:")
	PrintStudentTable(tx, mdm)
	//在 student 表的 majorid 字段建立索引
	mdm.CreateIndex("majoridIdx", "STUDENT", "majorid", tx)
	//查询建立在 student 表上的索引并根据索引输出对应的记录信息
	studetPlan := planner.NewTablePlan(tx, "STUDENT", mdm)
	updateScan := studetPlan.Open().(*query.TableScan)
	//先获取每个字段对应的索引对象,这里我们只有 majorid 建立了索引对象
	indexes := mdm.GetIndexInfo("STUDENT", tx)
	//获取 majorid 对应的索引对象
	majoridIdxInfo := indexes["majorid"]
	//将改rid 加入到索引表
	majorIdx := majoridIdxInfo.Open()
	updateScan.BeforeFirst()
	for updateScan.Next() {
		//返回当前记录的 rid
		dataRID := updateScan.GetRid()
		dataVal := updateScan.GetVal("majorid")
		majorIdx.Insert(dataVal, dataRID)
	}

	//通过索引表获得给定字段内容的记录
	majorid := 35
	majorIdx.BeforeFirst(query.NewConstantWithInt(&majorid))
	for majorIdx.Next() {
		datarid := majorIdx.GetDataRID()
		updateScan.MoveToRid(datarid)
		fmt.Printf("student name :%s, id: %d\n", updateScan.GetScan().GetString("name"),
			updateScan.GetScan().GetInt("majorid"))
	}

	majorIdx.Close()
	updateScan.GetScan().Close()
	tx.Commit()

}

func main() {
	TestIndex()
}

在代码中我们先创建 STUDENT表,插入两条记录,然后创建一个名为 majoridIdx 的索引,该索引对应的字段就是 majorid,然后代码通过 IndexInfo 创建 HashIndex 对象,接着代码遍历 STUDENT 表中的每条记录,获取这些记录对应的 blockid 和偏移 offset,HashIndex 将对应记录的字段值进行哈希后创建对应的索引记录表,然后将每条记录的 block id 和 offset 插入记录表中。

最后代码遍历创建的索引记录表,从中找到索引值为 35 的记录,然后取出记录对应的 block id 和 offset,通过这两个信息直接从磁盘上将记录信息读取并显示出来,上面代码运行后结果如下:

file manager is new: true
table after insert:
name: tylor, majorid: 30, gradyear: 2020
name: tom, majorid: 35, gradyear: 2023
student name :tom, id: 35
transation 1  committed

从输出我们可以看到,代码能直接通过索引记录表的记录信息直接查找想要的记录,不用向我们前面做的那样,在查询想要的记录时,将整个表的每条记录都搜索一遍,由此我们就能有效的提升查询效率。代码下载:
https://github.com/wycl16514/database_hashindex

更多内容和视频讲解演示请在 b 站搜索 coding迪尼斯

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何一键展示全平台信息?Python手把手教你搭建自己的自媒体展示平台

前言 灵感源于之前写过的Github中Readme.md中可以插入自己的js图片和动态api解析模块&#xff0c;在展示方面十分的美观&#xff1a; 这方面原理可以简化为&#xff0c;在Markdown中&#xff0c;你可以使用HTML标签来添加图像&#xff0c;就像这样&#xff1a; <tr><…

轻松设置Facebook自动隐藏评论和删除评论功能

Facebook作为海外营销的最大流量平台之一&#xff0c;是很多跨境卖家争夺的市场&#xff0c;希望可以通过Facebook这个全球性的平台来推广自己的产品或服务。身处这个竞争激烈的市场&#xff0c;任何一条负面评论或不当言论出现在你的品牌页面上都可能影响到品牌形象&#xff0…

晶核新手必备攻略,干货满满!

晶核游戏以其独特的玩法和丰富的内容吸引着众多玩家。然而&#xff0c;对于一些追求效率和资源的玩家来说&#xff0c;单开游戏往往难以满足他们的需求。多开游戏成为了一个不错的选择&#xff0c;它能帮助玩家更快地获取资源&#xff0c;提升账号实力。下面将为大家分享一些晶…

ardupilot开发 --- 远程标识 篇

1. wifi协议 https://zhuanlan.zhihu.com/p/660568077 AP 无线接入点 路由器STA 站点 接入路由器的终端SSID 标识符 无线网络的名称信标祯 Beacon AP通过广播Beacon祯来告诉想要接入者(STA)无线网络的信息&#xff0c;如SSIDWLAN数据帧 Wi-Fi网络中传输数据时所使用的数据帧格…

Docker部署Nexus Maven私服并且实现远程访问Nexus界面

目录 ⛳️推荐 1. Docker安装Nexus 2. 本地访问Nexus 3. Linux安装Cpolar 4. 配置Nexus界面公网地址 5. 远程访问 Nexus界面 6. 固定Nexus公网地址 7. 固定地址访问Nexus ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&am…

Linux:基本指令篇

文章目录 前言1.ls 指令2.pwd命令3.cd 指令4.touch指令5.mkdir指令&#xff08;重要&#xff09;6.rmdir指令 && rm 指令&#xff08;重要&#xff09;7.man指令&#xff08;重要&#xff09;8.cp指令&#xff08;重要&#xff09;9.mv指令&#xff08;重要&#xff09…

【THM】Passive Reconnaissance(被动侦察)-初级渗透测试

介绍 欢迎来到网络安全模块的第一个房间,该模块涵盖: 1.被动侦察 2.主动侦察 3.Nmap实时主机发现 4.Nmap基本端口扫描 5.Nmap高级端口扫描 6.Nmap后端口扫描 7.协议和服务器 8.协议和服务器2 9.网络安全挑战 在这个房间里,在我们定义被动侦察和主动侦察之后,我们…

友思特方案 | 构建缤纷:可调谐光源的荧光成像的应用

导读 生物荧光分析常常伴随使用多种荧光染料的需求。结合多通道光源技术与高性能成像设备&#xff0c;友思特可调谐光源荧光检测成像方案&#xff0c;以其灵活的系统组成&#xff0c;满足了丰富的荧光检测应用需求。 生物荧光分析技术 激发荧光成像技术是研究生物学过程的一种…

Python基于微博的大数据舆论情感分析、微博大数据舆论分析可视化系统

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…

无需注册即可使用 ChatGPT;Poe 创始人:大模型幻觉是创业公司的机会丨RTE 开发者日报 Vol.176

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE &#xff08;Real Time Engagement&#xff09; 领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、…

如何重塑IT运维核心竞争力?擎创夏洛克智能观测中心这么做

数字化浪潮的涌动&#xff0c;推进着金融企业业务全面进入线上时代。在云原生的环境下&#xff0c;为满足业务敏捷构建、高效交付、高性能并发等需求&#xff0c;企业对运维技术的要求日益增加。同时&#xff0c;在不断转型的过程中&#xff0c;受敏稳“双态”业务发展的影响&a…

YB4051系列设备是高度集成的 Li-lon 和 Li-Pol 线性充电器

概述&#xff1a; YB4051系列设备是高度集成的 Li-lon 和 Li-Pol 线性充电器&#xff0c;针对便携式应用的小容量电池。它是一个完整的恒流/恒压线性充电器。不需要外部感应电阻&#xff0c;由于内部 MOSFET 结构&#xff0c;不需要阻塞二极管。它可以提供高达300mA 的充电电流…

SpringBoot登录校验(三)JWT令牌

SpringBoot 登录认证&#xff08;一&#xff09;-CSDN博客 SpringBoot 登录认证&#xff08;二&#xff09;-CSDN博客 SpringBoot登录校验&#xff08;三&#xff09;-CSDN博客 前面我们介绍了传统的会话跟踪技术cookie和sesstion&#xff0c;本节讲解令牌技术。这里所提到的…

MyBatis 解决上篇的参数绑定问题以及XML方式交互

前言 上文:MyBatis 初识简单操作-CSDN博客 上篇文章我们谈到的Spring中如何使用注解对Mysql进行交互 但是我们发现我们返回出来的数据明显有问题 我们发现后面三个字段的信息明显没有展示出来 下面我们来谈谈解决方案 解决方案 这里的原因本质上是因为mysql中和对象中的字段属性…

基于粒子群算法的多目标搜索算法

粒子速度类似于梯度下降的方向 粒子群优化算法(Particle Swarm Optimization, PSO)的详细解读 - 知乎 (zhihu.com) 粒子群优化算法介绍 与多种群遗传算法有相似之处

未来购物新篇章:臻奶惠无人新零售

未来购物新篇章&#xff1a;臻奶惠无人新零售 随着科技的不断进步和消费者购物习惯的变化&#xff0c;无人新零售已经成为零售行业的一大趋势&#xff0c;它不仅重新定义了购物体验&#xff0c;也为零售行业带来了前所未有的变革。无人新零售&#xff0c;一种融合了AI技术、物…

保护Android应用安全:全面探究代码混淆在加固中的作用

Android APP 加固是优化 APK 安全性的一种方法&#xff0c;常见的加固方式有混淆代码、加壳、数据加密、动态加载等。下面介绍一下 Android APP 加固的具体实现方式。 混淆代码 使用 ipaguard工具可以对代码进行混淆&#xff0c;使得反编译出来的代码很难阅读和理解&#xff…

c++的STL(6)-- map和multimap

map和multimap概述 map和multimap中存储的是使用pair对象的键值对。 map和multimap底层也是使用红黑树的数据结构进行实现的。所以&#xff0c;map和multimap内部存储的键值对也是有序的。并且内部数据也是使用链表的形式进行关联。所以其的迭代器和指针&#xff0c;也只能进行…

淘宝商品详情用于选品上架,数据分析,代购商城建站,ERP系统商品数据选品,价格监控业务场景

大数据时代&#xff0c; 数据收集不仅是科学研究的基石&#xff0c; 更是企业决策的关键。 然而&#xff0c;如何高效地收集数据 成了摆在我们面前的一项重要任务。 本文将为你揭示&#xff0c; 一系列实时数据采集方法&#xff0c; 助你在信息洪流中&#xff0c; 找到…

分享一个宝藏课程:近屿AIGC工程师和产品经理训练营

说起AIGC&#xff0c;大家都会自然地想到近两年火的一塌糊涂的ChatGPT,而开发出它的OpenAI&#xff0c;去年年底的年化收入已突破16亿美元&#xff0c;部分OpenAI的管理层认为&#xff0c;按目前进度&#xff0c;到2024年底&#xff0c;OpenAI的年化收入至少能达到50亿美元。而…