当前位置: 首页 > news >正文

双向链表及如何使用GLib的GList实现双向链表

双向链表是一种比单向链表更为灵活的数据结构,与单向链表相比可以有更多的应用场景,本文讨论双向链表的基本概念及实现方法,并着重介绍使用GLib的GList实现单向链表的方法及步骤,本文给出了多个实际范例源代码,旨在帮助学习基于GLib编程的读者较快地掌握GList的使用方法,本文程序在 ubuntu 20.04 下编译测试完成,gcc 版本号 9.4.0;本文适合初学者阅读。

1 双向链表及其实现

  • 在文章《单向链表以及如何使用GLib中的GSList实现单向链表》中,介绍了单向链表以及基于 GLib 实现单向链表的方法,建议阅读本文前先阅读这篇文章;

  • 在文章《使用GLib进行C语言编程的实例》中,简单介绍了 GLib,建议阅读本文前先阅读这篇文章;

  • 双向链表(Doubly Linked List)是一种链式数据结构,每个节点包含三个主要部分:

    1. 数据部分:存储节点的数据
    2. 前向指针:指向链表中的下一个节点
    3. 后向指针:指向链表中的上一个节点
  • 可以看出,和单向链表相比较,双向链表多了一个指向前一个节点的指针

  • 双向链表的基本特性

    1. 双向性:与单向链表不同,双向链表允许从两个方向遍历,可以从头节点向尾节点遍历,也可以从尾节点向头节点遍历;
    2. 动态大小:双向链表的大小可以动态增长或缩小,不需要提前定义大小;
    3. 节点插入和删除:在双向链表中,插入和删除节点操作相对简单,因为每个节点都有指向前后节点的指针;
  • 双向链表的节点结构:

    struct Node {int data;               // 数据部分struct Node *next;      // 指向下一个节点的指针struct Node *prev;      // 指向前一个节点的指针
    };
    
  • 双向链表的基本操作

    1. 插入节点:可以在链表的开头、结尾或任意位置插入节点;
    2. 删除节点:可以删除链表中的任意节点,操作相对简单,因每个节点都知道其前一个和后一个节点;
    3. 遍历链表:可以从头到尾遍历链表(正向遍历)或从尾到头遍历链表(反向遍历);
  • 与单向链表相比,双向链表有以下特点:

    1. 由于数据结构中增加了后向指针,使链表可以双向遍历,而单向链表仅能单向遍历;
    2. 通过后向指针可以直接访问前一个节点,与单向链表相比,可以简化节点删除操作的复杂度;
    3. 在插入节点时,比单向链表更快捷更灵活;
    4. 与单向链表相比,由于增加了后向指针,内存开销增加;
    5. 与单向链表相比,双向链表需要操作两个指针,其操作和维护的复杂度要高一些;
  • 总的来说,‌双向链表比单向链表更加灵活,‌适用场景也要多一些。;

  • 下面程序是一个简单的双向链表的 C 语言标准库实现,dllist-c.c(点击文件名下载源程序)

  • 编译:gcc -Wall -g dllist-c.c -o dllist-c

  • 运行:./dllist-c

  • 该程序实现了双向链表的插入、删除以及正向遍历;

  • 该程序首先建立一个双向链表,并在链表中加入 4 个节点,数据分别为:1、2、3、5,然后显示整个链表;

  • 在第 3 个节点(数据为 3,索引号为 2)的后面插入节点,数据为 4,然后显示整个链表;

  • 将第 3 个节点(数据为 3,索引号为 2)删除,然后显示整个链表;

  • 最后释放整个链表;

  • 运行截图:

    screenshot of dllist-c

