【C++】list类的模拟实现

🏖️作者:@malloc不出对象
⛺专栏:C++的学习之路
👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈
在这里插入图片描述

目录

    • 前言
    • 一、list类的模拟实现
      • 1.1 list的主体框架
      • 1.2 无参构造函数
      • 1.3 push_back
      • 1.4 正向迭代器
      • 1.5 反向迭代器
      • 1.6 insert
      • 1.7 erase
      • 1.8 clear
      • 1.9 析构函数
      • 1.10 构造函数
      • 1.11 赋值运算符重载
      • 1.12 empty
      • 1.13 front && back
      • 1.14 完整代码
    • 二、vector与list的对比


前言

本篇文章我们要来模拟实现的是list类,它的底层是用带头结点的双向循环链表实现的。

一、list类的模拟实现

1.1 list的主体框架

既然我们是用双向循环链表实现的,那么每个结点肯定都存储着next、prev与data信息,那么接下来我们就来定义一个类对它的结点进行初始化操作。

template<class T>  // 模板参数T
struct list_node
{list_node<T>* _next; 	//list_node<T>* 是类型list_node<T>* _prev;T _data;list_node(const T& val = T())  // 匿名对象初始化: _next(nullptr), _prev(nullptr), _data(val){}
};

我们把节点定义好之后,我们就来定义list类了,list类的成员变量只需要一个哨兵位的头结点就可以了。

template <class T>
class list
{typedef list_node<T> node;	
private:node* _head;	// 哨兵位头节点
};

1.2 无参构造函数

list()
{_head = new node;		// 申请一个节点_head->_next = _head;   // _head->_next指向自己_head->_prev = _head;   // _head->_prev也指向自己
}

1.3 push_back

双向链表的插入和删除都是非常好实现的,因为每个结点都有上一个节点和下一个节点的信息。这里我们要想实现尾插,我们要找到尾结点再改变它的指向就行了,非常的简单这里我就不做过多的赘述了。另外后续在我们实现insert和erase之后全都可以进行复用,这里只是先给大家打个样。

void push_back(const T& x)
{node* tail = _head->_prev;node* newnode = new node(x);tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;
}

1.4 正向迭代器

有了尾插之后我们可以往链表里面插入数据,下面我们想遍历一下链表,我们知道list不支持[]下标访问,原因是因为它是不连续的空间,所以我们必须使用迭代器对它进行遍历。

我们知道在实现vector类(SGI版本)时,我们的迭代器是作为一个原生指针来使用的,而在vector类(P.J.版本)中我们的迭代器是自定义类型对原生指针的封装,但本质上它们都是在模拟指针的行为!!!那么在list类中迭代器到底充当什么角色呢?我们知道迭代器支持++ - -操作这是为了找到后一个数据和前一个数据的位置,对于list而言它是双向链表它的空间是不连续的,假设迭代器是一个原生指针的话,指针++ - -一步取决于指针所指向的类型,对于不连续的空间来说++ - -能否刚好指向下一个位置或者上一个位置一切都是未知数,因此我们的迭代器在list中是对自定义类型原生指针的封装!!!

我们先来看看SGI版本下对正向迭代器的封装源码:

在这里插入图片描述

好了,也许我们有些地方可能有些不太懂,而且标准库的源码采用了非常多的命名替换,这是命名规范的问题,接下来我们模拟实现的时候不采用标准库这种方式,我们尽量的实现简洁易懂些。

最原始的代码

template<class T>
struct __list_iterator
{typedef list_node<T> node;typedef __list_iterator<T> self;node* _node;__list_iterator(node* x)  // 初始化结点: _node(x){}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}self& operator++(){_node = _node->_next;return *this;}self operator++(int){__list_iterator tmp = *this;_node = _node->_next;return tmp;}self& operator--(){_node = _node->_prev;return *this;}self operator--(int){__list_iterator tmp = *this;_node = _node->_prev;return tmp;}bool operator!=(const self& s){return _node != s._node;}bool operator==(const self& s){return _node == s._node;}
};

这是我们最原始的代码,那么大家知道为什么源码为什么会多出两个模板参数吗?这里我们实现的是正向迭代器,那么我们要实现const正向迭代器版本呢?难道要再去写一个__list_const_iterator类吗?

显然这样出现了大量的代码重复,我们是极其不支持这种实现方式的,所以我们必须想办法让他们之间可以进行复用,我们只需要改变一下返回值类型、参数类型就能实现iterator和const_iterator版本,这里模板参数的作用就体现出来了,我们可以添加一个模板参数,到时候我们可以实例化一份iterator和const_iterator。至于第三个模板参数是为了重载->运算符函数的,它同样的有T*版本和const T*版本。

