JPEG编码原理及简易编码器实现

news2024/11/26 0:52:00

简介

以学习为目的编写的简易jpeg编码器,以看得懂为目标,代码尽可能清晰简洁,不对内存、性能做看不懂的优化,也不实现jpeg更多高级特性。

这篇文章是我从自己的开源工程中整理来的
本文对应的工程为https://gitee.com/dma/learn-jpeg-encode

JPEG 编码原理

网上相关的文章非常多,本文只挑重点进行解说

基本流程
色域转换 -> DCT变换 -> 量化 -> Huffman编码

色域转换

为什么要做色域转换?因为人眼的视杆细胞(对亮度敏感)的数量远多于视锥细胞(对颜色敏感),因此适当压缩颜色信息可以有效减少数据量。有关亮度和颜色的色域有YUV、HSL、HSV,为什么要用YUV?这个我真的不知道。

废话不多说了,这是整个JPEG编码过程中最简单的一个步骤,具体不详细展开了,网上相关内容太多了。
只说最重要的,色域的标准有BT601,BT656,BT709等,那么JPEG用的是哪一个?参考《ITU-T T.871》第7节“Conversion to and from RGB”,这里说的很清楚用的是BT601,但是和BT601有一点区别,BT601的YUV取值范围是16到235,JPEG中的取值范围是0到255。

书中给出的公式如下
在这里插入图片描述
如果在代码中使用这个公式转换色域最终编码出来的JPEG图片颜色会出问题

luma = 0.299f   * r + 0.587f  * g + 0.114f  * b;
cb   = -0.1687f * r - 0.3313f * g + 0.5f    * b + 128.0f;
cr   = 0.5f     * r - 0.4187f * g - 0.0813f * b + 128.0f;

使用这个公式才能得到正确的结果

luma = 0.299f   * r + 0.587f  * g + 0.114f  * b - 128;
cb   = -0.1687f * r - 0.3313f * g + 0.5f    * b;
cr   = 0.5f     * r - 0.4187f * g - 0.0813f * b;

查看 《ITU-T T.81》 的 A.3.1 节“Level shift”,这里提到数据需要 -2^(P-1) 的偏移,也就是说8位数据要减去128

DCT变换

这里还是要稍微介绍一下DCT变换
DCT变换的关键是,它假设任何数字信号都可以用余弦函数的组合来重建。
如图所示,可以看到它实际上是 cos(x)+cos(2x)+cos(4x) 的和
在这里插入图片描述
在这里插入图片描述
将它扩展到二维就可以对图像进行处理。其思想是,任何8x8块可以表示为不同频率上加权余弦变换的和。

换句话说任何8*8的图像都可以由这64幅图像乘以不同的系数并叠加而得到。
在这里插入图片描述

如何得到这幅图像?(这一段是我自己总结的,暂未找到相关文章,实际按这个方法生成的结果有比较大的误差,希望有数学大佬指点一下)
设Xij为一个8*8矩阵,其中ij表示行列下标
将第ij行列的元素置为1,其余元素置0,对得到这个矩阵进行DCT转换,就可得到对应的一个图像
重复上述过程直到完成全部64个元素对应的图像
在这里插入图片描述

来看这个实际演示,右边是原始图像,中间是原始图像乘以系数后的图像,左边是最终叠加的结果
在这里插入图片描述

想要了解有关DCT的更多知识或深入学习DCT原理请积极发挥主观能动性
这里直接给出二维DCT转换的公式
在这里插入图片描述
在这里插入图片描述
将C(u)前面的系数“2/N”乘进去可以得到
在这里插入图片描述
在这里插入图片描述
这两个公式是完全一样的,代码中用的是第二个公式

看不懂没关系,简单解释一下这几个符号:

  • Ymn是DCT转换后第m行第n列的结果
  • Xij是输入数据的第m行第n列的值
  • Cm和Cn是系数
  • N为DCT的大小,JPEG以88一块为单位进行处理
    这个公式做的事情很简单,就是求和而已,据此我们可以写出一个非常原始且性能很差的DCT变换(4层循环啊!!性能有多差用这个代码压缩一张1920
    1080就知道了)
#define DCT_SIZE 8

double ck(int32_t k)
{
    if (k == 0) {
        return sqrt(1.0 / DCT_SIZE);
    } else {
        return sqrt(2.0 / DCT_SIZE);
    }
}

