postgres 源码解析46 可见性映射表VM

news2025/7/15 10:37:54

简介

  Postgres 为实现多版本并发控制技术,当事务删除或者更新元组时,并非从物理上进行删除,而是将其进行逻辑删除[具体实现通过设置元组头信息xmax/infomask等标志位信息],随着业务的累增,表会越来越膨胀,对于执行计划的生成/最优路径的选择会产生干扰。为解决这一问题,可以通过调用VACUUM来清理这些无效元组。但是一个表可能有很多页组成,如何快速定位到含有无效元组的数据页在高并发场景显得尤为重要,幸运的是pg为表新增对应的附属文件—可见性映射表(VM),来加速判断heap块是否存在无效元祖。

VM 文件结构

在这里插入图片描述

  VM中为每个HEAP page设置两个比特位 (all-visible and all-frozen),分别对应于该页是否存在无效元祖、该页元组是否全部冻结。
all-visible 比特位的设置表明页内所有元组对于后续所有的事务都是可见的,因此该页无需进行 vacuum操作;
all-frozen 比特位的设置表明页内所有的元组已被冻结,在进行全表扫描vacuum请求时也无需进行vacuum操作。
NOTES: all-frozen 比特位的设置必须建立在该页已设置过 all-visible比特位。

简单介绍下标识位的写/更新逻辑:

在这里插入图片描述
其中比特位的含义如下:
all-visible 比特位: 0 ==> 含有无效元祖    1 ==> 元组均可见,不含无效元祖
all-frozen 比特位: 0 ==> 含有非冻结元祖   1 ==> 元组均冻结可见
方便讲述,取自页内的第一个字节示例:
字节对应的二进制信息: 00 00 00 10
根据上述内容可知,heap表的第一页至第三页含有无效元祖,第四页没有无效元祖
场景:对heap表进行vacuum操作,块1无效元祖被清除,需要设置 all-visible比特位,而块4所有元组冻结
在这里插入图片描述

读取数据是以字节为单位,因此通过 char *map数组读取出页内容首地址,通过偏移量确定all-visible 与 all-frozen比特位
1 Block-1对应的比特位为 00, 设置all-visible后更新为 10;
2 Block-4对应的比特位为 10, 设置all-frozen后更新为 11;

宏定义与数据结构

/* Number of bits for one heap page */
#define BITS_PER_HEAPBLOCK 2             // 每个heap块对应 2bits

/* Flags for bit map */
#define VISIBILITYMAP_ALL_VISIBLE	0x01	// all_visible
#define VISIBILITYMAP_ALL_FROZEN	0x02    // all_frozen 
#define VISIBILITYMAP_VALID_BITS	0x03	/* OR of all valid visibilitymap
											 * flags bits */
*
 * Size of the bitmap on each visibility map page, in bytes. There's no
 * extra headers, so the whole page minus the standard page header is
 * used for the bitmap.
 */
#define MAPSIZE (BLCKSZ - MAXALIGN(SizeOfPageHeaderData))    // map页大小

/* Number of heap blocks we can represent in one byte */
#define HEAPBLOCKS_PER_BYTE (BITS_PER_BYTE / BITS_PER_HEAPBLOCK)  // 1 字节对应 4个heap块

/* Number of heap blocks we can represent in one visibility map page. */
#define HEAPBLOCKS_PER_PAGE (MAPSIZE * HEAPBLOCKS_PER_BYTE)  // 一个map 对应的heap块数量

/* Mapping from heap block number to the right bit in the visibility map */
#define HEAPBLK_TO_MAPBLOCK(x) ((x) / HEAPBLOCKS_PER_PAGE)
#define HEAPBLK_TO_MAPBYTE(x) (((x) % HEAPBLOCKS_PER_PAGE) / HEAPBLOCKS_PER_BYTE)
#define HEAPBLK_TO_OFFSET(x) (((x) % HEAPBLOCKS_PER_BYTE) * BITS_PER_HEAPBLOCK)

