【Linux】缓冲区/磁盘inode/动静态库

news2024/11/17 13:35:56


目录

一、缓冲区

1、缓冲区的概念

2、缓冲区的意义

3、缓冲区刷新策略

4、同一份代码,打印结果不同

5、仿写FILE

5.1myFILE.h

5.2myFILE.c 

5.3main.c

6、内核缓冲区

二、了解磁盘

1、磁盘的物理结构

2、磁盘的存储结构

2.1磁盘的定位

3、磁盘的抽象存储结构

3.1为什么操作系统要将CHS抽象为LBA地址?

3.2大小为4KB的页框和页帧

4、磁盘的文件系统

4.1ext文件系统

4.2在文件系统中查找对应文件

4.3在文件系统中删除对应文件

4.4目录的内容和属性

三、软硬链接

1、软链接

1.1建立/删除软链接

1.2软链接的应用

2、硬链接

2.1建立/删除硬链接

2.2硬链接的应用

四、文件的三个时间

五、动静态库

1、静态库的制作

1.1静态库的生成

1.2将静态库和头文件合并

1.3用户如何使用静态库

2、动态库的制作

2.1静态库的生成

2.2将动态库和头文件合并

2.3用户如何使用动态库

3、动静态库的总结


一、缓冲区

1、缓冲区的概念

缓冲区的本质就是一段用作缓存的内存

2、缓冲区的意义

节省进程进行数据IO的时间。进程使用fwrite等函数把数据拷贝到缓冲区或者外设中。

3、缓冲区刷新策略

1、立即刷新(无缓冲)——ffush()

情况很少,比如调用printf后,手动调用fflush刷新缓冲区。

2、行刷新(行缓冲)——显示器

显示器需要满足人的阅读习惯,故采用行刷新的策略而不是全缓冲的策略。

虽然全缓冲的刷新方式,可以大大降低数据IO的次数,节省时间。但若数据暂存于缓冲区,等缓冲区满后再刷出,当人阅读时面对屏幕中出现的一大堆数据,很难不懵逼。所以显示器采用行刷新的策略,既保证了人的阅读习惯,又使得数据IO效率不至于太低。

3、缓冲区满后刷新(全缓冲)——磁盘文件

4、特殊的刷新情况

用户强制刷新或进程退出。

4、同一份代码,打印结果不同

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main()
{
    printf("hello printf\n");//先打印至stdout缓冲区中
    fprintf(stdout,"hello fprintf\n");
    fputs("hello fputs\n",stdout);

    const char* msg="hello write\n";
    write(1,msg,strlen(msg));
    fork();//生成子进程
    return 0;
}

运行上方代码生成的可执行文件,在显示器上是正常打印,但是将运行结果重定向至文本,会发现C接口的打印函数打印了两次。

这个现象和缓冲区有关,从侧面说明了缓冲区并不存在内核中,否则write也会打印两次。用户级语言层面提供的缓冲区在FILE*指向的stdin/stdout/stderr中,FILE结构体会包含fd和缓冲区。需要强制刷新时,调用fflush(FILE*);关闭文件时,调用fclose(FILE*)。参数为FILE*就是为了刷新FILE*指向的FILE结构体中的缓冲区。

上方代码现象的解释:

1、stdout默认采用行刷新策略,每条打印函数都带了'\n',所以在fork之前,数据已经全部被打印到了显示器,缓冲区被并没有数据,当代码运行到fork创建子进程时,子进程对应的缓冲区当然也是没有任何数据。

2、当写入的是磁盘文件时,采用的是全缓冲的刷新策略,程序运行到fork时,缓冲区并没有被写满,数据仍存在于缓冲区中,当然被创建的子进程也拷贝了一份缓冲区的数据,当父子进程退出时,父子进程缓冲区中的数据将被刷新。所以出现了C接口函数被打印了两份的现象。

3、上面的过程与系统调用write无关,write没有FILE,使用的是fd,当然就没有C提供的缓冲区。

5、仿写FILE

5.1myFILE.h

