postgres源码解析55 Brin Index--2(brinbuild流程)

news2025/2/21 23:28:16

  上一篇讲解了brin index的基本概念以及页布局postgres源码解析54 Brin Index–1,后续会从源码角度对索引的构建、维护等方面进行深入讲解。

1 关键数据结构

在这里插入图片描述

2 brinbuild执行流程图

**加粗样式**

3 brinbuild 函数详解

1 首先调用brin_matepage_init初始化brin meta元数据页,并构造对应的XLOG日志填充入至WAL buffer中;
2 紧接着调用brinRevmapInitialize初始化brin revmap映射页、BrinBuildState结构体用于记录后续brin tuple状态信息;
3 按heap表物理块的顺序扫描,构造对应的brin index 元组信息,元组的构造流程由回调函数brinbuildCallback实现;
4 调用form_and_insert_tuple将索引元组插入brin regular常规页中,同时将此元组的TID信息记录至brin revmap映射页中;
5 为此插入动作构造XLOG日志并插入至WAL buffer中;
6 最后释放锁资源,如发生页扩展情况需更新对应的FSM信息;

/*
 * brinbuild() -- build a new BRIN index.
 */
IndexBuildResult *
brinbuild(Relation heap, Relation index, IndexInfo *indexInfo)
{
	IndexBuildResult *result;
	double		reltuples;
	double		idxtuples;
	BrinRevmap *revmap;
	BrinBuildState *state;
	Buffer		meta;
	BlockNumber pagesPerRange;

	/*
	 * We expect to be called exactly once for any index relation.
	 */
	if (RelationGetNumberOfBlocks(index) != 0)
		elog(ERROR, "index \"%s\" already contains data",
			 RelationGetRelationName(index));

	/*
	 * Critical section not required, because on error the creation of the
	 * whole relation will be rolled back.
	 */

	meta = ReadBuffer(index, P_NEW);
	Assert(BufferGetBlockNumber(meta) == BRIN_METAPAGE_BLKNO);
	LockBuffer(meta, BUFFER_LOCK_EXCLUSIVE);

	brin_metapage_init(BufferGetPage(meta), BrinGetPagesPerRange(index),
					   BRIN_CURRENT_VERSION);
	MarkBufferDirty(meta);

	if (RelationNeedsWAL(index))
	{
		xl_brin_createidx xlrec;
		XLogRecPtr	recptr;
		Page		page;

		xlrec.version = BRIN_CURRENT_VERSION;
		xlrec.pagesPerRange = BrinGetPagesPerRange(index);

		XLogBeginInsert();
		XLogRegisterData((char *) &xlrec, SizeOfBrinCreateIdx);
		XLogRegisterBuffer(0, meta, REGBUF_WILL_INIT | REGBUF_STANDARD);

		recptr = XLogInsert(RM_BRIN_ID, XLOG_BRIN_CREATE_INDEX);

		page = BufferGetPage(meta);
		PageSetLSN(page, recptr);
	}

	UnlockReleaseBuffer(meta);

	/*
	 * Initialize our state, including the deformed tuple state.
	 */
	revmap = brinRevmapInitialize(index, &pagesPerRange, NULL);
	state = initialize_brin_buildstate(index, revmap, pagesPerRange);

	/*
	 * Now scan the relation.  No syncscan allowed here because we want the
	 * heap blocks in physical order.
	 */
	reltuples = table_index_build_scan(heap, index, indexInfo, false, true,
									   brinbuildCallback, (void *) state, NULL);

	/* process the final batch */
	form_and_insert_tuple(state);

	/* release resources */
	idxtuples = state->bs_numtuples;
	brinRevmapTerminate(state->bs_rmAccess);
	terminate_brin_buildstate(state);

	/*
	 * Return statistics
	 */
	result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));

	result->heap_tuples = reltuples;
	result->index_tuples = idxtuples;

	return result;
}

4 brin_form_tuple

  brin tuple在内存中形式为BrinMemTuple,磁盘形式为BrinTuple,因此在写入磁盘前需要将BrinMemTuple转换成BrinTuple;其执行流程为:
1 首先根据brdesc->bd_totalstored为values、nulls、phony_nullbitmap与untoasted_values数组申请内存空间;
2 遍历brdesc->bd_tupdesc->natts属性,检查tuple是否存在空值,如果存在的需要将在nulls数组的对应元素置为true;
3 后续依次将tuple中的数据读出,并填充至values数组中;
4 遍历完brin index所有属性后,开始计算磁盘形式Brin index的长度lens;
在这里插入图片描述
5 申请大小为lens的内存空间rettuple,填充rettuple->bt_blkno与rettuple->bt_info属性,后续调用heap_fill_tuple将values数组中的数值依次填充至rettuple的数据域区;
6 后续填充bitmap区域,设置null 位码;
7 最后更新bt_info标识信息,返回rettuple地址。

/*
 * Generate a new on-disk tuple to be inserted in a BRIN index.
 *
 * See brin_form_placeholder_tuple if you touch this.
 */
BrinTuple *
brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, BrinMemTuple *tuple,
				Size *size)
{
	Datum	   *values;
	bool	   *nulls;
	bool		anynulls = false;
	BrinTuple  *rettuple;
	int			keyno;
	int			idxattno;
	uint16		phony_infomask = 0;
	bits8	   *phony_nullbitmap;
	Size		len,
				hoff,
				data_len;
	int			i;

#ifdef TOAST_INDEX_HACK
	Datum	   *untoasted_values;
	int			nuntoasted = 0;
#endif

	Assert(brdesc->bd_totalstored > 0);

	values = (Datum *) palloc(sizeof(Datum) * brdesc->bd_totalstored);
	nulls = (bool *) palloc0(sizeof(bool) * brdesc->bd_totalstored);
	phony_nullbitmap = (bits8 *)
		palloc(sizeof(bits8) * BITMAPLEN(brdesc->bd_totalstored));

#ifdef TOAST_INDEX_HACK
	untoasted_values = (Datum *) palloc(sizeof(Datum) * brdesc->bd_totalstored);
#endif

	/*
	 * Set up the values/nulls arrays for heap_fill_tuple
	 */
	idxattno = 0;
	for (keyno = 0; keyno < brdesc->bd_tupdesc->natts; keyno++)
	{
		int			datumno;

		/*
		 * "allnulls" is set when there's no nonnull value in any row in the
		 * column; when this happens, there is no data to store.  Thus set the
		 * nullable bits for all data elements of this column and we're done.
		 */
		if (tuple->bt_columns[keyno].bv_allnulls)
		{
			for (datumno = 0;
				 datumno < brdesc->bd_info[keyno]->oi_nstored;
				 datumno++)
				nulls[idxattno++] = true;
			anynulls = true;
			continue;
		}

		/*
		 * The "hasnulls" bit is set when there are some null values in the
		 * data.  We still need to store a real value, but the presence of
		 * this means we need a null bitmap.
		 */
		if (tuple->bt_columns[keyno].bv_hasnulls)
			anynulls = true;

		/*
		 * Now obtain the values of each stored datum.  Note that some values
		 * might be toasted, and we cannot rely on the original heap values
		 * sticking around forever, so we must detoast them.  Also try to
		 * compress them.
		 */
		for (datumno = 0;
			 datumno < brdesc->bd_info[keyno]->oi_nstored;
			 datumno++)
		{
			Datum value = tuple->bt_columns[keyno].bv_values[datumno];

#ifdef TOAST_INDEX_HACK

			/* We must look at the stored type, not at the index descriptor. */
			TypeCacheEntry	*atttype = brdesc->bd_info[keyno]->oi_typcache[datumno];

			/* Do we need to free the value at the end? */
			bool free_value = false;

			/* For non-varlena types we don't need to do anything special */
			if (atttype->typlen != -1)
			{
				values[idxattno++] = value;
				continue;
			}

			/*
			 * Do nothing if value is not of varlena type. We don't need to
			 * care about NULL values here, thanks to bv_allnulls above.
			 *
			 * If value is stored EXTERNAL, must fetch it so we are not
			 * depending on outside storage.
			 *
			 * XXX Is this actually true? Could it be that the summary is
			 * NULL even for range with non-NULL data? E.g. degenerate bloom
			 * filter may be thrown away, etc.
			 */
			if (VARATT_IS_EXTERNAL(DatumGetPointer(value)))
			{
				value = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *)
															  DatumGetPointer(value)));
				free_value = true;
			}

			/*
			 * If value is above size target, and is of a compressible datatype,
			 * try to compress it in-line.
			 */
			if (!VARATT_IS_EXTENDED(DatumGetPointer(value)) &&
				VARSIZE(DatumGetPointer(value)) > TOAST_INDEX_TARGET &&
				(atttype->typstorage == 'x' || atttype->typstorage == 'm'))
			{
				Datum		cvalue = toast_compress_datum(value);

				if (DatumGetPointer(cvalue) != NULL)
				{
					/* successful compression */
					if (free_value)
						pfree(DatumGetPointer(value));

					value = cvalue;
					free_value = true;
				}
			}

			/*
			 * If we untoasted / compressed the value, we need to free it
			 * after forming the index tuple.
			 */
			if (free_value)
				untoasted_values[nuntoasted++] = value;