/* Masks for counting subsets of bits in the visibility map. */
#define VISIBLE_MASK64	UINT64CONST(0x5555555555555555) /* The lower bit of each
														 * bit pair */
#define FROZEN_MASK64	UINT64CONST(0xaaaaaaaaaaaaaaaa) /* The upper bit of each
														 * bit pair */
// 读取没有 line pointers文件页的访问方法,尤其适合于VM文件页
/*
 1. PageGetContents
 2. 	To be used in cases where the page does not contain line pointers.
 3.  4. Note: prior to 8.3 this was not guaranteed to yield a MAXALIGN'd result.
 5. Now it is.  Beware of old code that might think the offset to the contents
 6. is just SizeOfPageHeaderData rather than MAXALIGN(SizeOfPageHeaderData).
 */
#define PageGetContents(page) \
	((char *) (page) + MAXALIGN(SizeOfPageHeaderData))

接口函数

1 visibilitymap_set
该函数的主要功能是设置可见性标识位,其执行流程如下:
1)首先进行安全性校验,判断传入的heap buf 和 vmbuf是否有效以及buf中缓存页是否一一对应;
2)获取VM页内容首地址(跳过PageHeaderData),获取vmbuf的 BUFFER_LOCK_EXCLUSIVE;
3)如果之前没有设置过相应的标识位,进行如下操作:
   (1) 进入临界区,在指定bit位设置信息,将vmbuf标记为脏;
   (2) 写WAL日志,如果开启wal_log_hints,需要将此日志号的LSN更新至heap 页后中;最后更新vmbuf缓存页的LSN,并退出临界。
4)释放vmbuf 持有的排他锁。

/*
 *	visibilitymap_set - set bit(s) on a previously pinned page
 *
 * recptr is the LSN of the XLOG record we're replaying, if we're in recovery,
 * or InvalidXLogRecPtr in normal running.  The page LSN is advanced to the
 * one provided; in normal running, we generate a new XLOG record and set the
 * page LSN to that value.  cutoff_xid is the largest xmin on the page being
 * marked all-visible; it is needed for Hot Standby, and can be
 * InvalidTransactionId if the page contains no tuples.  It can also be set
 * to InvalidTransactionId when a page that is already all-visible is being
 * marked all-frozen.
 *
 * 在recovery时 recptr为XLOG 记录的LSN,正常运行时为 InvalidXLogRecPtr。
 * cutoff_xid为进行标记操作的最大事务号;在备机上如果页内没有元组则为 InvalidTransactionId
 * 在页标记为 all-frozen时其 cutoff_xid 为 InvalidTransactionId
 * 
 * Caller is expected to set the heap page's PD_ALL_VISIBLE bit before calling
 * this function. Except in recovery, caller should also pass the heap
 * buffer. When checksums are enabled and we're not in recovery, we must add
 * the heap buffer to the WAL chain to protect it from being torn.
 *
 * You must pass a buffer containing the correct map page to this function.
 * Call visibilitymap_pin first to pin the right one. This function doesn't do
 * any I/O.
 */