void block_dct(const int8_t *in, double *out)
{
    int32_t i, j, n, m;
    double sum = 0.0;
    for (m = 0; m < DCT_SIZE; m++) {
        for (n = 0; n < DCT_SIZE; n++) {
            for (i = 0; i < DCT_SIZE; i++) {
                for (j = 0; j < DCT_SIZE; j++) {
                    sum += in[i * DCT_SIZE + j] * cos((2 * j + 1) * n * M_PI / (2 * DCT_SIZE)) * cos((2 * i + 1) * m * M_PI / (2 * DCT_SIZE));
                }
            }
            out[m * DCT_SIZE + n] =  sum * ck(m) * ck(n);
            sum = 0.0;
        }
    }
}

如果将这个DCT算法改为快速DCT算法,并将浮点运算改为整数运算,性能将得到大幅度提升,这不在本文讨论范围内。

量化

首先我们需要一个量化表,这是《ITU-T T.81》中的亮度分量量化表,JPEG的标准并没有规定一定要使用这个量化表,只不过大多数JPEG编码器都把这张表当做默认的量化表来用。

const uint8_t default_luma_table[] =
{
    16, 11, 10, 16,  24,  40,  51,  61,
    12, 12, 14, 19,  26,  58,  60,  55,
    14, 13, 16, 24,  40,  57,  69,  56,
    14, 17, 22, 29,  51,  87,  80,  62,
    18, 22, 37, 56,  68, 109, 103,  77,
    24, 35, 55, 64,  81, 104, 113,  92,
    49, 64, 78, 87, 103, 121, 120, 101,
    72, 92, 95, 98, 112, 100, 103,  99,
};

jpeg 有一个质量因数的参数,取值为1到100,其中1是最差,50为默认值,100为最好。
然后使用这个公式算出一个中间变量(这个公式的出处我暂时没要找到,但是有关JPEG编码的文章都会讲到这个公式)

if (qt < 50) {
    alpha = 50.0f / qt;
}
else {
    alpha = 2.0f - qt / 50.0f;
}

用 alpha 分别乘以上面量化表中的每一个成员并将结果限制在1-255范围之内,得到的就是终要使用的量化表。

有了量化表下一步进行量化,之前步骤中对数据的转化都是无损的,量化这一步才会真正导致图像数据损失
假设我们有这样一组亮度分量的数据

694 -169   1 -41  -9 -16 -10   4 
134 -37  -64  33 -11   2   5  -5 
  9  59  -15 -21  27   7  -4  -2 
 14   5   16 -20  -7   4   1   2 
  5   9    0   8 -11  19  20   6 
  8   5    6  -1   8 -12   3   8 
-18 -14  -14 -16 -21   1   3   1 
  3  -4   -7  -7  -8 -19  -6   8 

将亮度数据分别除以亮度量化表中的每一项,就会得到最终的量化结果

 43 -15   0  -3   0   0   0   0 
 11  -3  -5   2   0   0   0   0 
  1   5  -1  -1   1   0   0   0 
  1   0   1  -1   0   0   0   0 
  0   0   0   0   0   0   0   0 
  0   0   0   0   0   0   0   0 
  0   0   0   0   0   0   0   0 
  0   0   0   0   0   0   0   0 

在进行编码前的数据要进行Z字扫描,简单来说Z自扫描的目的是尽量将非0数据集中在一起,便于下一步的压缩,Z字扫描的顺序如图所示
在这里插入图片描述
这个步骤很简单,略过,最终的结果是这样的

 43 -15  11   1  -3   0  -3  -5 
  5   1   0   0  -1   2   0   0 
  0  -1   1   0   0   0   0   0 
 -1   1   0   0   0   0   0   0 
  0   0   0   0   0   0   0   0 
  0   0   0   0   0   0   0   0 
  0   0   0   0   0   0   0   0 
  0   0   0   0   0   0   0   0 

编码

JPEG 图像数据的编码分为两个部分,码字的长度编码和数值编码。其中对码字的长度编码才是要进行Huffman编码的部分,对数值编码只是一个简单的数学运算。
图像的数据基本都是由“编码后的码长+码字”组成的,假设码字为值为 1100011,码长为7,7对应的Huffman编码为 10,最终的数据是 101100011。