#endif

			values[idxattno++] = value;
		}
	}

	/* Assert we did not overrun temp arrays */
	Assert(idxattno <= brdesc->bd_totalstored);

	/* compute total space needed */
	len = SizeOfBrinTuple;
	if (anynulls)
	{
		/*
		 * We need a double-length bitmap on an on-disk BRIN index tuple; the
		 * first half stores the "allnulls" bits, the second stores
		 * "hasnulls".
		 */
		len += BITMAPLEN(brdesc->bd_tupdesc->natts * 2);
	}

	len = hoff = MAXALIGN(len);

	data_len = heap_compute_data_size(brtuple_disk_tupdesc(brdesc),
									  values, nulls);
	len += data_len;

	len = MAXALIGN(len);

	rettuple = palloc0(len);
	rettuple->bt_blkno = blkno;
	rettuple->bt_info = hoff;

	/* Assert that hoff fits in the space available */
	Assert((rettuple->bt_info & BRIN_OFFSET_MASK) == hoff); // BRIN_OFFSET_MASK= 0x1F (00011111)

	/*
	 * The infomask and null bitmap as computed by heap_fill_tuple are useless
	 * to us.  However, that function will not accept a null infomask; and we
	 * need to pass a valid null bitmap so that it will correctly skip
	 * outputting null attributes in the data area.
	 */
	heap_fill_tuple(brtuple_disk_tupdesc(brdesc),
					values,
					nulls,
					(char *) rettuple + hoff,
					data_len,
					&phony_infomask,
					phony_nullbitmap);

	/* done with these */
	pfree(values);
	pfree(nulls);
	pfree(phony_nullbitmap);

#ifdef TOAST_INDEX_HACK
	for (i = 0; i < nuntoasted; i++)
		pfree(DatumGetPointer(untoasted_values[i]));
#endif

	/*
	 * Now fill in the real null bitmasks.  allnulls first.
	 */
	if (anynulls)
	{
		bits8	   *bitP;
		int			bitmask;

		rettuple->bt_info |= BRIN_NULLS_MASK;

		/*
		 * Note that we reverse the sense of null bits in this module: we
		 * store a 1 for a null attribute rather than a 0.  So we must reverse
		 * the sense of the att_isnull test in brin_deconstruct_tuple as well.
		 */
		bitP = ((bits8 *) ((char *) rettuple + SizeOfBrinTuple)) - 1;
		bitmask = HIGHBIT;
		for (keyno = 0; keyno < brdesc->bd_tupdesc->natts; keyno++)
		{
			if (bitmask != HIGHBIT)
				bitmask <<= 1;
			else
			{
				bitP += 1;
				*bitP = 0x0;
				bitmask = 1;
			}

			if (!tuple->bt_columns[keyno].bv_allnulls)
				continue;

			*bitP |= bitmask;
		}
		/* hasnulls bits follow */
		for (keyno = 0; keyno < brdesc->bd_tupdesc->natts; keyno++)
		{
			if (bitmask != HIGHBIT)
				bitmask <<= 1;
			else
			{
				bitP += 1;
				*bitP = 0x0;
				bitmask = 1;
			}

			if (!tuple->bt_columns[keyno].bv_hasnulls)
				continue;

			*bitP |= bitmask;
		}
		bitP = ((bits8 *) (rettuple + SizeOfBrinTuple)) - 1;
	}

	if (tuple->bt_placeholder)
		rettuple->bt_info |= BRIN_PLACEHOLDER_MASK;

	*size = len;
	return rettuple;
}
5 brin_doinsert 函数

  经过函数brin_form_tuple对内存形式BrinMemTuple加工生成磁盘形式Brin tuple,后进入真正的插入操作,由brin_doinsert函数实现。执行流程如下:
1 首先进行安全检查,判断待插入元组大小是否超过单个brin index元组最大阈值,如果是则写错误日志信息,返回InvalidOffsetNumber;
2 确保索引条目所在heap页域是否对应当前映射页,如果不对应,需进行扩展或者重用某映射页;
3 如果待插入常规页所在缓冲块有效,获取缓冲块排它锁;检查此常规页是否有足够空间容纳待插入索引,不能的话则释放缓冲块排它锁,将缓冲块号*buffer置为InvalidBuffer;
4 如果缓冲块号为InvalidBuffer,则循环调用brin_getinsertbuffer函数找到可用的brin buffer;
5 对步骤2中的revmap buffer施加缓冲块排它锁;
6 获取步骤4中brin buffer对应常规页地址page和常规页块号blk;
7 调用PageAddItem函数向page中插入索引元组,返回其偏移量off;
8 将上述blk与off信息封装成TID插入revmap映射页中,并为此操作生成对应的XLOG;
9 释放锁资源,如果发生brin index常规页扩展情况,需要更新对应的FSM信息。

在这里插入图片描述

OffsetNumber
brin_doinsert(Relation idxrel, BlockNumber pagesPerRange,
			  BrinRevmap *revmap, Buffer *buffer, BlockNumber heapBlk,
			  BrinTuple *tup, Size itemsz)
{
	Page		page;
	BlockNumber blk;
	OffsetNumber off;
	Size		freespace = 0;
	Buffer		revmapbuf;
	ItemPointerData tid;
	bool		extended;

	Assert(itemsz == MAXALIGN(itemsz));

	/* If the item is oversized, don't even bother. */
	if (itemsz > BrinMaxItemSize)
	{
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("index row size %zu exceeds maximum %zu for index \"%s\"",
						itemsz, BrinMaxItemSize, RelationGetRelationName(idxrel))));
		return InvalidOffsetNumber; /* keep compiler quiet */
	}

	/* Make sure the revmap is long enough to contain the entry we need */
	brinRevmapExtend(revmap, heapBlk);

	/*
	 * Acquire lock on buffer supplied by caller, if any.  If it doesn't have
	 * enough space, unpin it to obtain a new one below.
	 */
	if (BufferIsValid(*buffer))
	{
		/*
		 * It's possible that another backend (or ourselves!) extended the
		 * revmap over the page we held a pin on, so we cannot assume that
		 * it's still a regular page.
		 */
		LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
		if (br_page_get_freespace(BufferGetPage(*buffer)) < itemsz)
		{
			UnlockReleaseBuffer(*buffer);
			*buffer = InvalidBuffer;
		}
	}

	/*
	 * If we still don't have a usable buffer, have brin_getinsertbuffer
	 * obtain one for us.
	 */
	if (!BufferIsValid(*buffer))
	{
		do
			*buffer = brin_getinsertbuffer(idxrel, InvalidBuffer, itemsz, &extended);
		while (!BufferIsValid(*buffer));
	}
	else
		extended = false;

	/* Now obtain lock on revmap buffer */
	revmapbuf = brinLockRevmapPageForUpdate(revmap, heapBlk);

	page = BufferGetPage(*buffer);
	blk = BufferGetBlockNumber(*buffer);

	/* Execute the actual insertion */
	START_CRIT_SECTION();
	if (extended)
		brin_page_init(page, BRIN_PAGETYPE_REGULAR);
	off = PageAddItem(page, (Item) tup, itemsz, InvalidOffsetNumber,
					  false, false);
	if (off == InvalidOffsetNumber)
		elog(ERROR, "failed to add BRIN tuple to new page");
	MarkBufferDirty(*buffer);

	/* needed to update FSM below */
	if (extended)
		freespace = br_page_get_freespace(page);

	ItemPointerSet(&tid, blk, off);
	brinSetHeapBlockItemptr(revmapbuf, pagesPerRange, heapBlk, tid);
	MarkBufferDirty(revmapbuf);

	/* XLOG stuff */
	if (RelationNeedsWAL(idxrel))
	{
		xl_brin_insert xlrec;
		XLogRecPtr	recptr;
		uint8		info;

		info = XLOG_BRIN_INSERT | (extended ? XLOG_BRIN_INIT_PAGE : 0);
		xlrec.heapBlk = heapBlk;
		xlrec.pagesPerRange = pagesPerRange;
		xlrec.offnum = off;

		XLogBeginInsert();
		XLogRegisterData((char *) &xlrec, SizeOfBrinInsert);

		XLogRegisterBuffer(0, *buffer, REGBUF_STANDARD | (extended ? REGBUF_WILL_INIT : 0));
		XLogRegisterBufData(0, (char *) tup, itemsz);

		XLogRegisterBuffer(1, revmapbuf, 0);

		recptr = XLogInsert(RM_BRIN_ID, info);

		PageSetLSN(page, recptr);
		PageSetLSN(BufferGetPage(revmapbuf), recptr);
	}

	END_CRIT_SECTION();

	/* Tuple is firmly on buffer; we can release our locks */
	LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
	LockBuffer(revmapbuf, BUFFER_LOCK_UNLOCK);

	BRIN_elog((DEBUG2, "inserted tuple (%u,%u) for range starting at %u",
			   blk, off, heapBlk));

	if (extended)
	{
		RecordPageWithFreeSpace(idxrel, blk, freespace);
		FreeSpaceMapVacuumRange(idxrel, blk, blk + 1);
	}

	return off;
}

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

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