void
visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf,
				  XLogRecPtr recptr, Buffer vmBuf, TransactionId cutoff_xid,
				  uint8 flags)
{
	BlockNumber mapBlock = HEAPBLK_TO_MAPBLOCK(heapBlk);
	uint32		mapByte = HEAPBLK_TO_MAPBYTE(heapBlk);
	uint8		mapOffset = HEAPBLK_TO_OFFSET(heapBlk);
	Page		page;
	uint8	   *map;

#ifdef TRACE_VISIBILITYMAP
	elog(DEBUG1, "vm_set %s %d", RelationGetRelationName(rel), heapBlk);
#endif

	Assert(InRecovery || XLogRecPtrIsInvalid(recptr));
	Assert(InRecovery || BufferIsValid(heapBuf));
	Assert(flags & VISIBILITYMAP_VALID_BITS);

	/* Check that we have the right heap page pinned, if present */
	if (BufferIsValid(heapBuf) && BufferGetBlockNumber(heapBuf) != heapBlk)
		elog(ERROR, "wrong heap buffer passed to visibilitymap_set");

	/* Check that we have the right VM page pinned */
	if (!BufferIsValid(vmBuf) || BufferGetBlockNumber(vmBuf) != mapBlock)
		elog(ERROR, "wrong VM buffer passed to visibilitymap_set");

	page = BufferGetPage(vmBuf);
	map = (uint8 *) PageGetContents(page);
	LockBuffer(vmBuf, BUFFER_LOCK_EXCLUSIVE);

	if (flags != (map[mapByte] >> mapOffset & VISIBILITYMAP_VALID_BITS))
	{
		START_CRIT_SECTION();

		map[mapByte] |= (flags << mapOffset);
		MarkBufferDirty(vmBuf);

		if (RelationNeedsWAL(rel))
		{
			if (XLogRecPtrIsInvalid(recptr))
			{
				Assert(!InRecovery);
				recptr = log_heap_visible(rel->rd_node, heapBuf, vmBuf,
										  cutoff_xid, flags);

				/*
				 * If data checksums are enabled (or wal_log_hints=on), we
				 * need to protect the heap page from being torn.
				 */
				if (XLogHintBitIsNeeded())
				{
					Page		heapPage = BufferGetPage(heapBuf);

					/* caller is expected to set PD_ALL_VISIBLE first */
					Assert(PageIsAllVisible(heapPage));
					PageSetLSN(heapPage, recptr);
				}
			}
			PageSetLSN(page, recptr);
		}

		END_CRIT_SECTION();
	}

	LockBuffer(vmBuf, BUFFER_LOCK_UNLOCK);
}

2 visibilitymap_get_status

  1. 首先判断vmbuf是否有效,如果有效,则进一步其缓存的页是否为heap块对应页,若对应关系不匹配,则释放vmbuf pin;
  2. 若无效,则调用 vm_readbuf 将vm页加载至缓冲块中并返回vmbuf,若返回vmbuf无效,则返回false后退出;
    3)紧接着读取vm页首地址,根据偏移量读取相应的标识位信息;
    这里只需要pin 机制,无需加 BUFFER_LOCK_SHARE
/*
 *	visibilitymap_get_status - get status of bits
 *
 * Are all tuples on heapBlk visible to all or are marked frozen, according
 * to the visibility map?
 *
 * On entry, *buf should be InvalidBuffer or a valid buffer returned by an
 * earlier call to visibilitymap_pin or visibilitymap_get_status on the same
 * relation. On return, *buf is a valid buffer with the map page containing
 * the bit for heapBlk, or InvalidBuffer. The caller is responsible for
 * releasing *buf after it's done testing and setting bits.
 *
 * NOTE: This function is typically called without a lock on the heap page,
 * so somebody else could change the bit just after we look at it.  In fact,
 * since we don't lock the visibility map page either, it's even possible that
 * someone else could have changed the bit just before we look at it, but yet
 * we might see the old value.  It is the caller's responsibility to deal with
 * all concurrency issues!
 */