数值编码

数值编码的方法很简单,正数就是它本身,负数是它绝对值的反码,
正数的码长就是左起第一个不为0的位到结束的总位数,负数的码长就对应正数的码长,
例如
+123 = (补码)0x7B = 1111011 码长7
-123 = 绝对值的反码 = 0000100 码长7

构建Huffman编码树

JPEG中使用的Huffman编码并和平常所说的Huffman编码不同,而是范式Huffman编码(canonical Huffman code),Huffman编码和范式Huffman编码详细概念不再展开,下面重点将如何根据JPEG中的Huffman表构建Huffman编码树。

JPEG的标准并没有提供默认的Huffman编码表,实际编码时可以根据图像本身构建Huffman编码表来最大限度地压缩数据。只不过一般情况下大家都不会去构建编码表,而是直接拿这张表来用。
这是《ITU-T T.81》中的亮度交流分量Huffman码长度表,一共16个数据,从第一个到第十六个依次表示码长为N的Huffman码个数

const uint8_t default_ht_luma_ac_len[16] =
{
    0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d
};

例如上面这张表就是说码长为1的编码有0个,码长为2的编码有2个,码长为3的编码有1个……码长为16的编码有125个,加起来一共有162个编码

这是《ITU-T T.81》中的亮度交流分量Huffman表,也就是Huffman编码前的数据

const uint8_t default_ht_luma_ac[162] =
{
    0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
    0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0,
    0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28,
    0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
    0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
    0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
    0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
    0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5,
    0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2,
    0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
    0xF9, 0xFA
};

下面将如何构建Huffman编码树,网上很多文章都没能讲明白,看完还是一脸懵逼,我自己也花了很多精力才弄明白
流程是这样的:

  • 建立根节点
  • 为每一个节点建立两个分支,并标记为左0右1,同时树的高度(也就是码长)+1
  • 如果当前高度有编码,则从树的最左边一个节点开始依次填写对应的码字,填写完成后剩余的节点创建新的分支
  • 如果当前高度没有编码,则对其余的节点创建分支
  • 重复上述过程,直到完成

按照这个流程我们用上面两张表来实际构建一个Huffman编码树
创建根节点,并创建两个分支,现在树的高度是1

  root
 /    \
0      1

由于没有码长为1的编码,需要为所有的子节点继续创建分支,现在树的高度是2

    root
   /    \
  0      1
 / \    / \
0   1  0   1

有1个码长为2的编码,从左到右填入码字

       root
    /        \
   0          1
 /   \       / \
0     1     0   1
0x01  0x02

为剩余的两个子节点继续创建分支,现在树的高度是3

         root
    /           \
   0             1
 /   \         /   \
0     1       0     1
0x01  0x02   / \   / \
            0   1 0   1

有1个码长为3的编码,从左到右填入码字

         root
    /            \
   0              1
 /   \          /   \
0     1        0     1
0x01  0x02   /   \   / \
            0     1 0   1
            0x03

剩余的三个子节点继续创建分支,现在树的高度是4

            root
    /                 \
   0                   1
 /   \           /           \
0     1         0             1
0x01  0x02   /     \        /    \
            0       1      0      1
            0x03   / \    / \    / \
                  0   1  0   1  0   1

有3个码长为4的编码,从左到右填入码字

             root
    /                    \
   0                      1
 /   \           /                 \
0     1         0                   1
0x01  0x02   /      \             /    \
            0        1           0      1
            0x03   /   \       /   \    / \
                  0     1     0     1  0   1
                  0x00  0x04  0x11

重复上述过程直到结束
从根节点依次遍历每一个叶子节点就可以得到每个码字对应的Huffman编码
结果如下

Huffman编码码字
00b0x01
01b0x02
100b0x03
1010b0x00
1011b0x04
1100b0x11

可以对照《ITU-T T.81》中的表格来检验我们的编码结果是否正确
在这里插入图片描述

这样做的效率太低,实际不会用上面这种方式建立Huffman编码树并查询编码,而是用下面这种方法构建一张表,直接查表进行编码
建立的规则为:

  1. 最小编码长度的第一个编码必须从0开始。
  2. 相同长度编码必须是连续的。
  3. 编码长度为 j 的第一个符号可以从编码长度为 j-1 的最后一个符号所得知,即 C[j] = 2 * (C[j-1] + 1)。
  4. 如果没有长度为 j 的编码,则 C[j+1] = 2 * ((j+1)-(j-1)) * (C[j-1] + 1),就是中间缺少了n个编码,就要再扩大2^n倍

