Pytorch实现图像风格迁移(一)

news2025/1/17 6:18:45

图像风格迁移是图像纹理迁移研究的进一步拓展,可以理解为针对一张风格图像和一张内容图像,通过将风格图像的风格添加到内容图像上,从而对内容图像进行进一步创作,获得具有不同风格的目标图像。基于深度学习网络的图像风格迁移主要有三种类型,分别为固定风格固定内容的风格迁移、固定风格任意内容的快速风格迁移和任意风格任意内容的极速风格迁移。

图像风格迁移主要任务是将图像的风格迁移到内容图像上,使得内容图像也具有一定的风格。其中风格图像通常可以是艺术家的一些作品,如画家梵高的《向日葵》《星月夜》,日本浮世绘的《神奈川冲浪里》等经典的画作,这些图像通常包含一些经典的艺术家风格。风格图像也可以是经典的具有特色的照片,如夕阳下的照片、城市的夜景等,图像具有鲜明色彩图像。而内容图像则通常来自现实世界,可以是自拍照、户外摄影等。利用图像风格迁移则可以将内容图像处理为想要的风格。

1.固定风格固定内容的普通风格迁移

固定风格固定内容的风格迁移方法,也可以称为普通图像风格迁移方法,也是最早的基于深度卷积神经网络的图像风格迁移方法。针对每张固定内容图像和风格图像,普通图像风格迁移方法都需要重新经过长时间的训练,这是最慢的方法,也是最经典的方法。固定风格固定内容的风格迁移方法思路很简单,就是把图片当作可以训练的变量,通过不断优化图片的像素值,降低其与内容图片的内容差异,并降低其与风格图片的风格差异,通过对卷积网络的多次迭代训练,能够生成一幅具有特定风格的图像,并且内容与内容图片的内容一致,生成图片风格与风格图片的风格一致。

 上图是论文《Image Style Transfer Using Convolutional Neural Networks》中,提到的基于VGG16网络中卷积层的图像风格迁移流程。在图中左边的图像 \vec{a\mathbf{}} 为输入的风格图像,右边的图像\vec{p} 为输入的内容图像。中间的图像 \vec{x} 则是表示由随机噪声生成的图像风格迁移后的图像。\iota _{content}表示图像的内容损失,\iota _{style}  表示图像的风格损失,\alpha 和 \beta 分别表示内容损失权重和风格损失权重。
针对深度卷积神经网络的研究发现,使用较深层次的卷积计算得到的特征映射能够较好地表示图像的内容,而较浅层次的卷积计算得到的特征映射能够较好地表示图像的风格。基于这样的思想就可以通过不同卷积层的特征映射来度量目标图像在风格上和风格图像的差异,以及在内容上和内容图像的差异。
两个图像的内容相似性度量主要是通过度量两张图像在通过VGG16的卷积计算后,在conv4_2层上特征映射的相似性,作为图像的内容损失,内容损失函数如下所示:

\iota _{content}=\frac{1}{2}\sum_{i,j}^{}\left ( F_{ij}^{l}-P_{ij}^{l} \right )^{2}

式中,l 表示特征映射的层数; F 和 P 分别是目标图像和内容图像在对应卷积层输出的特征映射。
图像风格的损失并不是直接通过特征映射进行比较的,而是通过计算Gram矩阵先计算出图像的风格,再进行比较图像的风格损失。计算特征映射的Gram矩阵则是先将其特征映射变换为一个列向量,而Gram矩阵则使用这个列向量乘以其转置获得,Gram矩阵可以更好地表示图像的风格。所以输入风格图像 \vec{a} 和目标图像 \vec{x} ,使用 A^{l} 和 G^{l} 分别表示它们在 l 层特征映射的风格表示(计算得到的Gram矩阵),那么图像的风格损失可以通过下面的方式进行计算:
 

 式中,w_{l} 是每个层的风格损失的权重; N_{l} 和 M_{l} 对应着特征映射的高和宽。针对固定图像固定风格的图像风格迁移,使用PyTorch很容易实现。后续小节将介绍如何使用PyTorch进行固定图像固定风格的图像风格迁移。

2.固定风格任意内容的快速风格迁移