uint8
visibilitymap_get_status(Relation rel, BlockNumber heapBlk, Buffer *buf)
{
	BlockNumber mapBlock = HEAPBLK_TO_MAPBLOCK(heapBlk);
	uint32		mapByte = HEAPBLK_TO_MAPBYTE(heapBlk);
	uint8		mapOffset = HEAPBLK_TO_OFFSET(heapBlk);
	char	   *map;
	uint8		result;

#ifdef TRACE_VISIBILITYMAP
	elog(DEBUG1, "vm_get_status %s %d", RelationGetRelationName(rel), heapBlk);
#endif

	/* Reuse the old pinned buffer if possible */
	if (BufferIsValid(*buf))
	{
		if (BufferGetBlockNumber(*buf) != mapBlock)
		{
			ReleaseBuffer(*buf);
			*buf = InvalidBuffer;
		}
	}

	if (!BufferIsValid(*buf))
	{
		*buf = vm_readbuf(rel, mapBlock, false);
		if (!BufferIsValid(*buf))
			return false;
	}

	map = PageGetContents(BufferGetPage(*buf));

	/*
	 * A single byte read is atomic.  There could be memory-ordering effects
	 * here, but for performance reasons we make it the caller's job to worry
	 * about that.
	 */
	 //单一字节的读取是原子的 
	result = ((map[mapByte] >> mapOffset) & VISIBILITYMAP_VALID_BITS);
	return result;
}

3 vm_readbuf

vm_readbuf 函数的功能是负责将指定VM页加载至缓冲区中,若有需要会进行extend生成新页并进行初始化。其执行流程图如下:
在这里插入图片描述

/*
 * Read a visibility map page.
 *
 * If the page doesn't exist, InvalidBuffer is returned, or if 'extend' is
 * true, the visibility map file is extended.
 */
static Buffer
vm_readbuf(Relation rel, BlockNumber blkno, bool extend)
{
	Buffer		buf;
	SMgrRelation reln;

	/*
	 * Caution: re-using this smgr pointer could fail if the relcache entry
	 * gets closed.  It's safe as long as we only do smgr-level operations
	 * between here and the last use of the pointer.
	 */
	reln = RelationGetSmgr(rel);

	/*
	 * If we haven't cached the size of the visibility map fork yet, check it
	 * first.
	 */
	 // 首先检查 是否cached 对应fork (vm)页
	if (reln->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM] == InvalidBlockNumber)
	{
		if (smgrexists(reln, VISIBILITYMAP_FORKNUM))    // 判断是否存在,存在即cached
			smgrnblocks(reln, VISIBILITYMAP_FORKNUM);
		else
			reln->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM] = 0;
	}

	/* Handle requests beyond EOF */
	// 申请的页号超出对应 fork现有最大页号,且指定扩展,则调用 vm_extend进行新建,反之返回InvalidBuffer 
	if (blkno >= reln->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM])
	{
		if (extend)
			vm_extend(rel, blkno + 1);
		else
			return InvalidBuffer;
	}

	/*
	 * Use ZERO_ON_ERROR mode, and initialize the page if necessary. It's
	 * always safe to clear bits, so it's better to clear corrupt pages than
	 * error out.
	 *
	 * The initialize-the-page part is trickier than it looks, because of the
	 * possibility of multiple backends doing this concurrently, and our
	 * desire to not uselessly take the buffer lock in the normal path where
	 * the page is OK.  We must take the lock to initialize the page, so
	 * recheck page newness after we have the lock, in case someone else
	 * already did it.  Also, because we initially check PageIsNew with no
	 * lock, it's possible to fall through and return the buffer while someone
	 * else is still initializing the page (i.e., we might see pd_upper as set
	 * but other page header fields are still zeroes).  This is harmless for
	 * callers that will take a buffer lock themselves, but some callers
	 * inspect the page without any lock at all.  The latter is OK only so
	 * long as it doesn't depend on the page header having correct contents.
	 * Current usage is safe because PageGetContents() does not require that.
	 */
	 // 常规流程 ==》 从共享缓冲池选择一个缓冲块缓存指定的VM页面,如果是新NEW页,获取
	 // BUFFER_LOCK_EXCLUSIVE,后再次检查页面是否为NEW[进行两次判断其是否为新页,
	 // 是因为有其他进程在本进程申请锁时已经完成了初始化]
	buf = ReadBufferExtended(rel, VISIBILITYMAP_FORKNUM, blkno,
							 RBM_ZERO_ON_ERROR, NULL);
	if (PageIsNew(BufferGetPage(buf)))
	{
		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
		if (PageIsNew(BufferGetPage(buf)))
			PageInit(BufferGetPage(buf), BLCKSZ, 0);
		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
	}
	return buf;
}