#pragma once
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <assert.h>
#define SIZE 1024
//1,2,4二进制1的位置不一样
#define SYNC_NOW  1//立即刷新
#define SYNC_LINE 2//行刷新
#define SYNC_FULL 4//全刷新
typedef struct _FILE
{
    int flags;//刷新方式
    int fileno;//文件描述符
    int cap;//buffer总容量
    int size;//buffer当前使用量
    char buffer[];//缓冲区
}FILE_;
FILE_* fopen_(const char* path_name,const char* mode);
void fwrite_(const void* ptr,int num,FILE_* fp);
void fclose_(FILE_* fp);
void fflush_(FILE_* fp);

5.2myFILE.c 

#include "myStdio.h"
FILE_* fopen_(const char* path_name,const char* mode)
{
    int flags=0;//文件打开方式
    int defaultMode=0666;//权限
    if(strcmp(mode,"r")==0)
    {
        flags|=O_RDONLY;
    }
    else if(strcmp(mode,"w")==0)
    {
        flags|=(O_WRONLY|O_CREAT|O_TRUNC);
    }
    else if(strcmp(mode,"a")==0)
    {
         flags|=(O_WRONLY|O_CREAT|O_APPEND);
    }
    else 
    {}
    int fd=0;
    if(flags&O_RDONLY)
        fd=open(path_name,flags);
    else 
        fd=open(path_name,flags,defaultMode);
    if(fd<0)//打开失败
    {
        const char* err=strerror(errno);//获取错误
        write(2,err,strlen(err));
        return NULL;//打开文件失败返回NULL的原因
    }
    FILE_* fp=(FILE_*)malloc(sizeof(FILE_));
    assert(fp);
    fp->flags=SYNC_LINE;//默认设置为行刷新
    fp->fileno=fd;
    fp->cap=SIZE;
    fp->size=0;
    memset(fp->buffer,0,SIZE);
    return fp;//打开文件返回FILE*的指针
}
void fwrite_(const void* ptr,int num,FILE_* fp)//把数据写到缓冲区
{
    memcpy(fp->buffer+fp->size,ptr,num);//不考虑缓冲区满的情况
    fp->size+=num;
    // 判断是否刷新
    if(fp->flags&SYNC_NOW)
    {
        write(fp->fileno,fp->buffer,fp->size);
        fp->size=0;//清空缓冲区
    }
    else if(fp->flags&SYNC_FULL)
    {
        if(fp->size==fp->cap)
        {
            write(fp->fileno,fp->buffer,fp->size);
            fp->size=0;
        }
    }
    else if(fp->flags&SYNC_LINE)
    {
        if(fp->buffer[fp->size-1]=='\n')
        {
            write(fp->fileno,fp->buffer,fp->size);
            fp->size=0;
        }
    }
}
void fflush_(FILE_* fp)
{
    if(fp->size>0)
    {
        write(fp->fileno,fp->buffer,fp->size);
        fsync(fileno);//将数据强制刷新至磁盘
    }
}
void fclose_(FILE_* fp)//关闭文件
{
    fflush_(fp);//刷新文件
    close(fp->fileno);
}

5.3main.c

#include "myStdio.h"
#include <stdio.h>

int main()
{
    FILE_ *fp = fopen_("./log.txt", "w");
    if(fp == NULL)
    {
        return 1;
    }
    int cnt = 10;
    const char *msg = "hello bit ";//const char *msg = "hello bit\n";//log中刷新的策略不一样
    while(1)
    {
        fwrite_(msg, strlen(msg), fp);
        //fflush_(fp);//这样没带\n也刷
        sleep(1);
        printf("count: %d\n", cnt);
        //if(cnt == 5) fflush_(fp);
        cnt--;
        if(cnt == 0) break;
    }
    fclose_(fp);
    return 0;
}

main.c中msg指向的字符串有无\n,这个程序对应的刷新策略不同。

用户级缓冲区存在于FILE结构体中。

6、内核缓冲区

数据写入磁盘,完整的过程是先写入FILE结构体中的缓冲区,这个缓冲区的缓冲策略是立即缓冲、行缓冲、全缓冲;再通过struct file{}结构体将数据刷新至内核缓冲区;但是内核缓冲区的刷新并不遵循用户级的刷新策略,由操作系统自主决定,例如内存不足等原因均会影响操作系统的刷新。