固定风格任意内容的快速风格迁移,是在固定风格固定内容的图像风格迁移的基础上,做出的一些必要改进,即在普通图像风格迁移的基础上,添加一个可供训练的图像转换网络。针对一种风格图像进行训练后,可以将任意输入图像非常迅速地进行图像迁移学习,让该图像具有学习好的图像风格。其深度网络的框架如下:

 上图来自论文《Perceptual Losses for Real-Time Style Transfer and Super-Resolution》图示可以看作两个部分,一部分是通过输入图像 x 经过图像转换网络 f_{w},得到网络的输出 \hat{y},这部分是普通风格迁移图像框架中没有的部分,普通图像风格迁移的输入图像是随机噪声,而快速风格迁移的输入是一张图像经过转换网络 f_{w} 的输出;另一部分是使用VGG16网络中的相关卷积层去度量一张图像的内容损失和风格损失。
在图像转换网络( Image Transform Net)部分,可以分为3个阶段,分别是图像降维部分、残差连接部分和图像升维部分。
(1)图像降维部分:主要通过3个卷积层来完成,将图像的尺寸从256×256逐渐缩小到原来的1/4,即64×64,并且将通道数逐渐从3个增加到128个特征映射。
(2)残差连接部分:该部分是通过连接5个残差块,对图像进行学习,该结构用于学习如何在原图上添加少量内容,改变原图的风格。其中每个残差连接的结构如图所示:

(3)图像升维部分:该部分主要输出5个残差单元,通过3个卷积层的操作,逐渐将其通道数从128缩小到3,每个特征映射的尺寸从64×64放大到256 ×256,也可以使用转置卷积来完成网络的升维部分。

1.准备VGG19网络

从torchvision的models模块中导入预训练好的VGG19网络,预训练好的网络是在ImageNet数据集上进行训练的,所以使用时会非常方便。因为VGG19网络的作用是计算对应图像在网络中一些层输出的特征映射,在计算过程中,不需要更新VGG19的参数权重,所以导入VGG19网络后,需要将其中的权重冻结,程序如下所示:

import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
import torch
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
import torch.optim as optim
import requests
from torchvision import models
from torchvision import transforms
import time
import hiddenlayer as hl
from skimage.io import imread

vgg19=models.vgg19(pretrained=True)
vgg=vgg19.features
for param in vgg.parameters():
    param.requires_grad_(False)

在上面的程序中,使用models.vgg19(pretrained=True)读取已经预训练的VGG19网络,参数pretrained=True表示读取的网络是已经预训练过的。预训练过的网络可以直接进行相关的应用。因不需要网络中的分类器相关的层,所以使用vgg19.features即可获取网络中由卷积和池化组成的特征提取层。在冻结网络的权重时,通过一个循环来遍历网络中所有可以训练的权重,然后通过requires_grad_(False)方法,设置权重在接下来的计算中不更新梯度,即权重不更新。

准备好VGG19网络之后,还需要对输入的图像进行相关的处理,因为网络可以接受任意尺寸的输入图像(图像的尺寸不宜过小,预防深层的卷积操作后没有特征映射输出,或特征映射尺寸太小),但是图像的尺寸越大,在进行风格迁移时,需要进行的计算量就会越多,速度就会越慢,所以需要保持图像有合适的尺寸。虽然图像风格迁移时可以使用任意尺寸的图像,而且输入的风格图像的尺寸和内容图像的尺寸大小也可以不相同(在实际应用中为了方便,通常会将风格图像的尺寸和内容图像的尺寸设置为相同),但目标图像尺寸和内容图像的尺寸需要相同,这样才能计算和比较内容损失的大小。下面定义load_image()函数用于读取图像,在读取图像的同时,控制图像的尺寸大小,程序如下所示:

def load_image(img_path,max_size=400,shape=None):
    image=Image.open(img_path).convert('RGB')
    #如果图片尺寸过大,就对图像进行尺寸变换
    if max(image.size) > max_size:
        size=max_size
    else:
        size=max(image.size)
    #如果指定了图像的尺寸,就像图像转化为shape指定的尺寸
    if shape is not None:
        size=shape
    #使用transform将图像转化为张量,并进行标准化
    in_transform=transforms.Compose([
        transforms.Resize(size),#图像尺寸变换,图像的短边匹配size
        transforms.ToTensor(),#数组转化为张量
        #图像进行标准化
        transforms.Normalize((0.485,0.456,0.406),(0.229,0.224,0.225))
    ])
    image=in_transform(image)[:3,:,:].unsqueeze(dim=0)
    return image