4 vm_extend

当访问的vm页在文件中不存在时,此时需调用vm_extend函数扩展新页并完成相应的初始化工作,其执行流程图如下:
在这里插入图片描述

  1. 首先页面初始化,填充PageHeader结构体pd_lower、pd_upper/和flag初始信息;
    2)获取relation的extension锁,防止其他进程进行同样的扩展工作;
    3)如果文件不存在,则调用 smgrcreate进行创建,反之进入第4)步;
    4)获取当前vm块号,如果当前块号小于指定快号,则需在此调用vm_extend进行扩展(递归调用);
    5)向其他进程发送无效消息强制其关闭对rel的引用,其目的是避免其他进程对此文件的create或者extension,因为这写操作容易发生。
    6)最后释放锁资源;
/*
 * Ensure that the visibility map fork is at least vm_nblocks long, extending
 * it if necessary with zeroed pages.
 */
static void
vm_extend(Relation rel, BlockNumber vm_nblocks)
{
	BlockNumber vm_nblocks_now;
	PGAlignedBlock pg;
	SMgrRelation reln;

	PageInit((Page) pg.data, BLCKSZ, 0);

	/*
	 * We use the relation extension lock to lock out other backends trying to
	 * extend the visibility map at the same time. It also locks out extension
	 * of the main fork, unnecessarily, but extending the visibility map
	 * happens seldom enough that it doesn't seem worthwhile to have a
	 * separate lock tag type for it.
	 *
	 * Note that another backend might have extended or created the relation
	 * by the time we get the lock.
	 */
	LockRelationForExtension(rel, ExclusiveLock);

	/*
	 * Caution: re-using this smgr pointer could fail if the relcache entry
	 * gets closed.  It's safe as long as we only do smgr-level operations
	 * between here and the last use of the pointer.
	 */
	reln = RelationGetSmgr(rel);

	/*
	 * Create the file first if it doesn't exist.  If smgr_vm_nblocks is
	 * positive then it must exist, no need for an smgrexists call.
	 */
	if ((reln->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM] == 0 ||
		 reln->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM] == InvalidBlockNumber) &&
		!smgrexists(reln, VISIBILITYMAP_FORKNUM))
		smgrcreate(reln, VISIBILITYMAP_FORKNUM, false);

	/* Invalidate cache so that smgrnblocks() asks the kernel. */
	reln->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM] = InvalidBlockNumber;
	vm_nblocks_now = smgrnblocks(reln, VISIBILITYMAP_FORKNUM);

	/* Now extend the file */
	while (vm_nblocks_now < vm_nblocks)
	{
		PageSetChecksumInplace((Page) pg.data, vm_nblocks_now);

		smgrextend(reln, VISIBILITYMAP_FORKNUM, vm_nblocks_now, pg.data, false);
		vm_nblocks_now++;
	}

	/*
	 * Send a shared-inval message to force other backends to close any smgr
	 * references they may have for this rel, which we are about to change.
	 * This is a useful optimization because it means that backends don't have
	 * to keep checking for creation or extension of the file, which happens
	 * infrequently.
	 */
	CacheInvalidateSmgr(reln->smgr_rnode);

	UnlockRelationForExtension(rel, ExclusiveLock);
}

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

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

相关文章

【QScrollBar | QSlider | QDial | QProgressBar | QLCDNumber】

【QScrollBar | QSlider | QDial | QProgressBar | QLCDNumber】【1】UI设计界面【QScrollBar | QSlider 函数学习】【2】setMinimum | setMaximum【3】setSingleStep【4】setPageStep【5】setValue【6】setSliderPosition【7】setTracking【8】setOrientation【9】setInverted…

C++ :类和对象:文件操作