2 GLib 中双向链表结构 GList

  • GLib API version 2.0 手册 (点击查看手册)
  • GLib API 手册中 GList 部分 (点击查看手册)
  • 在 GLib 中,‌双向链表是通过 GList 结构体实现的,GList 是一个简单的双向链表结构,‌用于存储各种类型的数据;
  • GSList 定义如下:
    struct GList {gpointer data;GList *next;GList *prev;
    }
    
  • data 为双向链表的数据指针,可以指向任何类型或结构的数据;
  • next 为指向该双向链表当前节点的下一个节点的指针;
  • prev 为指向该双向链表当前节点的前一个节点的指针;
  • GLib 为双向链表结构 GList 的操作提供了大量的函数,本文仅就其中的一部分函数进行简单介绍;
  1. 添加、插入新节点

    • g_list_append() 在双向链表的最后添加一个新节点;
      GList *g_slist_append(GList *list, gpointer data)
      
      • list - 指向双向链表的指针
      • data - 指向添加节点的数据
      • 返回指向双向链表的起始指针;
      • 说明:在双向链表的最后添加节点,必须要遍历整个链表才能找到链表的尾部,这种做法效率很低,通常的做法是使用 g_list_prepend() 在链表的起始位置添加节点,当所有节点添加完毕后,再使用 g_list_reverse() 将整个链表反转;
    • g_list_prepend() 在双向链表的最前面添加一个新节点;
      GList *g_list_prepend(GList *list, gpointer data)
      
      • list - 指向双向链表的指针
      • data - 指向添加节点的数据
      • 返回指向双向链表的指针,在双向链表的开头添加一个节点,双向链表的指针是肯定会变化的;
    • g_list_insert() 在双向链表的中间插入一个新节点;
      GList *g_list_insert(GList *list, gpointer data, gint position)
      
      • list - 指向双向链表的指针
      • data - 指向添加节点的数据
      • position - 插入节点的位置,如果是负数或者超过了该双向链表的节点的数量,新节点将插到双向链表的最后;
      • 返回该双向链表的起始指针;
    • g_list_insert_before() 在包含指定数据的节点之前插入一个新节点;
      GList *g_list_insert_before(GList *list, GSList *sibling, gpointer data)
      
      • list - 指向双向链表的指针
      • data - 指向添加节点的数据
      • sibling - 指向一个节点的指针,将在这个节点前插入新节点
      • 返回该双向链表的起始指针;
  2. 删除节点

    • g_list_remove_link() 从双向链表中删除一个节点,但并不释放该节点占用的内存
      GList *g_list_remove_link(GList *list, GList *llink_)
      
      • list - 指向双向链表的指针;
      • llink_ - 指向双向链表中一个节点的指针,该节点将被删除;
      • 返回该双向链表的起始指针;
      • 该函数并不释放被删除的节点内存,被删除的节点的 next 和 prev 指针将指向 NULL,所以可以认为被删除的节点变成了一个只有一个节点的新的双向链表;
    • g_list_delete_link() 从双向链表中删除一个节点,并释放该节点占用的内存;
      GList *g_list_delete_link(GList *list, GList *link_)
      
      • list - 指向双向链表的指针;
      • link_ - 指向双向链表中一个节点的指针,该节点将被删除;
      • 返回该双向链表的起始指针;
      • 该函数与 g_list_remove_link() 的唯一区别是该函数在删除节点后释放了被删除节点占用的内存;
    • g_list_remove() 从双向链表中删除指定数据的一个节点,如果链表中有指定数据的节点有多个,将只删除第一个;
      GList *g_list_remove(GList *list, gconstpointer data)
      
      • list - 指向双向链表的指针
      • data - 指向要删除节点的数据
      • 返回该双向链表的起始指针;
    • g_list_remove_all() 从双向链表中删除指定数据的所有节点;
      GList *g_list_remove_all(GList *list, gconstpointer data)
      
      • list - 指向双向链表的指针
      • data - 指向要删除节点的数据
      • 返回该双向链表的起始指针;
  3. 遍历链表

    • g_list_foreach() 遍历双向链表,每个节点都会调用一个指定函数;
      void g_list_foreach(GList *list, GFunc func, gpointer user_data)
      
      • list - 指向双向链表的指针
      • func - 一个指向函数的指针,遍历到双向链表的每个节点时,都会调用这个函数;
      • GFunc 的定义如下:
      void (* GFunc) (gpointer data, gpointer user_data)
      
      • GFunc 的定义表明,传递给 func 的参数有两个,一个是 data - 指向当前节点的节点数据指针,另一个就是指向自定义参数 user_data 的指针
      • user_data - 指针指向调用 func 时传递的用户参数;
  4. 查找节点

    • g_list_find() 查找链表中包含给定数据的节点;
      GList *g_list_find(GList *list, gconstpointer data)
      
      • list - 指向双向链表的指针
      • data - 指向要查找节点的数据
      • 返回在双向链表中找到的节点的指针,如果没有找到相应节点,返回 NULL;
    • g_list_index() 获取包含给定数据的节点的位置(从 0 开始);
      gint g_list_index(GList *list, gconstpointer data)
      
      • list - 指向双向链表的指针;
      • data - 指向要查找节点的数据;
      • 返回数据为 data 的节点在双向链表中的位置(从 0 开始),如果没找到相应节点,则返回 -1;
    • g_list_position() 获取给定节点在链表中的位置(从 0 开始);
      gint g_list_position(GList *list, GList *llink)
      
      • list - 指向双向链表的指针;
      • llink - 指向双向链表中的一个节点的指针;
      • 返回 llink 指向的节点在双向链表中的位置(从 0 开始),如果没找到相应节点,则返回 -1;
  5. 释放链表

    • g_list_free() 释放链表使用的所有内存,该函数不会释放节点中动态分配的内存;
      void g_list_free(GList *list)
      
      • list - 指向双向链表的指针;
      • 该函数仅释放 GList 占用的内存,并不释放双向链表中各个节点动态申请的内存,如果链表中有动态申请内存,考虑使用 g_list_free_full() 或手动释放内存;
    • g_list_free_full() 释放链表使用的所有内存,并对每个节点的数据调用指定的销毁函数
      void g_list_free_full(GList *list, GDestroyNotify free_func)
      
      • list - 指向双向链表的指针;
      • free_func - 销毁函数,对双向链表中的每个节点数据将调用该函数,可用于释放节点中动态分配的内存;
      • GDestroyNotify 的定义如下:
      void (* GDestroyNotify) (gpointer data)
      
      • 所以在调用 free_func 时会将指向节点数据的指针传递给该函数;
  6. 其它

    • g_list_length() 获取双向链表的长度;
      guint g_list_length(GList *list)
      
      • list - 指向双向链表的指针;
      • 返回双向链表中节点的数量。
    • g_list_last() 获取双向链表的最后一个节点;
      GList *g_list_last(GList *list)
      
      • list - 指向单向链表的指针;
      • 返回双向链表的最后一个节点的指针,如果双向链表没有节点,则返回 NULL;
    • g_list_concat() 连接两个双向链表;
      GList *g_list_concat(GList *list1, GList *list2)
      
      • list1 - 指向第 1 个双向链表的指针;
      • list2 - 指向准备连接到第 1 个双向链表后面的双向链表的指针;
      • 返回连接好的双向链表的指针,
    • g_list_reverse() 反转整个双向链表
      GList *g_list_reverse(GList *list)
      
      • list - 指向双向链表的指针;
      • 返回该双向链表的起始指针;