load_image()函数有三个参数,第一个参数是输入需要读取图像的路径img_path,第二个参数和第三个参数用于控制图像的大小。如果指定了max_size参数,在读取图像时,若图像的尺寸过大,图像会进行相应的缩小,如果指定了图像的尺寸( shape参数),则将图像转化为shape指定的大小。读取图像后,为了方便通过卷积网络计算相关的特征输出,使用transforms的相关转换操作,对图像进行预处理,最后将输出一个可以使用的四维张量image。上述读取后的图像并不能通过matplotlib库直接进行可视化,需要定义一个im_convert()函数,该函数可以将一张图像的四维张量转化为一个可以使用matplotlib库可视化的三维数组,程序如下所示: 

def im_convert(tensor):
    """
    将[1,c,h,w]维度的张量转化为[h,w,c]的数组
    因为张量进行了标准化,所以要进行标准化逆变换
    :param tensor:
    :return:
    """
    image=tensor.data.numpy().squeeze()#去除batch维度数据
    image=image.transpose(1,2,0)#置换数组的维度[c,h,w]->[h,w,c]
    #进行标准化的逆操作
    image=image * np.array((0.229,0.224,0.225))+ np.array((0.485,0.456,0.406))
    image=image.clip(0,1)#将图像的取值剪切到0-1
    return image

下面的程序将读取需要使用的风格图像和内容图像,并将它们可视化。

content=load_image(r"C:\Users\zex\Desktop\sky.jpg",max_size=400)
print("content shape:",content.shape)
#根据内容图像的宽高来设置风格图像的宽高
style=load_image(r"C:\Users\zex\Desktop\fangao.png",shape=content.shape[-2:])
print("style shape:",style.shape)
#可视化图像,可视化内容图像和风格图像
fig,(ax1,ax2)=plt.subplots(1,2,figsize=(12,5))
ax1.imshow(im_convert(content))
ax1.set_title("content")
ax2.imshow(im_convert(style))
ax2.set_title("style")
plt.show()

 运行上述程序得到如下所示的输出和如下图所示的图像。为了保证两张图像具有相同的大小,在程序中通过内容图像的尺寸来定义风格图像的尺寸。

 2.图像的输出特征和Gram矩阵的计算

为了更方便获取图像在VGG19网络指定层上的特征映射输出,定义一个get_features()函数,程序如下所示:

def get_features(image,model,layer=None):
    """
    将一张图像image在一个网络model中进行前向传播计算,并获取指定层layer中特征映射输出
    :param image:
    :param model:
    :param layer:
    :return:
    """
    #将PyTorch的VGGNet的完整映射层名称与论文中的名称相对应
    #layers参数指定:需要用于图像的内容和样式表示的图层
    ##如果layers没有指定、就使用默认的层
    if layer is None:
        layers={
            '0':'conv1_1',
            '5':'conv2_1',
            '10':'conv3_1',
            '19':'conv4_1',
            '21':'conv4_2',
            '28':'conv5_1'
        }
    features={}#获取的每层特征保存到字典中
    x=image#需要获取的特征图像
    #model._modules是一个字典,保存着网络model每层信息
    for name,layer in model._modules.items():
        #从第一层开始获取图像的特征
        x=layer(x)
        #如果是layers参数指定的特征,那就保存到features中
        if name in layers:
            features[layers[name]]=x
        return features
    

get_features()函数通过输人参数图像(image),使用网络(model)和指定的层参数( layers),输出图像在指定网络层上的特征映射,并将输出的结果保存在一个字典中,如指定VGG19网络,但不指定layers参数,默认情况下会输出VGG19网络的conv1_1、conv2_1、conv3_1、conv4_1、conv4_2、conv5_1层的特征映射。比较两个图像是否具有相同的风格时,可以使用Gram矩阵来评价。我们定义函数gram_matrix()对一张图像的特征映射输出计算Gram矩阵。

def gram_matrix(tensor):
    """
    计算指定向量的Gram matrix,该矩阵表示图像的风格特征
    格拉姆矩阵最终能够在保证内容的情况下,进行风格传输
    tensor:是一张图像前向计算后的一层特征映射
    :param tensor:
    :return:
    """
    #获得tensor的batch_size,depth,height,width
    _,d,w,h=tensor.size()
    #改变矩阵的维度为(深度,高*宽)
    tensor=tensor.view(d,h*w)
    #计算gram matrix
    gram=torch.mm(tensor,tensor.t())
    return gram

 在上面定义的gram_matrix()函数是计算一张图像Gram矩阵,针对输入的四维特征映射,将其每一个特征映射设置为一个向量,得到一个行为d(特征映射数量),列为h* w(每个特征映射的像素数量)的矩阵,该矩阵乘以其转置即可得到需要的Gram矩阵。