如果操作系统突然挂了,那么会将内核缓冲区中的数据将会丢失。但如果是银行这种对数据安全敏感的行业呢?

#include <unistd.h>
int fsync(int fd);

使用fsync强制操作系统将内核缓冲区中该文件的数据立即刷新至存储设备。

二、了解磁盘

1、磁盘的物理结构

磁盘是计算机中唯一一个机械结构并且是一个外设,相对于其他存储设备来说较慢。但是价格低廉、存储量大,成为了企业存储设备的首选。磁盘磁头和盘面之间的距离极近,不能进灰尘,使用时禁止搬移抖动刮花盘面,造成数据丢失。

磁盘通过磁头充放电,完成盘面南北极的调转,即二进制数据的写入。

2、磁盘的存储结构

磁盘在寻址的时候,基本单位是扇区(512字节)。如图所示,绿色部分就是扇区,越靠近同心圆的扇区面积越小,越远离扇区的同心圆面积越大,但是每一个扇区的存储大小均为512字节。

2.1磁盘的定位

如何在盘面上定位扇区:通过磁头摆动确认在哪个磁道,通过盘片高速旋转让磁头定位扇区。(磁盘厂家会让磁盘转速与磁头寻址速度匹配,所以盘片旋转速度越快,该磁盘的IO效率越高。)

磁盘的所有磁头是共进退的,那么如何在磁盘中定位扇区:磁盘中定位一个扇区,硬件的定位方法采用CHS的定位法。1、先定位磁道(cylinder)(柱面)2、定位磁头(head)(即盘面)3、定位扇区(sector)。

3、磁盘的抽象存储结构

将一摞磁盘沿磁道“拉直”,就抽象成了线性结构。那么整个磁盘可以看做一个sector arr[n]数组,对磁盘数据的管理就变成了对数组的管理。只要知道了扇区的下标,就可以定位扇区。这个下标在操作系统内部称为LBA地址。根据LBA地址可以转化为CHS地址,从而找到对应扇区。

3.1为什么操作系统要将CHS抽象为LBA地址?

1、便于操作系统管理磁盘;

2、不想让操作系统的代码和硬件强耦合,硬件的变化并不会影响操作系统;

3.2大小为4KB的页框和页帧

虽然磁盘的最小单位是扇区512字节,但是太小了,操作系统的文件系统每次读取数据会以1KB、2KB、4KB为基本单位(大部分是4KB)读取至内存,哪怕用户只需要读取/修改1bit数据。这个特点也印证了顺序表缓存命中率高的优点,而链表由于节点存储地址跳跃,缓存命中率低。

以4KB为基本单位进行IO时,有时4KB数据并不能完全被利用,但这并不代表着浪费。根据局部性原理,当计算机访问某些数据时,它附近的数据也有非常大的概率被访问到,加载4KB有助于提高IO效率,同时增大缓存命中率。本质上就是一种数据预加载,以空间换时间的做法。

操作系统中内存被划分成了一块块4KB大小的空间,每个空间被称为页框

磁盘中的文件尤其是可执行文件,也是按照4KB大小划分好的块。每个块被称为页帧

4、磁盘的文件系统

磁盘采用分而治之的思想,例如一块500G的磁盘可以划分成4个125G进行管理,每个125G又可以分为多个5G进行管理·····

4.1ext文件系统

1、Super Block:存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。在一个分区中超级块的数量不止一个,作用是备份。

2、GDT(Group Descriptor Table):块组描述表,描述所有块组属性信息;

3、Block Bitmap:用0表示某位没有被使用,用1表示某位数据块已经被使用。

4、inode Bitmap:用0表示某位没有被使用,用1表示某位inode已经被使用。

5、inode Table:保存了分组内部所有的可用(已使用+未使用)的inode。如果inode表中有100个inode,每个inode的大小是128字节或256字节(根据文件系统的不同Inode大小不同),inode表总大小就是100*128或100*256字节。单个inode:存放文件中几乎所有的属性,如文件大小,所有者,最近修改时间等,唯独文件名不在inode表中存储。一个文件对应一个inode,inode是固定大小。每个分组中的inode为了区分彼此,它们都有自己的ID。

