超详细实现单链表的基础增删改查——基于C语言实现

文章目录

  • 1、链表的概念与分类
    • 1.1 链表的概念
    • 1.2 链表的分类
  • 2、单链表的结构和定义
    • 2.1 单链表的结构
    • 2.2 单链表的定义
  • 3、单链表的实现
    • 3.1 创建新节点
    • 3.2 头插和尾插的实现
    • 3.3 头删和尾删的实现
    • 3.4 链表的查找
    • 3.5 指定位置之前和之后插入数据
    • 3.6 删除指定位置的数据和删除指定位置之后的数据
    • 3.7 链表的销毁
  • 4、单链表与顺序表的区别
  • 5、结语
  • 6、 完整实现代码
    • 6.1 头文件SList.h
    • 6.2 SList.c文件

1、链表的概念与分类

1.1 链表的概念

链表是一种在物理结构上不连续、非线性的数据存储结构,但它在逻辑结构上是线性的,这是通过链表中的指针链接次序来实现。
链表结构

1.2 链表的分类

链表有很多种结构,分别带头和不带头,单向和双向,循环和不循环,以上各种情况组合起来就多达222 = 8种链表结构,分别是:

  1. 不带头单向不循环链表
  2. 不带头单向循环链表
  3. 不带头双向循环链表
  4. 不带头双向不循环链表
  5. 带头双向循环链表
  6. 带头单向循环链表
  7. 带头单向不循环链表
  8. 带头双向不循环链表

**单向和双向的区别:**单向的链表每个节点只有一个指向下一个节点的指针,而双向链表每个节点有指向下一个节点的指针,也有指向上一个节点的指针。
单向链表和双向链表
**带头和不带头的区别:**不带头链表的第一个节点也就是头节点,是第一个存储数据的有效节点,而带头链表的头节点则是不存储数据,也被叫做哨兵位,哨兵位后的第一个节点才开始存储有效数据。
带头和不带头
**循环与不循环的区别:**不循环链表的尾节点指向NULL,而循环链表的尾节点指向链表的头节点,构成环。
循环与不循环
上面这么多种结构中,我们最常用的是不带头单向不循环链表(简称单链表)和带头双向循环链表。

2、单链表的结构和定义

2.1 单链表的结构

依据前面所提的关于不带头单向不循环链表的结构,我们可以得出,单链表每个节点有两个变量,一个用来存放数据,一个用来指向下一节点,那么我们就可以根据这个结构来定义我们的单链表。

2.2 单链表的定义

typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;

其中,我们将int重定义为SLTDataType,是为了后续方便我们修改整个链表的数据类型,将struct SListNode重定义为SLTNode,方便我们后面使用。

3、单链表的实现

3.1 创建新节点

因为后续的插入操作都需要创建新节点,为实现代码复用,避免太多重复代码,我们封装一个用于创建新节点的函数。
实现思路:首先创建一个临时的节点并为其开辟空间(提高代码健壮性可以判断是否申请空间成功),然后将传过来的值赋给创建的节点,再将新节点的指针置空,最后返回这个节点。
具体实现代码如下:

SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = x;newnode->next = NULL;return newnode;
}

3.2 头插和尾插的实现

我们给出下面两个函数的声明,分别是实现链表的头插和尾插,注意这里传过去的是二级指针,因为链表头节点本身就是一个一级的结构体指针,我们要修改它的值,就要在这里进行传址操作。

//链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);

链表头插的实现思路:首先我们要判断传进来的指针是否为空,可以使用assert断言,如果为空则直接异常中止,如果不为空则创建新节点,并将新节点的指针指向头节点,再将新节点作为新的头节点。
实现代码:

void SListPushFront(SLTNode** pphead, SListDataType x)
{assert(pphead);SLTNode* newNode = SLTBuyNode(x);newNode->next = *pphead;*pphead = newNode;
}
//链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);

尾插的实现思路:同样,进来先用断言判断pphead是否为空,然后创建新节点,再判断链表是否为空,如果为空,则将新创建的节点作为头节点,然后返回,如果不为空,则需要遍历当前链表,找到尾节点,将尾节点的next指针指向我们的新节点。
代码实现如下:

//链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);//链表为空if (*pphead == NULL){*pphead = newnode;return;}//链表不为空,找尾节点SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}ptail->next = newnode;
}