以上述为例,最小长度为2,第一个编码从0开始,即00
长度为2的码有两个,相同长度编码必须连续,因此下一个是01
长度为3的第一个编码 = (长度为2的最后一个编码 + 1) * 2 即 (01 + 1) * 2 = 10 * 2 = 100
长度为4的第一个编码 = (长度为3的最后一个编码 + 1) * 2 即 (100 + 1) * 2 = 101 * 2 = 1010
长度为4的编码有三个,因此后两个是1011,1100

Huffman编码

编码很简单,从编码树中查找对应的编码就行
这里说一下JPEG的编码规则
一般JPEG文件里面有4张Huffman编码表,分别是直流亮度编码表,直流色度编码表,交流亮度编码表,交流色度编码表
DCT转换并量化后的第一个数据是直流分量,使用直流编码表进行编码,其余的63个数据是交流分量,使用交流编码表进行编码
直流编码的数据是当前亮度直流分量与上一个亮度直流分量之差(或当前色度直流分量与上一个色度直流分量之差,色度只使用一张表,因此不区分是cb分量还是cr分量)
交流编码的数据为1字节,由两部分组成,高4位是连续0的个数,低4位是码长,其中有两个特殊码字,0x00(EOB end-of-block)在编码结束或其余数据都是0的时候使用,0xF0(ZRL)表示16个连续0,如果有32个连续0就写两个,以此类推。

以上文提到的亮度分量数据为例

直流数据编码

  • 直流分量为43,上一个直流分量为0,差值为43
  • 对43(0x2b)进行数值编码,结果为101011,码长6
  • 查找亮度直流Huffman编码表,6对应的编码为1110
  • 最终编码结果为1110101011
  • 如果直流分量数值为0,则当做码长为0的编码,千万不要把它当成码长为1的编码

交流数据编码

  • -15数值编码为0000,码长4
  • 前面没有0,因此高4位填0,低4位填码长4,得到0x04
  • 查找亮度交流Huffman编码表,0x04对应的编码为1011
  • 最终编码结果为10110000
  • 重复上述过程直到第26个数据,之后全是0,因此编码结束,在第26个数据后面填写EOB,也就是1010

完成亮度数据编码后再进行色度数据编码,完成这一个8*8块的所有编码后再进行下一块,直到图像结束,至此就完成了全部的编码工作。

JPEG 文件格式

相关文章有很多,这里给出一篇供参考,如果有不理解的地方配合 JPEGsnoop 实际看几张JPEG图片
https://www.cnblogs.com/sddai/p/5666924.html
这部分只要遵循规则写入数据就行,这样一张JPEG图片就完成了。
简单补充说明几点:

  • 0xFFDB字段保存的量化表是Z字扫描后的量化表,不是原始量化表。(这一点我暂时没有找到相关的说明文章)
  • 一般app0用来保存JFIF,app1用的保存exif,其余的自定义。实际测试发现一般都是通过查找appx里面的前几个字节是jfif还是exif来判断它的具体内容
  • 编码过程中,如果遇到0xFF,要在后面再补充一个0x00,解码时再把0x00去掉

参考资料

T.81 Information technology - Digital compression and coding of continuous-tone still images - Requirements and guidelines
https://www.itu.int/rec/T-REC-T.81/en
https://www.w3.org/Graphics/JPEG/itu-t81.pdf

T.871 : Information technology - Digital compression and coding of continuous-tone still images: JPEG File Interchange Format (JFIF)
https://www.itu.int/rec/T-REC-T.871

JPEG wiki
https://en.wikipedia.org/wiki/JPEG

JPEG文件头标记
https://www.cnblogs.com/sddai/p/5666924.html

其他参考资料
https://www.freecodecamp.org/news/how-jpg-works-a4dbd2316f35/#.2l56xw83s
https://www.cnblogs.com/Arvin-JIN/p/9133745.html
https://zhuanlan.zhihu.com/p/72044095
https://calendar.perfplanet.com/2015/why-arent-your-images-using-chroma-subsampling/