在定义好两个辅助函数后,下面针对内容图像和风格图像计算特征输出,并且计算风格图像在每个特征输出上的Gram矩阵,程序如下所示:

#计算在第一次训练之前内容特征和风格特征,使用get_features函数
content_features=get_features(content,vgg)
#计算风格图像的风格表示
style_features=get_features(style,vgg)
#为风格图像的风格表示计算每层的格拉姆矩阵,使用字典保存
style_grams={layer: gram_matrix(style_features[layer]) for layer in style_features}
#使用内容图像的副本创建一个目标图像,训练时对目标图像进行调整
target=content.clone().requires_grad_(True)

3.进行图像风格迁移

在相关准备工作做好之后,下面就可以使用相关图像和网络进行图像风格迁移的学习,为了训练效果,在计算风格时,针对不同层的风格特征映射Gram矩阵,定义不同大小的权重,此处使用style_weights字典法完成,并且针对最终的损失,内容损失权重α和风格损失权重β分别定义为1和1×10^{6},程序如下所示:

style_weights={'conv1_1':1.,
               'conv2_1':0.75,
               'conv3_1':0.2,
               'conv4_1':0.2,
               'conv5_2':0.2}
alpha=1
beta=1e6
content_weight=alpha
style_weight=beta

需要注意的是,在style_weights中没有定义conv4_2层的Gram权重,这是因为该层的特征映射用于度量图像内容的相似性。
定义好权重参数后,下面使用Adam优化器进行训练,其中学习率为0.0003,并且为了监督网络在训练过程中的结果,每间隔1000次迭代输出目标图像的可视化情况,用于观察,并将迭代过程中每次相关损失值保存在列表中。用于优化目标图像的程序如下所示:

show_every=1000#每迭代1000次输出一个中间结果
#将损失保存
total_loss_all=[]
content_loss_all=[]
style_loss_all=[]
#使用Adam优化器
optimizer=optim.Adam([target],lr=0.0003)
steps=5000#优化时迭代的次数
t0=time.time()#记录需要的时间
for i in range(steps):
    #获取目标图像的特征
    target_features=get_features(target,vgg)
    #计算内容损失
    content_loss=torch.mean((target_features["conv4_2"] - content_features["conv4_2"])**2)
    #计算风格损失,并且初始化为0
    style_loss=0
    #将每层的gram_matrix损失相加
    for layer in style_weights:
        #计算要生成的图像风格表示
        target_feature = target_features[layer]
        target_gram=gram_matrix(target_feature)
        _,d,h,w=target_feature.shape
        #获取风格图像在每层的风格的gram_matrix
        style_gram=style_grams[layer]
        #计算要生成图像的风格和风格图像的风格之间的差距,每层都有一个权重
        layer_style_loss=style_weights[layer] * torch.mean((target_gram-style_gram)**2)
        #累加计算风格差异损失
        style_loss +=layer_style_loss/(d*h*w)
    #计算一次迭代的总的损失,即内容损失和风格损失的加权和
    total_loss=content_weight * content_loss + style_weight * style_loss
    #保留三种损失大小
    content_loss_all.append(content_loss.item())
    style_loss_all.append(style_loss.item())
    total_loss_all.append(total_loss.item())
    #更新需要生成的目标图像
    optimizer.zero_grad()
    total_loss.backward()
    optimizer.step()
    #输出每show_every次迭代后的生成图像
    if i % show_every==0:
        print('Total loss:',total_loss.item())
        print('Use time:',(time.time()-t0)/3600,'hour')
        newIm=im_convert(target)
        plt.imshow(newIm)
        plt.title("Iteration:"+str(i)+'times')
        plt.show()
        result=Image.fromarray((newIm * 255).astype(np.uint8))
        result.save('C:\\Users\\zex\\Desktop\\' +str(i)+'.bmp')