6、Data blocks:保存的是分组内部所有文件的数据块。单个Data block:存放文件内容,大小随文件大小变化而变化。

4.2在文件系统中查找对应文件

同一分区的inode是连续的,不同分区的inode是无关联的。使用inode编号找到对应块组,去inode Bitmap中查找该文件对应的比特位是不是1,是1则表示有文件属性,利用得到的inode Bitmap比特位,到inode Table中找到该文件的属性。

文件属性有了,那文件的数据如何获得?

inode结构体中除了inode编号,还保存了block[15]数据块数组。通过数组找到Data blocks中该文件对应的数据块。

每个数据块的大小为4KB,那么操作系统是如何用15个数组空间存储任意大小的文件呢?数组前12个下标中对应的数据块直接用于存储文件内容,数组后3个空间中存放的编号对应的数据块中存放了文件剩余数据的数据块编号。其中下标12是一级索引,它对应的数据块中存储的数据块编号直接用于存文件数据;下标13是二级索引,它对应的数据块中存储的数据块编号是一级索引;下标14是三级索引;逐级展开,能存储很大的文件。

4.3在文件系统中删除对应文件

在任何文件系统中,需要删除文件只需要将inode Bitmap、Block Bitmap中文件对应的比特位由1置0,这个文件就被删除了。当然想要恢复文件的话,只需要找到被删除文件的inode编号,将inode Bitmap中的比特位由0置1,找到该文件在inode Table中的位置,根据其中的映射关系找到文件的数据块,并把Block Bitmap由0置1即可恢复文件。注意文件被误删之后不要做任何非恢复操作,防止原文件属性和内容被覆盖。

4.4目录的内容和属性

虽说文件系统是使用inode编号查找和删除文件,但是用户使用的可不是inode,而是文件名。

目录也是个普通文件,也有自己的inode和数据块,目录的inode中存储的自然是目录的属性信息,但是目录的数据块中存的是当前目录下的文件名和inode的映射关系。(每个文件名对应它的inode)

这也解释了为什么一个目录下不能出现同名文件,因为一个名字对应一个inode。

在Linux权限中提到:在一个目录下创建文件,必须要有写入权限,原因就是创建文件需要在目录的数据块中写入文件名和它的映射关系。

三、软硬链接

1、软链接

1.1建立/删除软链接

unlink log_s//删除软硬链接

1.2软链接的应用

软链接类似windows中的快捷方式,快捷方式怎么用软链接就怎么用。上图是将其他路径中的可执行文件弄成软链接,直接./mylink执行即可,不用带路径。

软链接是一个独立的文件,有自己的inode属性和数据块。数据块中存储的是软链接指向目标文件的路径和文件名

2、硬链接

2.1建立/删除硬链接

unlink hard//删除软硬链接

对myfile.c建立硬链接hard,发现hard和myfile.c的inode值完全一样。硬链接没有自己的inode,根本就不是一个独立的文件,它只是对应文件的文件名和inode编号的映射关系。

这个数字就是硬链接数。代表有几个文件指向我。这个数组保存在inode结构体对象中的ref变量中,新建该文件的硬链接,ref++;反之,ref--,当ref减为0时,这个文件才被删除。(引用计数)

2.2硬链接的应用

一个普通文件被创建,硬链接数是1,因为自身的存在会让ref等于1;为什么一个新建的目录的硬链接数为2,?因为除了自身之外,目录中有一个隐藏的“·”指向当前目录。

在tmp目录下继续创建目录,硬链接++,因为tmp下新建目录dir中存在“··”,指向tmp。

四、文件的三个时间

[jly@VM-4-11-centos 动静态库]$ stat makefile 
  File: ‘makefile’
  Size: 0         	Blocks: 0          IO Block: 4096   regular empty file