这个视频也讲得非常好
https://www.bilibili.com/video/BV1Nr4y1S76N

这两篇文章讲的非常好,也是我的主要参考依据
https://www.impulseadventure.com/photo/jpeg-huffman-coding.html
https://www.impulseadventure.com/photo/jpeg-compression.html

JPEGsnoop (JPEG分析工具)
https://www.impulseadventure.com/photo/jpeg-snoop.html

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

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

相关文章

【OpenFOAM】-olaFlow-算例4- irreg45degTank

算例路径&#xff1a; olaFlow\tutorials\irreg45degTank 算例描述&#xff1a; 不规则波浪模拟 学习目标&#xff1a; 不规则波浪模拟&#xff1a;olaFlow中单向不规则波采用线性波浪叠加法生成&#xff0c;基本原理如图2所受&#xff0c;需要提供对应波谱的周期、波高和相位的…

生产制造业管理系统对企业究竟有哪些作用?

对于生产制造企业来说&#xff0c;除了涉及到产品的生产制造和原料采购&#xff0c;还需要管理销售、库存、财务等方方面面&#xff0c;生产制造业管理系统的使用&#xff0c;尤为重要。正因如此&#xff0c;借助生产制造业管理系统来完善生产管理流程、提升生产管理水平&#…

LVGL学习笔记4 - 主题Themes

目录 1. 获取主题句柄 2. 设置基础主题 3. 设置主题的回调函数 4. 使能主题 5. 实例 5.1 定义一个全局Style变量 5.2 显示默认主题风格的矩形 5.3 初始化新主题的样式 5.4 初始化新主题 5.5 回调函数的实现 5.6 设置新主题 5.7 显示 主题是风格的集合。对应的变量结构…

设计模式--reactor 模式

说明 本文基于 tomcat 8.5.x 编写。author blog.jellyfishmix.com / JellyfishMIX - githubLICENSE GPL-2.0 介绍 reactor 模式通常应用于网络 IO 场景&#xff0c;高性能的中间件 redis, netty 都在使用。 背景 原始的网络 IO 模型 最原始的网络 IO 模型&#xff0c;服务…

Java学习笔记【8】异常

⛵ ⛵ ⛵ ⛵ ⛵ &#x1f680; &#x1f680; &#x1f680; &#x1f680; &#x1f680;   大家好&#x1f91d;&#xff0c;我是 &#x1f449;老孙&#x1f448;&#xff0c;未来学习路上多多关照 &#x1f91d; 一个喜欢用 ✍️ 博客记录人生的程序猿 &#x1f649;&…

Python遥感图像处理应用篇(二十七):Python绘制遥感图像各波段热力图(相关系数矩阵)(续)

续-https://soderayer.blog.csdn.net/article/details/125757807 上一篇中使用csv文件计算的相关系数热力图,本篇我们直接使用遥感图像来计算图像波段之间的相关系数。 方法一:已有软件ENVI计算 实际上,目前已有的软件,如ENVI就可以直接计算图像波段之间的相关系数,该工…

【高精度定位】关于GPS、RTK、PPK三种定位技术的探讨

高精度定位通常是指亚米级、厘米级以及毫米级的定位&#xff0c;从市场需求来看&#xff0c;定位的精度越高往往越好。“高精度、低成本”的定位方案无疑将是未来市场的趋势。 在物联网时代&#xff0c;大多数的应用或多或少都与位置服务相关联&#xff0c;尤其是对于移动物体而…

深入理解MySQL——分库分表种类与原则

分库分表的种类 首先说明&#xff0c;这里所说的分库分表是指把数据库中数据物理地拆分到多个实例或多台机器上去&#xff0c;而不是MySQL原生的Partitioning。 这里稍微提一下Partitioning&#xff0c;这是MySQL官方版本支持的&#xff0c;在本地针对表的分区进行操作&#…

[Flask]各种子功能的实现

一、标准Flask架构搭建 ①config.py 新建一个文件config.py&#xff0c;在其中进行参数初始化&#xff0c;再使用下面代码加载到app.py&#xff08;主程序&#xff09;中 import config app.config.from_object(config) #由config.py初始化 ②exts.py 用于放置扩展模块&a…

(二十四)Vue之props配置项