在上面的程序中还需要注意以下几点:
(1)优化器的使用方式为optim.Adam([target], lr=0.0003),表明在优化器中,最终要优化的参数是目标图像的像素值,不会优化VGG网络中的权重等参数。
(2)获取目标图像在相关层的特征输出时使用get_features(target, vgg)函数,并且因为内容图像的特征映射在conv4_2层,所以内容损失计算时,需提取指定层的输出,即使用target_features['conv4_2']获得目标图像的内容表示,以及使用content__features['conv4_2']获得内容图像的内容表示。
(3)由于图像的风格表示的损失是通过多个层来表示,所以需要通过for循环来逐层计算相关的Gram矩阵和风格损失。
(4)最终的损失是风格损失和内容损失的加权和。
(5)为了观察和保留图像风格在迁移过程中的结果,将图像每间隔1000次迭代计算后的结果进行可视化并保存到指定的文件中。
由于以上程序训练时间十分漫长,这也是普通图像风格迁移方法的最大缺点,因此最终结果这里就不展示了。下一节课介绍快速图像风格迁移方法。

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

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

相关文章

玩转Fastdfs

FastDFS FastDFS是一个开源的轻量级分布式文件系统。它解决了大数据量存储和负载均衡等问题。特别适合以中小文件&#xff08;建议范围&#xff1a;4KB < file_size <500MB&#xff09;为载体的在线服务&#xff0c;如相册网站、视频网站等等 特性 文件不分块存储&am…

fast-lio2代码解析

代码结构很清晰&#xff0c;从最外层看包含两个文件夹&#xff0c;一个是fast-lio,另外一个是加上scan-context的回环检测与位姿图优化。 fast-lio 主要是论文的fast-lio2论文的实现&#xff0c;包括前向处理和ikd-tree的实现 1.先从cmakelist入手看代码结构&#xff1a; #这…

瑞芯微RK3568核心板强在何处?

RK3568核心板产品简介 RK3568核心板是武汉万象奥科基于瑞芯微Rockchip的RK3568设计的一款高性能核心板。该处理器集成了最新的高性能CPU、GPU&#xff0c;并拥有丰富的接口&#xff0c;非常适用于工业自动化控制、人机界面、中小型医疗分析器、电力等多种行业应用。 HD-RK3568-…

【Python】Json读写操作_JsonPath用法详解

【Python】Json读写操作_JsonPath用法详解 文章目录【Python】Json读写操作_JsonPath用法详解1. 介绍2. 代码示例3. 参考1. 介绍 JSONPath是一种信息抽取类库&#xff0c;是从JSON文档中抽取指定信息的工具&#xff0c;提供多种语言实现版本&#xff0c;包括Javascript、Pytho…

【dp】不同的子序列 两个字符串的删除操作 编辑距离

115. 不同的子序列 dp[i][j]&#xff1a;以j-1为结尾的t出现在以i-1为结尾的s子序列的个数 需要开辟m1行&#xff0c;n1列的二维数组 为啥状态方程是&#xff1a; s[i] t[j] 时 dp[i][j] dp[i-1][j-1] dp[i-1][j] s[i] ! t[j] 时 dp[i][j] dp[i-1][j] 先看s[i] t[j] 时…

GDI+下字体大小自适应方案初探

在某个瞬间&#xff0c;我忽然发觉&#xff0c;三体或是AI&#xff0c;本质上是非常相近的事物&#xff0c;甚至在面对任何未知领域的时候&#xff0c;人类总会不自觉地划分为降临派、拯救派和幸存派。姑且不论马斯克等人叫停 GPT-5 的真实动机如何&#xff0c;当大语言模型(LL…

JMU Oracle实验四

用来记录实验操作的 spool E:\oracle_record\record20230406.txt ... spool off老师问的问题 让我展示了一下open_cursor的alter操作问我怎么查看spfile文件&#xff0c;实例&#xff0c;会话的参数内容就这两个 1. 采用不同的方法查询Oracle数据库当前使用的初始化参数文件…

仅三行就能学会数据分析——Sweetviz详解

文章目录前言一、准备二、sweetviz 基本用法1.引入库2.读入数据3.调整报告布局总结前言 Sweetviz是一个开源Python库&#xff0c;它只需三行代码就可以生成漂亮的高精度可视化效果来启动EDA(探索性数据分析)。输出一个HTML。 如上图所示&#xff0c;它不仅能根据性别、年龄等…

PHP 调用百度人脸对比

本文章主要介绍人脸对比API能力、应用场景、请求实例、参数说明。 接口能力 两张人脸图片相似度对比&#xff1a;比对两张图片中人脸的相似度&#xff0c;并返回相似度分值。 多种图片类型&#xff1a;支持生活照、证件照、身份证芯片照、带网纹照四种类型的人脸对比。 活体检测…