Device: fd01h/64769d	Inode: 790078      Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1001/     jly)   Gid: ( 1001/     jly)
Access: 2022-12-22 08:31:52.313307187 +0800//文件最近被访问的时间
Modify: 2022-12-22 08:31:52.313307187 +0800//最近一次修改文件内容的时间
Change: 2022-12-22 08:31:52.313307187 +0800//最近一次修改文件属性的时间
 Birth: -

1、修改文件的内容时,Modify被改变可以理解,但为什么Change也被改变了。这是因为修改文件内容时,有可能会修改文件的属性,比如文件的大小可能被改变。

2、另一个现象是用户访问和修改文件时,Access时间大部分时候不改变。在较新的Linux内核中,Access时间不会被立即更新,经过一定的时间间隔,操作系统才对Access时间进行 一次更新。一个文件的读取或是修改,都会改变Access时间,如果当即对Access时间进行更新,操作系统会和磁盘进行大量的交互,使Linux系统变慢。Access时间的非即时更新是一个优化。

3、我们在编写完makefile后,make一下,就能生成对应的可执行程序。如果依赖的文件列表没有发生修改,gcc通过判断依赖文件的Modify time早于可执行程序的Modify time,说明依赖文件列表没有修改,那么再次make将会失败;反之,重新编译将会成功。不过多次make clean却不会失败,因为clean被.PHONY修饰,变成了一个伪目标,不受时间的约束,所以clean总是可被执行。

使用touch更新文件的三个时间:

touch makefile//makefile已存在,再次touch,将会更新makefile的三个时间

五、动静态库

ldd显示可执行程序依赖的库。

查看程序是动静态的方法:

file 可执行程序

静态程序的生成及动静态链接优缺点及更多详细信息可参照博主这一篇文章。

1、静态库的制作

一套完成的库包含1、库文件本身(二进制文件,人看不懂)2、头文件(文本类型,暴露库文件中的接口)3、说明文档。

/lib64        库文件的存放目录(有些是/usr/lib)
/usr/include  头文件的存放目录

费尽千辛万苦写出来的代码,目的是给别人用,但是我不想让别人知道源文件中方法的实现细节,那么就需要制作静态库。

1.1静态库的生成

1、将所有库文件编译为.o(可重定向二进制目标文件),用户拿到每个模块的.o文件,自行链接即可。将所有的.o打包就是库

使用ar -rc对多个.o进行打包。ar是gun归档工具。

gcc -c sub.c 
gcc -c add.c 
ar -rc libmymath.a sub.o add.o

对应的makefile: 

libmymath.a:sub.o add.o
	ar -rc $@ $^
%.o:%.c
	gcc -c $< 

先把两个.c生成两个.o,再把两个.o打包成.a静态库。

使用ar -tv查看静态库中的内容:

1.2将静态库和头文件合并

用户仅有静态库并不清楚静态库中的信息,所以需要将.h文件一并给用户。

以下为打包+.h整合至一个文件夹,并附带了一个安装功能的makefile:

libmymath.a:sub.o add.o
	ar -rc $@ $^
%.o:%.c
	gcc -c $< 
.PHONY:clean
clean:
	rm -rf *.o libmymath.a output

.PHONY:output
output:
	mkdir output 
	cp -rf *.h output 
	cp  libmymath.a output

.PHONY:install
install:
	cp *.h /usr/include 
	cp libmymath.a /lib64

1.3用户如何使用静态库

将打包好的静态库文件给到用户,用户自己写一个main函数,即可使用output中的.h文件。

不过在编译时,需要执行如下命令:

gcc test.c -I./output -L./output -lmymath
-I:告诉编译器在./output路径中找头文件
-L:告诉编译器在./output路径找库
-l:跟库名称(去掉前缀lib,去掉后缀.so或.a)

makefile: 

test:test.c 
	gcc -o $@ $^ -I./output -L./output -lmymath
.PHONY:clean
clean:
	rm -f test

使用编译器提供的库并行不需要带这些选项,是因为编译器有自己的环境变量,能够找到位于/lib64库文件的存放目录和/usr/include头文件的存放目录。