3 如何使用 GList 实现双向链表

  • 文章的一开始有一个使用标准 C 语言函数库的双向链表的实例,使用 GLib 的 GList 操作双向链表要容易得多;

  • 下面程序是使用 C 语言,基于 GLib 实现的双向链表,dllist-glib.c(点击文件名下载源程序)

  • 该程序实现的功能与文章开头的程序 dllist-c.c 完全一样,但程序看上去要简洁很多,我们不妨把源程序列在这里

  • 该程序与文章《单向链表以及如何使用GLib中的GSList实现单向链表》中使用 GLib 实现单向链表的程序非常相似

    #include <stdio.h>
    #include <glib.h>void print_node(gpointer data, gpointer user_data) {printf("%d -> ", GPOINTER_TO_INT(data));
    }
    void print_list(GList *list) {g_list_foreach(list, &print_node, NULL);printf("NULL\n");
    }int main() {GList *list = NULL;printf("Append 4 nodes, the data are 1, 2, 3, 5.\n");list = g_list_append(list, GINT_TO_POINTER(1));list = g_list_append(list, GINT_TO_POINTER(2));list = g_list_append(list, GINT_TO_POINTER(3));list = g_list_append(list, GINT_TO_POINTER(5));print_list(list);printf("Insert a new node after node with the data 3.\n");list = g_list_insert(list, GINT_TO_POINTER(4), 3);print_list(list);printf("Remove node with the data 3.\n");list = g_list_remove(list, GINT_TO_POINTER(3));print_list(list);// Free the listg_list_free(list);return 0;
    }
    
  • 该程序中涉及到的两个宏:GINT_TO_POINTER(value)GPOINTER_TO_INT(p),在文章《单向链表以及如何使用GLib中的GSList实现单向链表》中有比较详细的介绍;

  • 编译:

    gcc -Wall -g dllist-glib.c -o dllist-glib `pkg-config --cflags --libs glib-2.0`
    
  • 其中,pkg-config --cflags --libs glib-2.0 的含义在文章《使用GLib进行C语言编程的实例》中做过介绍;

  • 运行:./dllist-glib

  • 该程序实现了双向链表的插入、删除、遍历;

  • print_list() 中使用 g_list_foreach() 对链表进行遍历,对链表中的每个节点数据,将调用函数 print_node()

  • 运行截图:

    screenshot of dllist-glib