3.3 头删和尾删的实现

函数声明:

//链表的头删
void SLTPopFront(SLTNode** pphead);

头删的实现思路:
头删我们要考虑链表可能存在为空、只有一个节点、有多个节点三种情况,因此我们在用断言判断头节点指针是否为空后要再判断头节点是否为空,再去考虑只有一个节点和多个节点的情况,如果只有一个节点,那么执行完头删后链表为空,我们需要释放头节点的内存,并将头节点置空,然后返回,如果有多个节点,我们则创建一个新的节点,来存放头节点的next指针,也就是第二个节点,再释放头节点的内存(这里就是头删操作),这个时候再将我们创建的新节点赋值给头节点,此时头节点就为原来的第二个节点,完成头删操作。
代码实现如下:

void SLTPopFront(SLTNode** pphead) {assert(pphead);assert(*pphead);//链表只有一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}SLTNode* newhead = (*pphead)->next;free(*pphead);*pphead = newhead;}
//链表的尾删
void SLTPopBack(SLTNode** pphead);

尾删的实现思路:
同样,我们还是要考虑链表为空、链表只有一个节点、链表有多个节点的情况,链表为空则无法删,链表只有一个节点那么就和头删一样的处理方式,释放头节点,置空然后返回,如果存在多个节点,我们则需要遍历链表找到为尾节点,不同于头删的是,我们在遍历链表找尾节点的时候,要创建一个临时的变量prev来存放尾节点的前一个节点,然后找到尾节点后释放尾节点,并将prev的next指针置空,再赋值给尾节点,此时就完成了尾删操作。
代码实现如下:

void SLTPopBack(SLTNode** pphead) {assert(pphead);//链表不能为空assert(*pphead);//链表只有一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}prev->next = NULL;free(ptail);ptail = NULL;
}

3.4 链表的查找

函数声明:

//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);

查找的实现思路:这个实现的思路比较简单,首先传进来的pphead不能为空,然后遍历整个链表,用每个节点的data和x进行比较,如果相同则返回当前节点,如果遍历完整个链表找不到和x相同的data,则说明不存在这个节点,那么就返回一个NULL。
代码实现如下:

//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{assert(pphead);//遍历链表SLTNode* pcur = *pphead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}

将SLTNode* 类型作为查找函数的返回类型,可以方便我们后续指定位置进行操作。

3.5 指定位置之前和之后插入数据

函数声明:

//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead,SLTNode* pos, SLTDataType x);

指定位置之前插入数据的实现思路:首先我们要考虑传进来的pphead是否为空,删除的节点是否为空,以及链表是否为空的情况,所以一开始要有3次断言,创建一个新节点,然后考虑删除的节点pos正好是头节点的情况,以及pos不是头节点的情况,如果是头节点则直接进行头插操作,不是头节点我们则需要遍历链表找pos节点,这里的循环条件是当前节点prev的下一个节点不为pos,那么循环终止时,当前节点prev就应该是pos的前一个节点,此时我们再让新节点的next指针指向pos,让prev的next指针指向新节点,完成pos前插入数据的操作。
代码实现如下:

//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {assert(pphead);assert(pos);assert(*pphead);SLTNode* newnode = SLTBuyNode(x);//pos刚好是头节点if (pos == *pphead){//头插SLTPushFront(pphead, x);return;}//pos不是头节点的情况SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}newnode->next = pos;prev->next = newnode;
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);

指定位置之后插入数据的实现思路:
可以看到这个函数的参数只有pos节点和插入数据x,也就是说不需要对整个链表进行操作,具体是如何实现的呢,同样的,我们需要先用断言判断pos节点是否为空,然后创建一个新节点存放插入数据x,我们要在pos节点之后插入数据,这个操作会同时影响到pos和pos的下一个节点,也就是pos->next,我们只需要让新节点的next指针指向pos的下一个节点pos->next,再让pos的next指针指向新节点,即可完成pos后插入数据的操作。
代码实现如下:

//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);//  pos   newnode   pos->nextnewnode->next = pos->next;pos->next = newnode;
}

3.6 删除指定位置的数据和删除指定位置之后的数据

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);

