postgresql之List

本文详细介绍了PostgreSQL中的List结构,包括其内存布局、API功能、扩容策略和各种操作,如创建、遍历、插入、删除、拷贝、排序等。List采用数组实现,支持随即访问,预分配空间减少扩容次数,且头和body可以分离以减少缓存失效。此外,还讨论了如何进行浅拷贝和深拷贝,以及提供了丰富的集合操作,如并集、交集和差集。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、简介

postgresql中实现了一套功能强大的List库,List分为两部分,head+body。

  • body可以是任意对象,使用方便
  • 使用数组方式实现,存储紧凑、随即访问、访问方便
  • 使用0长数组,扩展方便
  • 预分配空间,减少扩容次数,提升写入速度
  • 可以将head和body分离,扩容后,可以减少缓存实效
  • 功能强大,交、并、差集,头插、尾插、任意位置插入,头删、尾删、任意位置删除 …

二、 List结构

src/include/nodes/pg_list.h

2.1 定义

typedef union ListCell
{
   
   
	void	   *ptr_value;
	int			int_value;
	Oid			oid_value;
} ListCell;

typedef struct List
{
   
   
	NodeTag		type;			/* T_List, T_IntList, or T_OidList */
	int			length;			/* number of elements currently present */
	int			max_length;		/* allocated length of elements[] */
	ListCell   *elements;		/* re-allocatable array of cells */
	/* We may allocate some cells along with the List header: */
	ListCell	initial_elements[FLEXIBLE_ARRAY_MEMBER];
	/* If elements == initial_elements, it's not a separate allocation */
} List;

NodeTag标识当前List的类型,同时也确定了List中存储的数据类型,是指针还是整数等。虽然List中可以存储任意的ListCell,但是ListCell中没有表示存储的类型,不方便读取,所以每个List都是存储的同一类数据。

2.2 结构图

在这里插入图片描述

三、 List API

src/include/nodes/pg_list.h

src/backend/nodes/list.c

3.1 创建list

创建一个新的list, 初始大小将进行2的幂次方对齐,这样将会产生多余的空间(即预分配空间)

static List *
new_list(NodeTag type, int min_size)
{
   
   
	List	   *newlist;
	int			max_size;

...
	max_size = pg_nextpower2_32(Max(8, min_size + LIST_HEADER_OVERHEAD));
	max_size -= LIST_HEADER_OVERHEAD;
...

	newlist = (List *) palloc(offsetof(List, initial_elements) +
							  max_size * sizeof(ListCell));
	newlist->type = type;
	newlist->length = min_size;
	newlist->max_length = max_size;
	newlist->elements = newlist->initial_elements;

	return newlist;
}

在这里插入图片描述

  • head 和 body是一体的
  • length表示当前已经使用的元素个数
  • max_length表示当前已经分配的元素个数
  • 一般max_length >= length, 这样预分配有多余的空间,后续使用可以减少扩容次数
  • 获取元素个数时间复杂度O(1)
  • 因为是数组实现,所以除了尾部插入元素,其他位置都需要移动数据,建议都尾插入和尾删

3.2 扩容list

static void
enlarge_list(List *list, int min_size)
{
   
   
	int			new_max_len;
...

	/*
	 * As above, we prefer power-of-two total allocations; but here we need
	 * not account for list header overhead.
	 */

	/* clamp the minimum value to 16, a semi-arbitrary small power of 2 */
	new_max_len = pg_nextpower2_32(Max(16, min_size));
...

	if (list->elements == list->initial_elements)
	{
   
   
		/*
		 * Replace original in-line allocation with a separate palloc block.
		 * Ensure it is in the same memory context as the List header.  (The
		 * previous List implementation did not offer any guarantees about
		 * keeping all list cells in the same context, but it seems reasonable
		 * to create such a guarantee now.)
		 */
		list->elements = (ListCell *)
			MemoryContextAlloc(GetMemoryChunkContext(list),
							   new_max_len * sizeof(ListCell));
		memcpy(list->elements, list->initial_elements,
			   list->length * sizeof(ListCell));

		...
	}
	else
	{
   
   
...
		/* Normally, let repalloc deal with enlargement */
		list->elements = (ListCell *) repalloc(list->elements,
											   new_max_len * sizeof(ListCell));
...
	}

	list->max_length = new_max_len;
}