4 双向链表的应用场景

  • 双向链表是一种数据结构,它的每个节点包含对前一个节点和后一个节点的引用;这种结构在许多应用场景中非常有用,以下是一些常见的应用场景:
  1. 浏览器历史记录:

    双向链表可以用来实现浏览器的“后退”和“前进”按钮,用户可以在历史记录中前后移动当前指针;

  2. 音乐播放器:

    在音乐播放器中,双向链表可以用于管理播放列表,允许用户在歌曲之间前后切换;

  3. 文本编辑器:

    在实现撤销和重做功能时,双向链表可用于存储编辑历史,方便在不同操作间切换;

  4. LRU缓存:

    在实现最近最少使用(LRU)缓存时,双向链表可以高效地维护访问顺序,以便快速找到和删除最少使用的项;

  5. 操作系统中的进程调度:

    在某些调度算法中,双向链表可用于管理就绪队列,使得进程可以方便地添加和移除;

  6. 图形界面中的组件布局:

    在某些图形用户界面(GUI)框架中,双向链表用于管理组件的顺序和关系,使得组件之间的插入和删除变得灵活;

  7. 实现栈和队列:

    双向链表可以作为基础结构来实现栈和队列,提供灵活的插入和删除操作。

5 基于 GLib 的 GList 模拟终端命令的历史记录

  • 当我们在 Linux 终端上输入命令时,终端应用程序会记录你输入的命令并形成历史记录,可以使用 history 命令来查看这个历史记录;

  • 在终端上也可以使用上、下箭头键来翻看曾经输入过的前一个或者后一个历史命令,这个命令历史记录给使用终端带来了一定的便利;

  • 本实例模拟了终端输入命令并使用双向链表生成命令的历史记录,按上下箭头键可以查看上一条或下一条命令;

  • 源程序 cmd-history.c(点击文件名下载源程序) 基于 GLib 的 GList 模拟了终端历史记录;

  • 该程序首先建立了一个双向链表队列,然后模拟输入命令,链表中的每个节点存储一条命令,命令输入完成后显示最后一条命令,然后按上下箭头键可以从链表中取出上一条命令或者下一条命令并显示在屏幕上;

  • 很显然,使用单向链表实现命令历史记录是不方便的,但使用双向链表就很方便;

  • 编译:

    gcc -Wall -g cmd-history.c -o cmd-history `pkg-config --cflags --libs glib-2.0`
    
  • 其中,pkg-config --cflags --libs glib-2.0 的含义在文章《使用GLib进行C语言编程的实例》中做过介绍;

  • 运行:./cmd-history

  • 运行截图:

    screenshot of cmd-history在这里插入图片描述

  • 该程序涉及到终端的操作,使用了结构 struct termios、函数 tcgetattr()tcsetattr(),这些并不在 C 标准库 libc 中,需要启用 GNU 扩展库,所以在程序的开始有 #define _GNU_SOURCE

  • 有关终端操作的相关数据结构、宏定义以及相关函数,并不在本文的讨论之内,请自行参考其它资料;

  • 该程序中还涉及到了使用 ESC 转义符对终端屏幕进行清屏操作,有关 ESC 转义符的含义,请参考另一篇文章《ANSI的ESC转义序列》

  • 该程序中还涉及到了从键盘缓冲区读取上、下箭头键的方法,上箭头键返回的编码为 ESC [ A,下箭头键返回的编码为 ESC [ B,这里说明一下有助于读者更快地读懂程序。


email: hengch@163.com

相关文章:

双向链表及如何使用GLib的GList实现双向链表

双向链表是一种比单向链表更为灵活的数据结构&#xff0c;与单向链表相比可以有更多的应用场景&#xff0c;本文讨论双向链表的基本概念及实现方法&#xff0c;并着重介绍使用GLib的GList实现单向链表的方法及步骤&#xff0c;本文给出了多个实际范例源代码&#xff0c;旨在帮助…...

ProCalun卡伦纯天然万用膏,全家的皮肤健康守护

受季节交替、生活环境变化、空气污染等方面因素的影响&#xff0c;加上作息不规律导致的免疫力降低&#xff0c;我们或多或少会出现一些如湿疹、痤疮、瘙痒之类的皮肤问题&#xff0c;且反复概率很高。很多人盲目用药&#xff0c;甚至诱发激素依赖性皮炎。所以近年来&#xff0…...

FastAPI全方位分析:优劣尽显

近年来,随着技术的飞速发展,快速构建高性能API的需求越来越强烈。Python作为一个广泛使用的编程语言,也在这一领域下涌现出了许多优秀的框架。FastAPI便是其中一颗璀璨的新星。 FastAPI以其卓越的性能和独特的功能吸引了众多开发者。本文将深入剖析FastAPI的各个方面,详细…...

【rust】rust基础代码案例

文章目录 代码篇HelloWorld斐波那契数列计算表达式&#xff08;加减乘除&#xff09;web接口 优化篇target/目录占用一个g&#xff0c;仅仅一个actix的helloWorld demo升级rust版本&#xff0c; 通过rustupcargo换源windows下放弃吧&#xff0c;需要额外安装1g的toolchain并且要…...

【深度学习】PromptFix:多功能AI修图

PromptFix:你来提示,我们修图 NeurIPS 2024 最近,在计算机视觉和图像处理领域,一个名为PromptFix的新项目引起了广泛关注。PromptFix是一个基于PyTorch实现的开源项目,旨在根据用户的自然语言指令,对受损或需要处理的图像进行智能修复和优化。 本文将详细介绍PromptFix…...

2024最新AI绘画系统软件(Midjourney)+GPT4文档分析总结,多模态识图理解,AI文生图/图生图/混图生图(图像混合)

一、前言 人工智能的快速发展已成为全球关注的焦点&#xff0c;其应用领域广泛&#xff0c;涵盖绘图、语言处理、视频编辑等。前沿技术不仅推动科技创新&#xff0c;还在艺术创作、内容生产和商业实践等方面展示出巨大潜力。例如&#xff0c;AI语言模型显著提升了内容自动生成、…...

【信号处理】基于联合图像表示的深度学习卷积神经网络

Combined Signal Representations for Modulation Classification Using Deep Learning: Ambiguity Function, Constellation Diagram, and Eye Diagram 信号表示 Ambiguity Function(AF) 模糊函数描述了信号的两个维度(dimensions):延迟(delay)和多普勒(Doppler)。 …...

C#基础-区分数组与集合

目录 区分数组与集合 1.定义 1&#xff09;数组 2&#xff09;集合 2.大小 1&#xff09;数组 2&#xff09;集合 3.访问速度 1&#xff09;数组 2&#xff09;集合 4.内存管理 1&#xff09;数组 2&#xff09;集合 5.使用场景 1&#xff09;数组 2&#xff0…...

ORACLE 19C 安装数据库补丁的详细过程

ORACLE 19c安装DB补丁&#xff1a; 1 确定OPatch的可用性&#xff1a;这里需要注意的是p6880880_190000_Linux-x86-64.zip是有版本对应区别的&#xff0c;需要注意你要打的补丁版本是否支持。 2 将原$ORACLE_HOME目录下的OPatch目录删除或者改名&#xff0c;比如说&#xff1a…...

tensorflow案例5--基于改进VGG16模型的马铃薯识别,准确率提升0.6%,计算量降低78.07%

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 本次采用VGG16模型进行预测&#xff0c;准确率达到了98.875&#xff0c;但是修改VGG16网络结构&#xff0c; 准确率达到了0.9969&#xff0c;并且计算量…...

代码中的设计模式-策略模式

假如我们有一段代码,有很多的if else function executeAction(type) {if (type A) {console.log(Action A);} else if (type B) {console.log(Action B);} else if (type C) {console.log(Action C);} else {console.log(Unknown action);} }executeAction(A); // 输出: Ac…...

后端Node学习项目-项目基础搭建

前言 各位好&#xff0c;我是前端SkyRain。最近为了响应公司号召&#xff0c;开始对后端知识的学习&#xff0c;作为纯粹小白&#xff0c;记录下每一步的操作流程。 项目仓库&#xff1a;https://gitee.com/sky-rain-drht/drht-node 因为写了文档&#xff0c;代码里注释不是很…...

Python | Leetcode Python题解之第538题把二叉搜索树转换为累加树

题目&#xff1a; 题解&#xff1a; class Solution:def convertBST(self, root: TreeNode) -> TreeNode:def getSuccessor(node: TreeNode) -> TreeNode:succ node.rightwhile succ.left and succ.left ! node:succ succ.leftreturn succtotal 0node rootwhile nod…...

【ZeroMQ 】ZeroMQ中inproc优势有哪些?与其它传输协议有哪些不同?

inproc 是 ZeroMQ 提供的一种传输协议&#xff0c;用于在同一进程内的不同线程之间进行高效的通信。与其他传输协议&#xff08;如 tcp、ipc 等&#xff09;不同&#xff0c;inproc 专门针对线程间通信进行了优化&#xff0c;具有极低的延迟和开销。以下是 inproc 的底层原理和…...

spark的学习-03

RDD的创建的两种方式&#xff1a; 方式一&#xff1a;并行化一个已存在的集合 方法&#xff1a;parallelize 并行的意思 将一个集合转换为RDD 方式二&#xff1a;读取外部共享存储系统 方法&#xff1a;textFile、wholeTextFile、newAPIHadoopRDD等 读取外部存储系统的数…...

一文了解Android SELinux

在Android系统中&#xff0c;SELinux&#xff08;Security-Enhanced Linux&#xff09;是一个增强的安全机制&#xff0c;用于对系统进行强制访问控制&#xff08;Mandatory Access Control&#xff0c;MAC&#xff09;。它限制了应用程序和进程的访问权限&#xff0c;提供了更…...

数据血缘追踪是如何在ETL过程中发挥作用?

在大数据环境下&#xff0c;数据血缘追踪具有重要意义&#xff0c;它能够帮助用户了解数据的派生关系、变换过程和使用情况&#xff0c;进而提高数据的可信度和可操作性。通过数据血缘追踪&#xff0c;ETL用户可以准确追溯数据的来源&#xff0c;快速排查数据异常和问题。 一、…...

跟我学C++中级篇——生产中如何调试程序

一、程序的BUG和异常 程序不是发布到生产环境就万事大吉了。没有人敢保证自己写的代码没有BUG&#xff0c;放心&#xff0c;说这种话的人&#xff0c;基本可以断定是小白。如果在开发阶段出现问题&#xff0c;还是比较好解决的&#xff0c;但是如果真到了生产上&#xff0c;可…...

Python爬虫实战 | 爬取网易云音乐热歌榜单

网易云音乐热歌榜单爬虫实战 环境准备 Python 3.xrequests 库BeautifulSoup 库 安装依赖 pip install requests beautifulsoup4代码 import requests from bs4 import BeautifulSoupdef get_cloud_music_hot_songs():url "http://music.163.com/#/discover/playlist…...

apk因检测是否使用代理无法抓包绕过方式

最近学习了如何在模拟器上抓取APP的包&#xff0c;APP防恶意行为的措施可分为三类&#xff1a; &#xff08;1&#xff09;反模拟器调试 &#xff08;2&#xff09;反代理 &#xff08;3&#xff09;反证书检验 第一种情况&#xff1a; 有的app检验是否使用系统代理&#xff0c…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

Java + Spring Boot + Mybatis 实现批量插入

在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法&#xff1a;使用 MyBatis 的 <foreach> 标签和批处理模式&#xff08;ExecutorType.BATCH&#xff09;。 方法一&#xff1a;使用 XML 的 <foreach> 标签&#xff…...

如何做好一份技术文档?从规划到实践的完整指南

如何做好一份技术文档&#xff1f;从规划到实践的完整指南 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 总有一行代码&#xff0c;能点亮万千星辰。 &#x1f50d; 在技术的宇宙中&#xff0c;我愿做永不停歇的探索者。 ✨ 用代码丈量世界&…...

【记录坑点问题】IDEA运行:maven-resources-production:XX: OOM: Java heap space

问题&#xff1a;IDEA出现maven-resources-production:operation-service: java.lang.OutOfMemoryError: Java heap space 解决方案&#xff1a;将编译的堆内存增加一点 位置&#xff1a;设置setting-》构建菜单build-》编译器Complier...

spring boot使用HttpServletResponse实现sse后端流式输出消息

1.以前只是看过SSE的相关文章&#xff0c;没有具体实践&#xff0c;这次接入AI大模型使用到了流式输出&#xff0c;涉及到给前端流式返回&#xff0c;所以记录一下。 2.resp要设置为text/event-stream resp.setContentType("text/event-stream"); resp.setCharacter…...

【多线程初阶】单例模式 指令重排序问题

文章目录 1.单例模式1)饿汉模式2)懒汉模式①.单线程版本②.多线程版本 2.分析单例模式里的线程安全问题1)饿汉模式2)懒汉模式懒汉模式是如何出现线程安全问题的 3.解决问题进一步优化加锁导致的执行效率优化预防内存可见性问题 4.解决指令重排序问题 1.单例模式 单例模式确保某…...

VUE3 ref 和 useTemplateRef

使用ref来绑定和获取 页面 <headerNav ref"headerNavRef"></headerNav><div click"showRef" ref"buttonRef">refbutton</div>使用ref方法const后面的命名需要跟页面的ref值一样 const buttonRef ref(buttonRef) cons…...

Nginx 事件驱动理解

在做埋点采集服务的过程中&#xff0c;主要依靠openresty加lua脚本来实现采集。高并发还是主要依靠nginx来实现。而其核心就是事件驱动/多路io复用&#xff08;epoll机制&#xff09;&#xff0c;不同的linux服务器都有对应的实现方式。 而epoll机制就是&#xff0c;应用启动的…...