为什么要重载->运算符?

struct AA
{int _a1;int _a2;AA(int a1, int a2): _a1(a1), _a2(a2){}
};void test()
{list<AA> lt;lt.push_back(AA(1, 1));lt.push_back(AA(2, 2));lt.push_back(AA(3, 3));list<AA>::iterator it = lt.begin();while (it != lt.end()){cout << (*it)._a1 << " " << (*it)._a2 << endl;  ++it;}cout << endl;
}

我们可以看到上诉代码对于一个自定义类型要想访问它的成员变量就必须得写成(*it)._a1、(*it)._a2,先*it得到AA对象,再访问它的成员变量,这种写法是不是未免有些麻烦了?我们平常可以直接使用->去访问它的成员变量,就像这段代码我们可以写成it->_a1、it->_a2,但是我们此时未重载->运算符,所以为了方便使用这里我们还需要重载一下->运算符。

对于__list_iterator类我们可以重载->写出下面的代码:

T* operator->()
{return &_node->_data;
}

但你有没有发现一些奇怪之处??

在这里插入图片描述

好了,关于为什么要重载->运算符这里我们已经讲清楚了,那么为什么这跟添加第三个模板参数有什么关系呢?原因很简单,一个T*版,一个const T*版,添加第三个模板参数Ptr也是为了复用T*版本。

所以最终我们的__list_iterator可以写成这种版本:

template<class T, class Ref, class Ptr>
struct __list_iterator
{typedef list_node<T> node;typedef __list_iterator<T, Ref, Ptr> self;node* _node;__list_iterator(node* x)  // 初始化结点: _node(x){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}self& operator++(){_node = _node->_next;return *this;}self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}self& operator--(){_node = _node->_prev;return *this;}self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}bool operator==(const self& s){return _node == s._node;}bool operator!=(const self& s){return _node != s._node;}};

我们在list类中就可以实例化iterator和const_iterator这两种版本的迭代器,list类中迭代器的定义如下:

typedef list_node<T> node;
public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;iterator begin(){return iterator(_head->_next);}const_iterator begin() const{return const_iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator end() const{return const_iterator(_head);}

我们来进行测试一下:

在这里插入图片描述

我们可以看到对应的过程,当list<int>显式声明模板类时,此时我们的类模板就根据类型实例化出一个具体的类。

在这里插入图片描述

1.5 反向迭代器

我们知道C++追求极致的性能,既然能复用绝不会写出两份差不多的代码,,所以我们实现反向迭代器并不会像正向迭代器那样倒着来,而是去复用正向迭代器!!!

反向迭代器其实也是一种适配器,它可以适配出各种容器的反向迭代器,其中最重要的就是将正向迭代器作为底层结构来封装反向迭代器,反向迭代器 ++ 就复用正向迭代器的 - -,反向迭代器 - - 就复用正向迭代器的 ++。

在这里插入图片描述

我们的反向迭代器既然是作为适配器去使用,那么我们就把它封装到单独的一个类中对它进行模拟实现,并且正向迭代作为它的模板参数进行复用它的功能!!

反向迭代器的模拟实现

// iterator.h
namespace curry
{template<class Iterator, class Ref, class Ptr>struct ReverseIterator{typedef ReverseIterator<Iterator, Ref, Ptr> Self;Iterator _cur;  // _cur就是一个正向迭代器ReverseIterator(Iterator it)  : _cur(it){}Ref operator*(){Iterator tmp = _cur;--tmp;return *tmp;}Self& operator++(){--_cur;return *this;}Self operator++(int){Self tmp = *this;--_cur;return tmp;}Self& operator--(){++_cur;return *this;}Self operator--(int){Self tmp = *this;++_cur;return tmp;}// 返回当前对象的地址Ptr operator->(){return &(operator*());}bool operator!=(const Self& s){return _cur != s._cur;}bool operator==(const Self& s){return _cur == s._cur;}};
}

只要知道了反向迭代器与正向迭代器的特性,我们就能够很容易的通过复用正向迭代器的成员函数来实现反向迭代器的成员函数!!同时反向迭代器其实解决了所有的双向迭代器的问题,因为只要将对应容器的正向迭代器作为反向迭代器的模板参数我们就能够对反向迭代器进行复用,所以我们之前的vector类的反向迭代器也能够直接使用它的正向迭代器复用实现!!这是一种非常巧妙的思想!!

1.6 insert

void insert(iterator pos, const T& x)
{node* cur = pos._node;   // 当前位置node* prev = cur->_prev; // 前一个位置node* newnode = new node(x);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;
}

实现了insert接口函数,那么我们的push_back与push_front都是可以复用的。

push_back(int x)

void push_back(const T& x)
{insert(end(), x);
}

push_front(int x)

void push_back(const T& x)
{insert(begin(), x);
}

list类与vector类的insert不同之处在于list类insert不会导致迭代器失效,因为它的空间的不连续的,并且没有挪动数据造成迭代器失效,所以我们也可以看到它的返回值为void,并不需要放回插入位置的迭代器。

1.7 erase

iterator erase(iterator pos)
{assert(pos != end());node* prev = pos._node->_prev;node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;return iterator(next);
}

迭代器失效即迭代器所指向的节点的无效,即该节点被删除了,所以对于list类的erase会导致指向删除节点的迭代器失效,其他迭代器不会受到影响而vector类进行erase会导致当前位置或者后续迭代器失效,所以正确的解决办法是给迭代器重新赋值!!

实现了erase函数接口,pop_back()以及pop_front()就可以进行复用了。

pop_back()

void pop_back()
{erase(--end());
}

pop_front()

void pop_front()
{erase(begin());
}

1.8 clear

void clear()
{iterator it = begin();while (it != end()){it = erase(it);	// erase返回下一个位置的迭代器}
}

clear释放链表中的结点,_head哨兵位头结点除外。

1.9 析构函数

~list()
{clear();delete _head;_head = nullptr;
}

析构函数的作用是释放所有结点,我们可以先调用clear依次释放链表中的结点,最后再释放头结点。

1.10 构造函数

传统写法

void empty_init()
{// 创建并初始化哨兵位头节点_head = new node;_head->_prev = _head;_head->_next = _head;
}// 拷贝构造传统写法 lt2(lt1)
list(const list<T>& lt)
{empty_init();for (auto& e : lt)	// 加引用避免自定义类型的拷贝构造{push_back(e);}
}

现代写法

template <class Iterator>  // 双向迭代器类型构造
list(Iterator first, Iterator last)
{empty_init();while (first != last){push_back(*first);++first;}
}void swap(list<T>& tmp)
{std::swap(_head, tmp._head);	// 交换哨兵位的头节点
}// 拷贝构造现代写法 lt2(lt1)
list(const list<T>& lt)
{empty_init();	list<T> tmp(lt.begin(), lt.end());	// 迭代器区间初始化swap(tmp);	
}

1.11 赋值运算符重载

传统写法

list<T>& operator=(const list<T>& lt)
{if (this != &lt)	// 防止自己给自己赋值{clear();	// 清理数据for (auto& e : lt){push_back(e);}}return *this;
}

现代写法

list<T>& operator=(list<T> lt)
{swap(lt);return *this;
}

一些常用的函数接口就讲到这里了,还有一些简单的函数接口读者下来也可以自己去尝试实现一下。

1.12 empty

bool empty()
{return _head->_next == _head &&_head->_prev == _head;
}

1.13 front && back

T& front()
{assert(!empty());return *begin();
}const T& front() const
{assert(!empty());return *begin();
}T& back()
{assert(!empty());return *(--end());
}const T& back() const
{assert(!empty());return *(--end());
}

1.14 完整代码

// list.h#include "iterator.h"namespace curry
{template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _data;list_node(const T& val = T()): _next(nullptr), _prev(nullptr), _data(val){}};template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> node;typedef __list_iterator<T, Ref, Ptr> self;node* _node;__list_iterator(node* x)  // 初始化结点: _node(x){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}self& operator++(){_node = _node->_next;return *this;}self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}self& operator--(){_node = _node->_prev;return *this;}self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}bool operator==(const self& s){return _node == s._node;}bool operator!=(const self& s){return _node != s._node;}};template<class T>class list{typedef list_node<T> node;public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;typedef ReverseIterator<iterator, T&, T*> reverse_iterator;typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;iterator begin(){return iterator(_head->_next);}reverse_iterator rbegin(){return reverse_iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_reverse_iterator rbegin() const{return const_reverse_iterator(_head);}iterator end(){return iterator(_head);}reverse_iterator rend(){return reverse_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}const_reverse_iterator rend() const{return const_reverse_iterator(_head->_next);}list(){empty_init();}// 现代写法list(const list<T>& lt){empty_init();list<T> tmp(lt.begin(), lt.end());swap(tmp);}template<class Iterator>list(Iterator first, Iterator last){empty_init();while (first != last){push_back(*first);++first;}}list<T>& operator=(list<T> lt){swap(lt);return *this;}// 释放所有结点~list(){clear();delete _head;_head = nullptr;}void swap(list<T>& tmp){std::swap(_head, tmp._head);}// 释放结点,但是_head头结点不处理void clear(){iterator it = begin();while (it != end()){it = erase(it);}}void empty_init(){_head = new node;_head->_next = _head;_head->_prev = _head;}void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void insert(iterator pos, const T& x){node* cur = pos._node;node* prev = cur->_prev;node* newnode = new node(x);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator erase(iterator pos){assert(pos != end());node* prev = pos._node->_prev;node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;return iterator(next);}T& front(){assert(!empty());return *begin();}const T& front() const{assert(!empty());return *begin();}T& back(){assert(!empty());return *(--end());}const T& back() const{assert(!empty());return *(--end());}bool empty(){return _head->_next == _head &&_head->_prev == _head;}private:node* _head;};
}

二、vector与list的对比

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

vectorlist
底层结构动态顺序表,一段连续空间带头结点的双向循环链表
随机访问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素效率O(N)
插入和删除任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生态指针对原生态指针(节点指针)进行封装
迭代器失效在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问

以上就是本文的所有内容了,如有错处或者疑问欢迎大家在评论区相互交流orz~🙈🙈

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

list(介绍与实现)

目录 1. list的介绍及使用 1.1 list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 list iterator的使用 1.2.3 list capacity 1.2.4 list element access 1.2.5 list modififiers 1.2.6 list的迭代器失效 2. list的模拟实现 2.1 模拟实现list 2.2 list的反向迭代器 1.…

Spring详解

文章目录 一、引言1.1 原生web开发中存在哪些问题&#xff1f; 二、Spring框架2.1 概念2.2 访问与下载 三、Spring架构组成四、自定义工厂4.1 配置文件4.2 工厂类 五、构建Maven项目5.1 新建项目5.2 选择Maven目录5.3 GAV坐标 六、Spring环境搭建6.1 pom.xml中引入Spring常用依…

数学建模-模型详解(2)

微分模型 当谈到微分模型时&#xff0c;通常指的是使用微分方程来描述某个系统的动态行为。微分方程是描述变量之间变化率的数学方程。微分模型可以用于解决各种实际问题&#xff0c;例如物理学、工程学、生物学等领域。 微分模型可以分为两类&#xff1a;常微分方程和偏微分…

倒数 2 周|期待 2023 Google开发者大会

9 月 6-7 日&#xff0c;中国上海 前沿科技&#xff0c;新知同享 趣味体验&#xff0c;灵感齐聚 技术生态&#xff0c;多元共进 关注官网最新信息&#xff0c;敬请期待大会开幕 2023 Google 开发者大会官网 相信你一定记得&#xff0c;在今年 5 月的 Google I/O 大会上&am…

Leetcode-每日一题【剑指 Offer 35. 复杂链表的复制】

题目 请实现 copyRandomList 函数&#xff0c;复制一个复杂链表。在复杂链表中&#xff0c;每个节点除了有一个 next 指针指向下一个节点&#xff0c;还有一个 random 指针指向链表中的任意节点或者 null。 示例 1&#xff1a; 输入&#xff1a;head [[7,null],[13,0],[11,4]…

分享2个u盘还原方法,轻松恢复u盘数据!

U盘&#xff0c;作为便捷的存储设备&#xff0c;经常用于传输和存储重要文件。然而&#xff0c;由于误操作、病毒感染或其他原因&#xff0c;U盘上的数据可能会丢失。在这种情况下&#xff0c;进行u盘还原成为救回丢失数据的关键一步。 本文将解释U盘还原的意义&#xff0c;并…

C# textBox1.Text=““与textBox1.Clear()的区别

一、区别 textbox.Text "" 和 textbox.Clear() 都可以用于清空文本框的内容&#xff0c;但它们之间有一些细微的区别。 textbox.Text "": 这种方式会将文本框的 Text 属性直接设置为空字符串。这样会立即清除文本框的内容&#xff0c;并将文本框显示为空…

openCV实战-系列教程2:阈值与平滑处理(图像阈值/图像平滑处理/高斯/中值滤波)、源码解读

OpenCV实战系列总目录 1、图像阈值 t图像阈值函数&#xff0c;就是需要判断一下像素值大于一个数应该怎么处理&#xff0c;小于一个数应该怎么处理 ret, dst cv2.threshold(src, thresh, maxval, type) 参数解析&#xff1a; src&#xff1a; 原始输入图&#xff0c;只…

MySQL—buffer pool

一、buffer pool的介绍 Buffer pool是什么 一个内存区域&#xff0c;为了提⾼数据库的性能&#xff0c;数据库操作数据的时候&#xff0c;把硬盘上的数据加载到buffer pool&#xff0c;不直接和硬盘打交道&#xff0c;操作的是 buffer pool的数据&#xff0c;数据库的增删改查…

Ubuntu【系统环境下】【编译安装OpenCV】【C++调用系统opencv库】

Ubuntu【系统环境下】【编译安装OpenCV】【C调用系统opencv库】 前言&#xff1a; 本人需要用C写代码&#xff0c;调用OpenCV库&#xff0c;且要求OpenCV版本号大于4.1.0 由于使用的是18.04的版本&#xff0c;所以apt安装OpenCV的版本始终是3.2.0&#xff0c;非常拉胯&#…

四层负载均衡的NAT模型与DR模型推导 | 京东物流技术团队

导读 本文首先讲述四层负载均衡技术的特点&#xff0c;然后通过提问的方式推导出四层负载均衡器的NAT模型和DR模型的工作原理。通过本文可以了解到四层负载均衡的技术特点、NAT模型和DR模型的工作原理、以及NAT模型和DR模型的优缺点。读者可以重点关注NAT模型到DR模型演进的原…

Python序列类型

序列&#xff08;Sequence&#xff09;是有顺序的数据列&#xff0c;Python 有三种基本序列类型&#xff1a;list, tuple 和 range 对象&#xff0c;序列&#xff08;Sequence&#xff09;是有顺序的数据列&#xff0c;二进制数据&#xff08;bytes&#xff09; 和 文本字符串&…

数据生成 | MATLAB实现GAN生成对抗网络结合SVM支持向量机的数据生成

数据生成 | MATLAB实现GAN生成对抗网络结合SVM支持向量机的数据生成 目录 数据生成 | MATLAB实现GAN生成对抗网络结合SVM支持向量机的数据生成生成效果基本描述程序设计参考资料 生成效果 基本描述 数据生成 | MATLAB实现GAN生成对抗网络结合SVM支持向量机的数据生成。 生成对抗…

UnitTest笔记: 拓展库DDT的使用

DDT (Data-Drivers- Tests) 允许使用不同的测试数据运行同一个测试用例&#xff0c;展示为不同的测试用例。 第一步&#xff1a; pip安装 ddt 第二步&#xff1a; 创建test_baidu_ddt.py 1. 测试类要使用ddt 修饰 2. 不同形式的参数化&#xff1a; 列表&#xff0c;字典&a…

Mesa 23.2 开源图形栈现已可供下载

作为 Mesa 23 系列的第二个重要版本&#xff0c;Mesa 23.2 开源图形栈现已可供下载&#xff0c;它为 AMD GPU 的 RADV Vulkan 驱动程序带来了新功能&#xff0c;改进了 Linux 游戏&#xff0c;并新增了 Asahi 功能。 Mesa 23.2 的亮点包括 Asahi 上的 OpenGL 3.1 和 OpenGL ES …

Windows用户如何安装Cpolar

目录 概述 什么是cpolar&#xff1f; cpolar可以用在哪些场景&#xff1f; 1. 注册cpolar帐号 1.1 访问官网站点 2. 下载Windows版本cpolar客户端 2.1 下载并安装 2.2 安装完验证 3. token认证 3.1 将token值保存到默认的配置文件中 3.2 创建一个随机url隧道&#x…

java八股文面试[JVM]——JVM调优

知识来源&#xff1a; 【2023年面试】JVM性能调优实战_哔哩哔哩_bilibili

java gradle 项目 在idea上 搭建一个简单的thrift实例

前言 Thrift是RPC通信的一种方式&#xff0c;可以通过跨语言进行通信&#xff0c;最近项目需要进行跨语言的通信&#xff0c;因此首先尝试搭建了一个简单的thrift框架&#xff0c;因为网上的实例大都参差不全&#xff0c;通过gpt查询得到的结果对我帮助更大一点&#xff0c;但…

爱奇艺数据湖实战 - 基于数据湖的日志平台架构演进

01 背景 为了满足公司内日志实时查询分析的需求&#xff0c;爱奇艺大数据团队自研了Venus日志服务平台&#xff0c;负责爱奇艺各服务日志的采集、存储、处理、分析等场景。早期采用基于ElasticSearch的存储分析架构&#xff0c;随着数据规模的不断扩大&#xff0c;出现了成本高…

大数据-玩转数据-Flink窗口函数

一、Flink窗口函数 前面指定了窗口的分配器, 接着我们需要来指定如何计算, 这事由window function来负责. 一旦窗口关闭, window function 去计算处理窗口中的每个元素. window function 可以是ReduceFunction,AggregateFunction,or ProcessWindowFunction中的任意一种. Reduc…