实现思路:首先我们还是要对pphead、*pphead以及pos进行断言,然后考虑pos节点是否为头节点的情况,如果为头节点,那么我们直接进行头删操作即可,如果不是,我们就要遍历链表,找到pos的前一个节点prev,让prev的next指针指向pos的下一个节点pos->next,再释放pos节点的内存,将pos置空,至此完成删除pos节点操作。
代码实现如下:

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos) {assert(pphead);assert(*pphead);assert(pos);//pos是头节点if (pos == *pphead){//头删SLTPopFront(pphead);return;}SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);

实现思路:我们要考虑两种情况,pos节点是否为空,以及pos是否为尾节点(如果pos是尾节点就没有删除pos之后节点的说法了),因此我们需要对pos和pos->next进行断言。我们创建一个临时的节点del存放pos的下一个节点,也就是我们要删除的节点,然后让pos的next指针指向del的下一个节点,也就是pos的下下个节点,再释放del,将del置空,这样就完成对pos后的节点删除的操作了。
代码实现:

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;}

3.7 链表的销毁

函数声明:

//销毁链表
void SLTDestroy(SLTNode** pphead);

实现思路:首先还是要对pphead和pphead进行断言,然后就是遍历链表对每个节点依次释放内存,最后要将头节点pphead置空。
代码实现如下:

//销毁链表
void SLTDestroy(SLTNode** pphead) {assert(pphead);assert(*pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

4、单链表与顺序表的区别

单链表和顺序表都是线性表,但顺序表在物理结构上一定连续,因为顺序表的底层结构是数组,数组的存储就是一块连续的空间,而单链表在物理结构上不一定连续,为什么说不一定的,因为每个节点的地址都是随机分配的,无法确定。顺序表相对于单链表而言,它因为底层结构是数组,所以能够做到随机访问,而链表随机访问一个节点都要进行遍历,效率较低,但顺序表插入和删除数据需要对整个数组的元素进行搬移,而链表则只需要修改指针指向,二者各有各的优缺点,这也就给他们带来了不同的应用场景,但你需要频繁访问存储的数据时,可以考虑顺序表作为底层结构,当你需要频繁的删除插入操作时,链表就更加符合你的需求。所以说存在即合理,每个数据结构都有他们的优势和缺陷,没有绝对的谁好谁差的区分。我们要做到的是了解、熟悉每一个数据结构,在未来遇到各种应用场景时,能够根据不同的需求选择最合适的数据结构。

5、结语

这篇文章就讲到这里了,单链表相关功能的测试,大家就自己去尝试一下,如果存在什么错误和纰漏的地方,请及时指出,一定改正,数据结构这部分确实是代码量最多的一部分,不仅要学数据结构,还有各种各样的算法,需要不断地加以练习,后面会开个新专栏用来记录我的刷题,努力学习,共同进步。最后附上完整的代码,希望这篇文章能够给你带来帮助。

6、 完整实现代码

6.1 头文件SList.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//链表节点结构typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//打印链表
void SLTPrint(SLTNode* phead);//链表的头插和尾插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPushBack(SLTNode** pphead, SLTDataType x);//链表的头删和尾删
void SLTPopFront(SLTNode** pphead);
void SLTPopBack(SLTNode** pphead);//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead,SLTNode* pos, SLTDataType x);//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);//销毁链表
void SLTDestroy(SLTNode** pphead);

6.2 SList.c文件

#include"SList.h"//打印链表
void SLTPrint(SLTNode* phead) {SLTNode* pcur = phead;while (pcur){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}//创建新节点
SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = x;newnode->next = NULL;return newnode;
}//链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);//链表为空if (*pphead == NULL){*pphead = newnode;return;}//链表不为空,找尾节点SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}ptail->next = newnode;
}//链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x) {assert(pphead);SLTNode* newnode = SLTBuyNode(x);//链表为空//链表不为空newnode->next = *pphead;*pphead = newnode;
}//链表的头删和尾删
void SLTPopFront(SLTNode** pphead) {assert(pphead);assert(*pphead);//链表只有一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}SLTNode* newhead = (*pphead)->next;free(*pphead);*pphead = newhead;}
void SLTPopBack(SLTNode** pphead) {assert(pphead);//链表不能为空assert(*pphead);//链表只有一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}prev->next = NULL;free(ptail);ptail = NULL;
}//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{assert(pphead);//遍历链表SLTNode* pcur = *pphead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {assert(pphead);assert(pos);assert(*pphead);SLTNode* newnode = SLTBuyNode(x);//pos刚好是头节点if (pos == *pphead){//头插SLTPushFront(pphead, x);return;}//pos不是头节点的情况SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}newnode->next = pos;prev->next = newnode;
}//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos) {assert(pphead);assert(*pphead);assert(pos);//pos是头节点if (pos == *pphead){//头删SLTPopFront(pphead);return;}SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;
}//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;}//销毁链表
void SLTDestroy(SLTNode** pphead) {assert(pphead);assert(*pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

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

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

相关文章

17.整体代码讲解

从入门AI到手写Transformer-17.整体代码讲解 17.整体代码讲解代码 整理自视频 老袁不说话 。 17.整体代码讲解 代码 import collectionsimport math import torch from torch import nn import os import time import numpy as np from matplotlib import pyplot as plt fro…

前端性能优化:所有权转移

前端性能优化&#xff1a;所有权转移 在学习rust过程中&#xff0c;学到了所有权概念&#xff0c;于是便联想到了前端&#xff0c;前端是否有相关内容&#xff0c;于是进行了一些实验&#xff0c;并整理了这些内容。 所有权转移&#xff08;Transfer of Ownership&#xff09;…

Missashe考研日记-day23

Missashe考研日记-day23 0 写在前面 博主前几天有事回家去了&#xff0c;断更几天了不好意思&#xff0c;就当回家休息一下调整一下状态了&#xff0c;今天接着开始更新。虽然每天的博客写的内容不算多&#xff0c;但其实还是挺费时间的&#xff0c;比如这篇就花了我40多分钟…

Docker 中将文件映射到 Linux 宿主机

在 Docker 中&#xff0c;有多种方式可以将文件映射到 Linux 宿主机&#xff0c;以下是常见的几种方法&#xff1a; 使用-v参数• 基本语法&#xff1a;docker run -v [宿主机文件路径]:[容器内文件路径] 容器名称• 示例&#xff1a;docker run -it -v /home/user/myfile.txt:…

HarmonyOS-ArkUI-动画分类简介

本文的目的是,了解一下HarmonyOS动画体系中的分类。有个大致的了解即可。 动效与动画简介 动画,是客户端提升界面交互用户体验的一个重要的方式。可以使应用程序更加生动灵越,提高用户体验。 HarmonyOS对于界面的交互方面,围绕回归本源的设计理念,打造自然,流畅品质一提…

C++如何处理多线程环境下的异常?如何确保资源在异常情况下也能正确释放

多线程编程的基本概念与挑战 多线程编程的核心思想是将程序的执行划分为多个并行运行的线程&#xff0c;每个线程可以独立处理任务&#xff0c;从而充分利用多核处理器的性能优势。在C中&#xff0c;开发者可以通过std::thread创建线程&#xff0c;并使用同步原语如std::mutex、…

区间选点详解

步骤 operator< 的作用在 C 中&#xff0c; operator< 是一个运算符重载函数&#xff0c;它定义了如何比较两个对象的大小。在 std::sort 函数中&#xff0c;它会用到这个比较函数来决定排序的顺序。 在 sort 中&#xff0c;默认会使用 < 运算符来比较两个对象…

前端配置代理解决发送cookie问题

场景&#xff1a; 在开发任务管理系统时&#xff0c;我遇到了一个典型的身份认证问题&#xff1a;​​用户登录成功后&#xff0c;调获取当前用户信息接口却提示"用户未登录"​​。系统核心流程如下&#xff1a; ​​用户登录​​&#xff1a;调用 /login 接口&…

8.1 线性变换的思想

一、线性变换的概念 当一个矩阵 A A A 乘一个向量 v \boldsymbol v v 时&#xff0c;它将 v \boldsymbol v v “变换” 成另一个向量 A v A\boldsymbol v Av. 输入 v \boldsymbol v v&#xff0c;输出 T ( v ) A v T(\boldsymbol v)A\boldsymbol v T(v)Av. 变换 T T T…

【java实现+4种变体完整例子】排序算法中【冒泡排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格

以下是冒泡排序的详细解析&#xff0c;包含基础实现、常见变体的完整代码示例&#xff0c;以及各变体的对比表格&#xff1a; 一、冒泡排序基础实现 原理 通过重复遍历数组&#xff0c;比较相邻元素并交换逆序对&#xff0c;逐步将最大值“冒泡”到数组末尾。 代码示例 pu…

系统架构设计(二):基于架构的软件设计方法ABSD

“基于架构的软件设计方法”&#xff08;Architecture-Based Software Design, ABSD&#xff09;是一种通过从软件架构层面出发指导详细设计的系统化方法。它旨在桥接架构设计与详细设计之间的鸿沟&#xff0c;确保系统的高层结构能够有效指导后续开发。 ABSD 的核心思想 ABS…

Office文件内容提取 | 获取Word文件内容 |Javascript提取PDF文字内容 |PPT文档文字内容提取

关于Office系列文件文字内容的提取 本文主要通过接口的方式获取Office文件和PDF、OFD文件的文字内容。适用于需要获取Word、OFD、PDF、PPT等文件内容的提取实现。例如在线文字统计以及论文文字内容的提取。 一、提取Word及WPS文档的文字内容。 支持以下文件格式&#xff1a; …

Cesium学习笔记——dem/tif地形的分块与加载

前言 在Cesium的学习中&#xff0c;学会读文档十分重要&#xff01;&#xff01;&#xff01;在这里附上Cesium中英文文档1.117。 在Cesium项目中&#xff0c;在平坦坦地球中加入三维地形不仅可以增强真实感与可视化效果&#xff0c;还可以​​提升用户体验与交互性&#xff0c…

Spring Boot 断点续传实战:大文件上传不再怕网络中断

精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 一、痛点与挑战 在网络传输大文件&#xff08;如视频、数据集、设计稿&#xff09;时&#xff0c;常面临&#xff1a; 上传中途网络中断需重新开始服务器内…

数码管LED显示屏矩阵驱动技术详解

1. 矩阵驱动原理 矩阵驱动是LED显示屏常用的一种高效驱动方式&#xff0c;利用COM&#xff08;Common&#xff0c;公共端&#xff09;和SEG&#xff08;Segment&#xff0c;段选&#xff09;线的交叉点控制单个LED的亮灭。相比直接驱动&#xff0c;矩阵驱动可以显著减少所需I/…

【上位机——MFC】菜单类与工具栏

菜单类 CMenu&#xff0c;封装了关于菜单的各种操作成员函数&#xff0c;另外还封装了一个非常重要的成员变量m_hMenu(菜单句柄) 菜单使用 添加菜单资源加载菜单 工具栏相关类 CToolBarCtrl-》父类是CWnd&#xff0c;封装了关于工具栏控件的各种操作。 CToolBar-》父类是CC…

liunx中常用操作

查看或修改linux本地mysql端口 cat /etc/my.cnf 如果没有port可以添加&#xff0c;有可以修改 查看本地端口占用情况 bash netstat -nlt | grep 3307 HADOOP集群 hdfs启动与停止 # 一键启动hdfs集群 start-dfs.sh # 一键关闭hdfs集群 stop-dfs.sh #除了一键启停外&#x…

衡石chatbi如何通过 iframe 集成

iframe 集成方式是最简单的一种&#xff0c;您只需要在您的 HTML 文件中&#xff08;或 Vue/React 组件中&#xff09;添加一个 iframe 元素&#xff0c;并设置其 src 属性为 AI 助手的 URL。 <iframesrc"https://develop.hengshi.org/copilot"width"100%&q…

Java集合框架深度解析:HashMap、HashSet、TreeMap、TreeSet与哈希表原理详解

一、核心数据结构总览 1. 核心类继承体系 graph TDMap接口 --> HashMapMap接口 --> TreeMapSet接口 --> HashSetSet接口 --> TreeSetHashMap --> LinkedHashMapHashSet --> LinkedHashSetTreeMap --> NavigableMapTreeSet --> NavigableSet 2. 核心特…

HTTP 1.0 和 2.0 的区别

HTTP 1.0 和 2.0 的核心区别体现在性能优化、协议设计和功能扩展上&#xff0c;以下是具体对比&#xff1a; 一、核心区别对比 特性HTTP 1.0HTTP 2.0连接方式非持久连接&#xff08;默认每次请求新建 TCP 连接&#xff09;持久连接&#xff08;默认保持连接&#xff0c;可复用…