redis双写一致问题场景及方案

产生问题的场景 写入数据库后立即更新缓存&#xff08;较常见&#xff09; 这种场景下 问题产生的主要原因是写入数据库与更新缓存非原子性 有延迟 所以这样会导致谁更新缓存慢 谁会真正的更新缓存 更新数据库后立即删除缓存 查询时再插入缓存 与上一场景类似 虽然写入数据库…

C#,码海拾贝(18)——矩阵的(一般)三角分解法(Triangular Decomposition)之C#源代码,《C#数值计算算法编程》源代码升级改进版

1 三角分解法 Triangular Decomposition 三角分解法亦称因子分解法&#xff0c;由消元法演变而来的解线性方程组的一类方法。设方程组的矩阵形式为Axb&#xff0c;三角分解法就是将系数矩阵A分解为一个下三角矩阵L和一个上三角矩阵U之积&#xff1a;ALU&#xff0c;然后依次解…

Vue——模板引用

目录 访问模板引用​ v-for 中的模板引用​ 函数模板引用​ 组件上的 ref​ 虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作&#xff0c;但在某些情况下&#xff0c;我们仍然需要直接访问底层 DOM 元素。要实现这一点&#xff0c;我们可以使用特殊的 ref att…

Vue3技术3之setup的两个注意点、computed计算属性

Vue3技术3setup的两个注意点Vue2中的一些知识点App.vueDemo.vuesetup的两个注意点第一个注意点App.vueDemo.vue第二个注意点App.vueDemoTwo.vue总结computed计算属性App.vueDemo.vue总结setup的两个注意点 Vue2中的一些知识点 App.vue <template><div><h1>…

CnOpenData制造业单项冠军企业工商注册基本信息数据

一、数据简介 2016年3月&#xff0c;工信部印发《制造业单项冠军企业培育提升专项行动实施方案》&#xff0c;方案指出&#xff1a;“到2025年&#xff0c;总结提升200家制造业单项冠军示范企业&#xff0c;发现和培育600家有潜力成长为单项冠军的企业”。截至2022年&#xff0…

工程行业管理系统-专业的工程管理软件-提供一站式服务

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示…

基于Python的简单40例和爬虫详细讲解(文末赠书)

目录 先来看看Python40例 学习Python容易坐牢&#xff1f; 介绍一下什么是爬虫 1、收集数据 2、爬虫调研 3、刷流量和秒杀 二、爬虫是如何工作的&#xff1f; 三、爬虫与SEO优化 什么是python爬虫 Python爬虫架构 最担心的问题 本期送书 随着人工智能以及大数据的兴起…

《JavaEE》HashTable、HashMap、ConcurrentHashMap

目录 HashTable HashMap ConcurrentHashMap ​编辑 HashTable与ConcurrentHashMap的区别 &#x1f451;作者主页&#xff1a;Java冰激凌 &#x1f4d6;专栏链接&#xff1a;JavaEE 进入到线程模块 必不可少的就是接触到线程安全的数据结构 例如StringBuffer、BlockingQueu…

计网第五章.运输层—TCP的拥塞控制

以下来自湖科大计算机网络公开课笔记及个人所搜集资料 目录一、拥塞控制与流量控制1.1 拥塞控制的目的1.2 区分拥塞控制与流量控制二、四种拥塞控制算法2.1 慢开始和拥塞避免2.2 快重传2.3 快恢复一、拥塞控制与流量控制 1.1 拥塞控制的目的 先看一下什么是拥塞&#xff1a; …

AVL树介绍

AVL树AVL树的概念AVL树结点的定义AVL树的插入AVL树的旋转&#xff08;1&#xff09;左单旋&#xff08;2&#xff09;右单旋&#xff08;3&#xff09;左右双旋&#xff08;4&#xff09;右左双旋AVL树的验证AVL树的性能AVL树的概念 二叉搜索树虽然可以提高我们查找数据的效率…

第三章 Linux实际操作——vi和vim编辑器

第三章 Linux实际操作——vi和vim编辑器3.1 vi和vim的基本介绍3.2 vi和vim常用的三种3.2.1 正常模式3.2.2 插入模式3.2.3 命令行模式3.3 vi和vim基本使用3.4 各种模式的相互切换3.5 vi和vim的快捷键3.1 vi和vim的基本介绍 Linux系统会内置 vi文本编辑器Vim具有程序编辑的能力&…