前言&#xff1a; 程序运行时产生的数据都属于临时数据&#xff0c;程序一旦运行结束&#xff0c;数据都会被释放。通过文件可以 将数据持久化&#xff0c;C 中对文件操作需要包含头文件 <fstream>。 文件类型分为两种&#xff1a; 1&#xff1a;文本文件&#xff1a;文件…

历时9个月重构iNeuOS工业互联网操作系统,打造工业领域的“Office”

目 录 1. 概述... 1 2. 整体介绍... 2 3. 主要功能简介... 5 1. 概述 历时9个月的时间&#xff0c;对iNeuOS工业互联网操作系统进行全面重构&#xff0c;发布内部测试版本。重构的主要目的&#xff1a;工程化的框架优化&#xff0c;更好的聚焦工业领…

35.前端笔记-CSS3-3D转换

1、3D的特点 进大远小物体后面遮挡不可见 x:右为正 y:下为正 z:屏幕外是正&#xff0c;往里是负 3D移动之translate transform:translateX(100px);//仅仅是x轴移动。px或百分比 transform:translateY(100px);//仅仅是y轴移动&#xff0c;px或百分比 transform:translateZ(1…

33页企业内容管理与应用建设整体解决方案

当前企业在采购管理上面临的主要问题总体应对思路利用数字化技术&#xff0c;推动企业采购管理效能与职能升级 基于互联网技术架构推出数字化采购管理平台&#xff0c;帮助企业构建采购过程与供应商管理的两大流程闭环&#xff0c;实现采购过程的在线化协同&#xff0c;进而提升…

华为云大数据BI解决方案,助力企业实现数字化转型

2022年1月12日&#xff0c;国务院印发了《“十四五”数字经济发展规划》&#xff0c;规划明确提出到2025年&#xff0c;数字经济核心产业增加值占国内生产总值比重达到10%。这一规划的出台&#xff0c;充分释放出加快发展数字经济的明确信号&#xff0c;为各行业进行数字化转型…

使用FCN实现语义分割

来源&#xff1a;投稿 作者&#xff1a;王浩 编辑&#xff1a;学姐 这篇文章的核心内容是讲解如何使用FCN实现图像的语义分割。 在文章的开始&#xff0c;我们讲了一些FCN的结构和优缺点。然后&#xff0c;讲解了如何读取数据集。接下来&#xff0c;告诉大家如何实现训练。最后…

Redis跳跃表(SkipList)

什么是跳跃表 跳跃表&#xff08;skiplist&#xff09;是一种有序且随机化的数据结构&#xff0c;它通过在每个节点中维持多个指向其他节点的指针&#xff0c;从而达到快速访问节点的目的。 跳跃表的用处 有序集合(zset)的底层可以采用数组、链表、平衡树等结果来实现, 但是他…

仪表盘读数识别检测 Python+yolov5

仪表读数识别检测利用Pythonyolov5深度学习对仪表盘刻度数进行实时识别检测读取。Python是一种由Guido van Rossum开发的通用编程语言&#xff0c;它很快就变得非常流行&#xff0c;主要是因为它的简单性和代码可读性。它使程序员能够用更少的代码行表达思想&#xff0c;而不会…

艾美捷硫代巴比妥酸反应物质 (TBARS)检测试剂盒试剂准备

艾美捷TBARS&#xff08;TCA法&#xff09;测定试剂盒提供了一种简单、可重复和标准化的工具&#xff0c;用于测定血浆、血清、尿液、组织匀浆和细胞裂解物中的脂质过氧化。在高温&#xff08;90-100C&#xff09;和酸性条件下&#xff0c;通过MDA和TBA反应形成的MDA-TBA加合物…

kali渗透测试系列---信息收集