相关文章

【Day_29慢就是快】代码随想录-二叉树-二叉树的所有路径

给定一个二叉树&#xff0c;返回所有从根节点到叶子节点的路径。 思路 求根节点到叶子节点的路径&#xff0c;需要前序遍历&#xff0c;方便让父节点指向孩子节点&#xff0c;找到对应的路径。 使用递归方法做前序遍历&#xff0c;递归与回溯是一家的。 递归 1. 递归参数及返…

C++ Opencv视频检测

使用OpenCV进行视频检测的一般步骤如下&#xff1a;导入OpenCV库和视频文件。 对每一个视频帧进行对象检测。可以使用诸如Haar特征分类器、Cascade分类器或深度学习模型等技术进行对象检测。 #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> …

OpenLdap +PhpLdapAdmin + Grafana docker-compose部署安装

目录 一、OpenLdap介绍 二、PhpLdapAdmin介绍 三、使用docker-compose进行安装 1. docker-compose.yml 2. grafana配置文件 3. provisioning 四、安装openldap、phpldapadmin、grafana 五、配置OpenLDAP 1. 登陆PhpLdapAdmin web管理 2. 需要注意的细节 内容介绍参考…

Linux系统的安装

文章目录 1 Linux介绍1.1 Linux是什么1.2 Linux的特点1.3 Linux的应用1.4 Linux的发行版本1.5 Linux的Shell 2 Linux安装2.1 安装方式2.2 什么是VMware2.3 VMware主要功能2.4 什么是CentOS2.5 VMware与CentOS与Linux的关系2.6 VMware安装CentOS的步骤 1 Linux介绍 1.1 Linux是…

【前沿资讯】2023年最新遥感类SCIE/ESCI期刊影响因子汇总

6月28日&#xff0c;Clarivate发布了最新的JCR报告&#xff0c;公布了期刊的最新SCIE影响因子&#xff0c;并首次发布了ESCI期刊的影响因子。其中归入遥感“remote sensing”类的SCIE期刊有33本&#xff0c;归入ESCI期刊的有25本&#xff0c;以下分别为它们的相关指标。 表1 遥…

【LeetCode75】第四十六题 除法求值

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们多个二维数组形式的除法等式&#xff0c;在二维数组里有两个字符串&#xff0c;表示同名的未知数&#xff0c;另一个数组中对应…

SpringBoot 集成 Canal 实现监听MySQL表数据

SpringBoot 集成 Canal 准备工作什么是 CanalCanal 在 Spring Boot 中的作用和优势准备工作安装和配置 MySQL 数据库 安装Canal项目集成导入依赖添加配置信息创建监听类测试 准备工作 什么是 Canal Canal 是阿里巴巴开源的基于数据库增量日志解析的数据同步和订阅组件&#x…

写得了代码,焊得了板!嵌入式开发工程师必修之代码管理方案(下)

目录 极狐GitLab嵌入式开发场景解决方案 3.1 高可用部署与灾备 3.2 组织管理 3.3 分支策略 3.4 分支保护 3.5 推送规则 3.6 代码评审 3.7 数据保护 3.8 其他相关 本文来自 武让 极狐GitLab 高级解决方案架构师 &#x1f4a1; 前两篇文章&#xff0c;作者介绍了嵌入式开…

接口自动化测试系列-接入测试平台