可以将静态库和头文件放入这些目录或其他相关目录下,这就是一般软件的安装过程。但是不推荐(自己写的库什么水平没点数吗?)。

2、动态库的制作

2.1静态库的生成

同样的,将所有.o进行打包。

makefile:

#-shared:形成一个动态链接的共享库
libmymath.so:add.o sub.o
	gcc -shared -o $@ $^
#-fPIC:产生.o目标文件,程序内部的地址方案是:与位置无关,库文件可以在内存的任意位置加载,不影响其他程序的关联性
%.o:*%.c
	gcc -fPIC -c $<

.PHONY:clean
clean:
	rm -f libmymath.so

这样就得到了一个动态库libmymath.so

2.2将动态库和头文件合并

makefile:

#-shared:形成一个动态链接的共享库
libmymath.so:add.o sub.o
	gcc -shared -o $@ $^
#-fPIC:产生.o目标文件,程序内部的地址方案是:与位置无关,库文件可以在内存的任意位置加载,不影响其他程序的关联性
%.o:*%.c
	gcc -fPIC -c $<

.PHONY:clean
clean:
	rm -rf libmymath.so *.o output

#发布
.PHONY:output
output:
	mkdir output 
	cp ./*.h output
	cp libmymath.so ./output  

2.3用户如何使用动态库

makefile:

test:test.c 
	gcc -o $@ $^ -I./output -L./output -lmymath//这里只是告知编译器头文件和库路径在哪里
.PHONY:clean
clean:
	rm -f test

动静态库的使用方式是一样的。

但是运行可执行程序会报错:没有这个文件或目录

使用ldd命令发现缺少了自己写的动态库:因为makefile只是告诉编译器头文件和库的路径,编译能通过,但是运行又不是编译器来运行,当然不知道详细库路径!

静态库能运行是因为静态链接是将所有内容全部拷贝到源文件。动态库编译/运行都需要这些路径,运行时需要通过加载器,告诉操作系统库路径在哪里。

方案一:将动态库和头文件拷贝至对应的系统库路径和头文件路径下(不推荐)

方案二:更改环境变量LD_LIBRARY_PATH

用于指定查找共享库(动态链接库)时除了默认路径(./lib和./usr/lib)之外的其他路径。
export LD_LIBRARY_PATH=/home/jly/5、基础IO/动静态库/动态库/test_A/output

当然这个环境变量在下次重新登录就没了,如果想让这个环境变量永久生效,可以把这个环境变量添加到登录相关的启动脚本里,下面两个都行,但是不建议,如果真要改,多开几个终端,防止改了之后登不上Linux: 

vim ~/.bash_profile
vim ~/.bashrc

方案三:ldconfig 

[jly@VM-4-11-centos test_A]$ ll /etc/ld.so.conf.d/ -d
drwxr-xr-x. 2 root root 4096 Sep  6 22:04 /etc/ld.so.conf.d/

/etc/ld.so.conf.d/是系统搜索动态库的路径

[jly@VM-4-11-centos ld.so.conf.d]$ sudo touch new.conf
写入路径
[jly@VM-4-11-centos ld.so.conf.d]$ sudo vim new.conf
[jly@VM-4-11-centos ld.so.conf.d]$ cat new.conf 
/home/jly/5、基础IO/动静态库/动态库/test_A/output
更新ldconfig
[jly@VM-4-11-centos ld.so.conf.d]$ sudo ldconfig

方案三重启有效。

3、动静态库的总结

制作动静态库:

1、将所有的源文件编译为.o可重定向目标文件;

2、制作动静态库的本质就是将所有.o和头文件“打包”,静态库使用ar -rc,动态库使用-shared和gcc -fFIC

3、使用:include+.a或.so文件

静态库只能静态链接,动态库只能动态链接。一般需要提供动静态两种版本的库,gcc和g++优先使用动态库进行链接,想要静态链接,需要手动在编译指令后添加-static选项。

Linux操作系统中一定会存在动态库,操作系统中有很多命令是由C语言写的,它们采用动态链接。

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

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

相关文章

【openGauss】浅试openGauss3.1.0中有关mysql兼容的部分特性

前言 在9月30号&#xff0c;openGauss推出了3.1.0这一预览版&#xff08;注意&#xff0c;openGauss的“x.y.z”版本号&#xff0c;“y”的位置如果不是0&#xff0c;就不是长期支持版&#xff0c;不建议生产使用&#xff09;。 这个版本增加了不少新内容&#xff0c; https:/…

【KGAT】Knowledge Graph Attention Network for Recommendation

note 其实不结合KG&#xff0c;何向南团队之前也直接使用GCN做了NGCF和LightGCN。KGAT结合KG和GAT&#xff0c;首先是CKG嵌入表示层使用TransR模型获得实体和关系的embedding&#xff1b;然后在attention表示传播层&#xff0c;使用attention求出每个邻居节点的贡献权重&#…

35岁有儿有女,为什么我开始自学编程?

零基础编程入门越来越容易 这么讲并不夸张&#xff1a;无论你初学哪门编程语言&#xff0c;第一行代码几乎都是打印出 Hello world ! print(Hello world!) print(Hello python!) 遥想当年&#xff0c;花上一两天折腾完各种安装配置调试环境&#xff0c;写下第一句“面世代码…

该怎么选择副业,三条建议形成自己的副业思维

受经济环境的影响&#xff0c;许多年轻人觉得原来稳定的工作不那么稳定&#xff0c;看着周围的朋友因为企业破产和失业&#xff0c;生活变得没有信心&#xff0c;也想找到自己的副业&#xff0c;在紧急情况下赚更多的钱。所以&#xff0c;年轻人在选择副业时也面临着很多困惑&a…

Java --- JUC的CompletableFuture的使用

目录 一、Future接口 二、Future接口的功能 三、FutureTask 四、CompletableFuture背景及使用 4.1、CompletionStage 4.2、CompletableFuture 4.3、四个静态方法 4.4、减少阻塞和轮询 4.5、使用CompletableFuture完成电商大比价 五、CompletableFuture常用API 5.1、获…

【华为OD机试真题 C++】TLV解析 【2022 Q4 | 100分】

■ 题目描述 TLV编码是按[Tag Length Value]格式进行编码的&#xff0c;一段码流中的信元用Tag标识&#xff0c;Tag在码流中唯一不重复&#xff0c;Length表示信元Value的长度&#xff0c;Value表示信元的值。 码流以某信元的Tag开头&#xff0c;Tag固定占一个字节&#xff0…

机器学习 | 逻辑回归

一.基本原理 面对一个分类问题&#xff0c;建立代价函数&#xff0c;通过优化方法迭代求解出最优的模型参数&#xff0c;然后测试验证我们这个求解的模型的好坏。逻辑回归是一种分类方法&#xff0c;主要用于二分类问题&#xff0c;应用于研究某些事件发生的概率 二.优缺点 …

day28【代码随想录】回溯之组合、组合总和|||、电话号码的字母组合

文章目录前言一、组合&#xff08;力扣77&#xff09;剪枝优化二、组合总和 III&#xff08;力扣216&#xff09;剪枝优化三、电话号码的字母组合&#xff08;力扣17&#xff09;总结前言 1、组合 2、组合总和||| 3、电话号码的字母组合 一、组合&#xff08;力扣77&#xff0…

第1章 计算机组成原理概述

文章目录前言1.0 课程简介1.0.1 课程的地位1.0.2 课程学习思路1.0.3 课程组成1.1 计算机系统简介1.1.1 计算机组成1.计算机的类型2.计算机的组成3.软件组成1.1.2 计算机系统的层次结构1.物理层方面2.程序员角度1.1.3 计算机体系结构与计算机组成1.2 计算机的基本组成1.2.1 冯诺…

esp8266测试1.44英寸TFT屏(驱动7735)的demo

参考这教程: 使用esp8266点亮福利屏型号st7735的1.44的TFT屏 管脚连接&#xff1a; 我的用的TFT1.44寸ST7735&#xff0c;与NodeMCU针脚接线成功连接 VCC——3V GND——G LED——3V CLK——D5 SDI——D7 RS——D6 RST——D4 CS——D8 这里给出常用的屏幕管脚定义 以及esp8266…

女生也能学编程:行政女生转行学编程获13000元薪资

“女生不能学编程” “女生学编程找不到工作” “企业根本不会招女生” …… 这样类似的说法&#xff0c;让非常多的女生放弃了学编程&#xff0c;但达妹今天要明确的说&#xff0c;这种说法是 错误的&#xff01; 只要你愿意改变&#xff0c;有梦想&#xff0c;想追求更好的…

想要快速准备好性能数据?方法这不就来了!

[内部资源] 想拿年薪30W的软件测试人员&#xff0c;这份资料必须领取~ Python自动化测试全栈性能测试全栈&#xff0c;挑战年薪40W 性能测试的一般流程 收集性能需求——>编写性能脚本——>执行性能测试——>分析测试报告——>系统性能调优。 在收集性能需求后…

Spring IOC\AOP\事务\注解

DAY1 一、引言 1.1 原生web开发中存在哪些问题&#xff1f; 传统Web开发存在硬编码所造成的过度程序耦合&#xff08;例如&#xff1a;Service中作为属性Dao对象&#xff09;。 部分Java EE API较为复杂&#xff0c;使用效率低&#xff08;例如&#xff1a;JDBC开发步骤&…

17. 【gRPC系列学习】http2 各类型帧的含义

本节介绍http2有哪些类型的帧以及各帧的主要作用,是rfc7540规范标准定义,文末有参考链接,为后续介绍gRPC帧处理做技术储备。 1. 帧结构 帧长度3个字节 24 bit帧类型1个字节,含义如下:FrameData FrameType = 0x0FrameHeaders FrameType = 0x1FramePriority …

MySQL#4(JDBC常用API详解)

目录 一.简介 1.概念 2.本质 3.优点 4.步骤 二.API详解 1.DriverManager(驱动管理类) 2.Connection 3.Statement 4.ResultSet 5.PreparedStatement 一.简介 1.概念 JDBC就是使用Java语言操作关系型数据库的一套API(Java DataBase Connectivity)Java 数据库连接 2.本…

年货节微信活动有哪些_分享微信小程序商城开发好处

新年临近&#xff0c;又是百姓们囤年货的日子。各行业的微商商城或者线下实体店的商家们&#xff0c;趁此机会别&#xff0c;做一波优惠促销活动&#xff0c;今年的业绩就靠它来个完美的收尾啦&#xff01; 1.类型&#xff1a;转盘拆福袋等抽奖活动 点击对应抽奖按钮&#xff0…

Doo Prime 提供高达 1000 倍杠杆,助您撬动无限机遇

2022 年 11 月 19 日&#xff0c;Doo Prime 正式将全部账户类型的可选杠杆从 1:500 上调至 1:1000 倍&#xff0c;提供更灵活的杠杆选择&#xff0c;让全球客户有机会以更少的资金撬动更高的潜在利润&#xff0c;进一步拓展投资机遇。 *备注&#xff1a;杠杆调整详情请参阅下文…

Sentinel系列——概述与安装1-1

Sentinel系列——概述与安装1-1概述服务雪崩解决方法基本概念资源规则Sentinel 是如何工作的安装Sentinel下载地址启动修改sentinel启动参数设置启动端口设置用户名密码概述 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言…

面试官问 Redis 的数据结构的时候怎么答能加分?

一提到 Redis&#xff0c;我们的脑子里马上就会出现一个词&#xff1a;“快。”但是你有没有想过&#xff0c;Redis 的快&#xff0c;到底是快在哪里呢&#xff1f;实际上&#xff0c;这里有一个重要的表现&#xff1a;它接收到一个键值对操作后&#xff0c;能以微秒级别的速度…

【代码随想录】鱼与钰遇雨数据结构与算法刷题笔记

代码随想录 https://www.programmercarl.com/ 编程素养 代码风格 C代码风格参考google C style Guide 我使用的是驼峰命名法&#xff0c;其中空格留白的规则如下例&#xff1a; class Solution { public:void moveZeroes(vector<int>& nums) {int slowIndex 0…