kali 渗透测试系列 文章目录kali 渗透测试系列信息收集信息收集 信息收集阶段可以说是在整个渗透测试或者攻击很重要的阶段&#xff0c;毕竟知己知彼才能百战百胜&#xff0c;否则从目标主机使用的平台到数据库的使用再到 web 应用开发的语言等等的种类多如繁星我们一个个的尝…

Opencv(C++)笔记--直方图均衡化、直方图计算

目录 1--直方图均衡化 2--直方图计算 1--直方图均衡化 ① 简述&#xff1a; 对图片的对比度进行调整&#xff0c;输入为灰度图像&#xff0c;对亮度进行归一化处理&#xff0c;提高灰度图的对比度&#xff1b; ② Opencv API&#xff1a; cv::equalizeHist(gray, dst); ③…

Unprojecting_text_with_ellipses过程分析

文章目录一、单应性1. 图片实例2. 数学表达式二、算法思路1. 算法流程2. 透视失真具体解决方案3. 图片旋转具体解决方案4. 图片文字倾斜具体解决方案三、实际处理过程四、算法问题五、OCR识别原文链接 https://mzucker.github.io/2016/10/11/unprojecting-text-with-ellipses.h…

ListView的基本创建方式

ListView的基本创建方式 1.ListView 主要介绍了采用标签创建以及ArrayAdapter适配器以及采用继承ListActivity的方式创建列表项 1.简介 是一个列表控件&#xff0c;以列表的形式展示具体内容&#xff0c;可以给各行设置事件监听器ListView中View负责显示和更新&#xff0c;数据…

最小生成树

文章目录基本原理Kruskal算法Prim算法基本原理 连通图中的每一棵生成树&#xff0c;都是原图的一个极大无环子图&#xff0c;即&#xff1a;从其中删去任何一条边&#xff0c;生成树就不在连通&#xff1b;反之&#xff0c;在其中引入任何一条新边&#xff0c;都会形成一条回路…

二叉树9:二叉树的最大深度

主要是我自己刷题的一些记录过程。如果有错可以指出哦&#xff0c;大家一起进步。 转载代码随想录 原文链接&#xff1a; 代码随想录 leetcode链接&#xff1a; 104. 二叉树的最大深度 559.n叉树的最大深度 104.二叉树的最大深度 题目&#xff1a; 给定一个二叉树&#xff0…

校招面试真题 | 你的期望薪资是多少?为什么

很多人去面试的时候&#xff0c;就像打游戏&#xff0c;过五关斩六将&#xff0c;终于到最后一关了&#xff0c;但是谈薪资的难度堪比打游戏中搞定终级 boss 的难度&#xff0c;真的是太「南」了&#xff0c;好多人都是因为这个问题让自己五味杂陈呀。报高了怕好 offer 失之交臂…

Ubuntu: Docker安装与操作

在进行docker安装前&#xff0c;我们首先得有以下工具&#xff1a;xshell,FileZilla Client Xshell下载安装教程 FileZilla Client下载安装教程 如果你的Ubuntu是纯净的(也就是说刚下好并且刚用虚拟机装好的)&#xff0c;你得先 打开终端&#xff08;CtrlAltT&#xff09; 一…

远程连接服务器(运用密钥)连接winscp/vscode/mobaxterm

1.连接ssh 先检查自己是否登上校园VPN校园VPN导航页 (xjtu.edu.cn) sslvpn 进入cmd&#xff08;黑框框&#xff09; 输入&#xff1a; &#xff08;1&#xff09; ssh &#xff08;用户名&#xff09;&#xff08;IP名&#xff09; -p &#xff08;端口如22、2022&#x…

基于SSM框架的旅游网站的设计与实现

1 简介 今天向大家介绍一个帮助往届学生完成的毕业设计项目&#xff0c;*基于SSM框架的旅游网站的设计与实现 *。 计算机毕业生设计,课程设计需要帮助的可以找我 2 设计概要 1.1.研究背景 随着互联网技术的飞速发展&#xff0c;网络与我们的生活息息相关&#xff0c;在我们日…