测试平台目录 测试平台自建源码 后台核心代码 def add_api(kwags):"""插入api数据"""try:join_info CaseApi(namekwags.get("name"), httpTypekwags.get("httpType"),headerskwags.get("headers") if kwags.ge…

Rhinoceros(犀牛)使用技巧:有关曲线和曲面的分析

Rhinoceros&#xff08;犀牛&#xff09; for Mac破解版是一款功能强大的高级建模软件&#xff0c;可以创建、编辑、分析、提供、渲染、动画与转换 NURBS 线条、曲面、实体与多边形网格。不受精度、复杂、阶数或是尺寸的限制&#xff0c;在本篇文章中&#xff0c;为您介绍的是有…

AI与科学知识共生的桥梁,在未来AI会不会取代大学呢?

原创 | 文 BFT机器人 2023年&#xff0c;随着GPT在各行各业的爆发&#xff0c;“是否能将GPT用于科研场景”成为了一个水到渠成的问题。当ChatGPT超越大部分人类在高考、SAT、美国法考、医考等领域取得令人咋舌的高分后&#xff0c;人们对于GPT驱动科研的兴趣愈发高涨。截止本…

layui表格高度

layui表格的高度设置时使用 height:‘full’ 高度就是表格每个页面的总高度。也可以直接写数值&#xff0c;但是这是定高。 也可以使用 height&#xff1a;“full-数值”&#xff0c;比如 height:full-80 那么就会在表格占据剩余div的时候底部留100px。相当于margin-bottom:10…

【AI测试】python文字图像识别tesseract

[AI测试]python文字图像识别tesseract github官网&#xff1a;https://github.com/tesseract-ocr/tesseract python版本&#xff1a;https://github.com/madmaze/pytesseract OCR&#xff0c;即Optical Character Recognition&#xff0c;光学字符识别&#xff0c;是指通过扫…

骨传导耳机用久了伤耳朵吗?骨传导耳机有什么优势

骨传导耳机用久了不伤耳朵&#xff0c;相对于传统的入耳式耳机来说&#xff0c;对耳朵的压力和损伤较小。由于骨传导技术不直接通过耳道传递声音&#xff0c;而是通过振动将声音传送到内耳&#xff0c;因此相比其他类型的耳机&#xff0c;它在减少听力损伤的风险方面具有优势。…

3月面试华为被刷,准备半年,9月二战华为终于上岸,要个27K不过分吧?

终于二战上岸了&#xff0c;二战华为也并不是说非华为不可&#xff0c;只是觉得心里憋着一口气&#xff0c;这就导致我当时有其他比较好的offer&#xff0c;我也没有去&#xff0c;就是想上岸华为来证明自己,现在也算是如愿了&#xff0c;来跟大伙们分享一下~ 个人情况 我本人…

如何检查Windows 11笔记本电脑电池健康状况

如果你拥有一台运行微软最新操作系统的便携式电脑&#xff0c;那么检查Windows 11笔记本电脑的电池健康状况可能很重要。 电池寿命显然是一件大事&#xff0c;无论你是在最好的商务笔记本电脑上工作&#xff0c;还是在目前市场上最好的游戏笔记本电脑上享受马拉松式的Starfiel…

自然语言处理(七):来自Transformers的双向编码器表示(BERT)

来自Transformers的双向编码器表示&#xff08;BERT&#xff09; BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;是一种预训练的自然语言处理模型&#xff0c;由Google于2018年提出。它是基于Transformer模型架构的深度双向&#xff0…

DOM 简介 | 深入了解DOM

目录 一、DOM是什么 二、DOM的访问 三、DOM节点类型 四、DOM的分级 今天我们将了解WEB编程中一个重要的概念DOM&#xff08;Document Object Model&#xff09;文档对象模型&#xff0c;它帮助我们使用JavaScript&#xff08;或其他编程语言&#xff09;操纵文档。 一、DO…

java IDEA文件路径分层级

如下图这样 在设置里找到Compact Middle Packages&#xff0c;去掉勾选就行了

MEMS传感器的原理与构造——单片式硅陀螺仪

一、前言 机械转子式陀螺仪在很长的一段时间内都是唯一的选项&#xff0c;也正是因为它的结构和原理&#xff0c;使其不再适用于现代小型、单体、集成式传感器的设计。常规的机械转子式陀螺仪包括平衡环、支撑轴承、电机和转子等部件&#xff0c;这些部件需要精密加工和…