文章目录props基本使用props的数组形式props的对象形式检测类型检测类型 其他验证Vue学习目录 上一篇&#xff1a;&#xff08;二十三&#xff09;Vue之ref属性 props props 可以是数组或对象&#xff0c;用于让组件接收外部传过来的数据 约定props是只读的&#xff0c;Vue…

开源 高性能 云原生!时序数据库 TDengine 上线亚马逊Marketplace

近日&#xff0c;涛思数据旗下开源、高性能、云原生的时序数据库&#xff08;Time Series Database&#xff0c;TSDB&#xff09;TDengine 成功上线亚马逊云科技 Marketplace&#xff0c;为用户提供了更加丰富的订阅渠道。 TDengine 是针对时序数据特点研发和优化的数据库解决方…

CentOS8 Elasticsearch8.x 安装遇到的问题解决汇总

报错清单 启动报错&#xff1a;ERROR: Elasticsearch exited unexpectedly curl测试报错&#xff1a;curl: (52) Empty reply from server 报错解决 启动报错 起因 使用archive方式安装elasticsearch后&#xff0c;在目录中运行./bin/elasticsearch报错如下&#xff1a; 原…

第二十七章 数论——快速幂与逆元

第二十七章 快速幂与扩展欧几里德算法一、快速幂1、使用场景2、算法思路&#xff08;1&#xff09;二进制优化思想&#xff08;2&#xff09;模运算法则3、代码实现&#xff08;1&#xff09;问题&#xff08;2&#xff09;代码二、快速幂求逆元1、什么是逆元&#xff1f;&…

结构体位段问题

每一位勇敢努力的少年&#xff0c;必将不负众望&#xff01; 什么是位段 位段的详细解释 位段其实也是一种结构体的类型 1.位段的成员是 int ,short int unsigned int , signed int , short , char 类型 2.位段的成员名后有一个冒号和一个数字 看一个例子&#xff1a; st…

通过静态LSP、LDP LSP、MPLS TE三种方式实现总部与分支的互通

一、静态LSP 特点&#xff1a;类似静态路由&#xff0c;简单易用&#xff0c;手动建立lsp&#xff0c;定制转发路径&#xff0c;无需控制报文&#xff0c;资源消耗少。 缺点&#xff1a;不适合大型复杂拓扑&#xff0c;不能根据网络变化而动态调整&#xff0c;需要管理员手动调…

【jprofiler应用-oom原因定位】

1.安装jprofiler jprofiler_windows-x64_11_0_2.exe 2.使用KeyGen.exe生成注册码然后输入 3.idea中安装jprofiler插件 File-->Setting-->Plugins 搜索jprofiler插件然后安装 4.以一个内存溢出的程序为例子进行分析(一直分配内存&#xff0c;List容器引用着Student导致…

医疗产品设计的新趋势

随着个人健康和医疗数据技术的发展&#xff0c;消费者可以选择更多的方法来跟踪和管理他们的健康状况&#xff0c;因此医疗产品开始转向更多的健康预防领域。医疗器械设计公司认为&#xff0c;随着医疗产品设计从医疗产品转向家庭&#xff0c;医疗产品的设计需要考虑更多的新问…

【HTML+CSS+JavaScript】实现简单网页版的飞机大战

文章目录【HTMLCSSJavaScript】实现简单网页版的飞机大战一. HTML部分代码二. CSS部分代码三. JavaScript部分代码四. 完整的代码和图片获取【HTMLCSSJavaScript】实现简单网页版的飞机大战 本文分享的是键盘版飞机大战的代码&#xff0c;且文章末尾有惊喜。 效果图&#xff1a…

前端食堂技术周刊第 64 期:Node.js 19、Interop 2022、SvelteKit 1.0、2022 Web 性能回顾、最流行的 Node.js

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;冰糖雪梨 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 本期摘要 Node.js 19 的新特性Interop 2022 年终更新SvelteKit 1.02022 Web 性能回…

Python爬虫学习第十二天---scrapy学习

Python爬虫学习第十二天—scrapy学习 一、scrapy的概念和流程 1、scrapy概念 Scrapy是一个Python编写的开源网络爬虫框架&#xff0c;它是一个被设计用于爬取网络数据、提取结构性数据的框架。Scrapy文档地址&#xff1a;http://scrapy-chs.readthedocs.io/zh_CN/1.0/intro/…