当数组填满后,将进行扩容,但是扩容又和普通的List扩容不同 ,这里只扩容body, head保持不变,这样可以减少缓存失效。

  • 第一次扩容时,head 和body分离
  • 第二次扩容开始,只扩容body

在这里插入图片描述

注意

原始body还指向相同的对象,不要再访问, 原代码中做了处理,这里粘贴的代码删除了。

3.3 返回已有元素的个数

/* Fetch list's length */
static inline int
list_length(const List *l)
{
   
   
	return l ? l->length : 0;
}

3.4 返回元素

1. 返回首元素

/* Fetch address of list's first cell; NULL if empty list */
static inline ListCell *
list_head(const List *l)
{
   
   
	return l ? &l->elements[0] : NULL;
}

2.返回尾元素

/* Fetch address of list's last cell; NULL if empty list */
static inline ListCell *
list_tail(const List *l)
{
   
   
	return l ? &l->elements[l->length - 1] : NULL;
}

使用场景不同,list_tail不知道list是否为空,而list_last_cell使用时list必须非空。

/*
 * Return the last cell in a non-NIL List.
 */
static inline ListCell *
list_last_cell(const List *list)
{
   
   
	Assert(list != NIL);
	return &list->elements[list->length - 1];
}

3. 返回某个位置的元素

/*
 * Locate the n'th cell (counting from 0) of the list.
 * It is an assertion failure if there is no such cell.
 */
static inline ListCell *
list_nth_cell(const List *list, int n)
{
   
   
	Assert(list != NIL);
	Assert(n >= 0 && n < list->length);
	return &list->elements[n];
}

4. 返回当前元素的下一个元素

/*
 * Get the address of the next cell after "c" within list "l", or NULL if none.
 */
static inline ListCell *
lnext(const List *l, const ListCell *c)
{
   
   
	Assert(c >= &l->elements[0] && c < &l->elements[l->length]);
	c++;
	if (c < &l->elements[l->length])
		return (ListCell *) c;
	else
		return NULL;
}

3.5 返回元素值

1. 获取索引0-3及最后位置元素值

由函数list_nth_cell构成了一些列的宏函数, 获取索引0-3以及last的位置的元素的值。

#define lfirst(lc)				((lc)->ptr_value)
#define lfirst_int(lc)			((lc)->int_value)
#define lfirst_oid(lc)			((lc)->oid_value)
#define lfirst_node(type,lc)	castNode(type, lfirst(lc))

#define linitial(l)				lfirst(list_nth_cell(l, 0))
#define linitial_int(l)			lfirst_int(list_nth_cell(l, 0))
#define linitial_oid(l)			lfirst_oid(list_nth_cell(l, 0))
#define linitial_node(type,l)	castNode(type, linitial(l))

#define lsecond(l)				lfirst(list_nth_cell(l, 1))
#define lsecond_int(l)			lfirst_int(list_nth_cell(l, 1))
#define lsecond_oid(l)			lfirst_oid(list_nth_cell(l, 1))
#define lsecond_node(type,l)	castNode(type, lsecond(l))

#define lthird(l)				lfirst(list_nth_cell(l, 2))
#define lthird_int(l)			lfirst_int(list_nth_cell(l, 2))
#define lthird_oid(l)			lfirst_oid(list_nth_cell(l, 2))
#define lthird_node(type,l)		castNode(type, lthird(l))

#define lfourth(l)				lfirst(list_nth_cell(l, 3))
#define lfourth_int(l)			lfirst_int(list_nth_cell(l, 3))
#define lfourth_oid(l)			lfirst_oid(list_nth_cell(l, 3))
#define lfourth_node(type,l)	castNode(type, lfourth(l))

#define llast(l)				lfirst
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值