LZ77算法压缩和解压缩

2023-11-13

LZ77简介

Ziv和Lempel于1977年发表题为“顺序数据压缩的一个通用算法(A Universal Algorithm for Sequential Data Compression )”的论文,论文中描述的算法被后人称为LZ77算法。值得说的是,LZ77严格意义上来说不是一种算法,而是一种编码理论。同Huffman编码一样,只定义了原理,并没有定义如何实现。基于这种理论来实现的算法才称为LZ77算法,或者人们更愿意称为LZ77变种。实际上这类算法已经有很多了,比如LZSS、LZB、LZH等。至今,几乎我们日常使用的所有通用压缩工具,象ARJ,PKZip,WinZip,LHArc,RAR,GZip,ACE,ZOO,TurboZip,Compress,JAR„„甚至许多硬件如网络设备中内置的压缩算法,无一例外,都可以最终归结为这两个以色列人的杰出贡献。

LZ77是一种基于字典的算法,它将长字符串(也称为短语)编码成短小的标记,用小标记代替字典中的短语,从而达到压缩的目的。也就是说,它通过用小的标记来代替数据中多次重复出现的长串方法来压缩数据。其处理的符号不一定是文本字符,可以是任意大小的符号。

短语字典的维护

不同的基于字典的算法使用不同的方法来维护它们的字典。LZ77使用的是一个前向缓冲区和一个滑动窗口

LZ77首先将一部分数据载入前向缓冲区。为了便于理解前向缓冲区如何存储短语并形成字典,我们将缓冲区描绘成S1,...,Sn的字符序列,Pb是由字符组成的短语集合。从字符序列S1,...,Sn,组成n个短语,定义如下:

Pb = {(S1),(S1,S2),...,(S1,...,Sn)}

例如,如果前向缓冲区包含字符(A,B,D),那么缓冲区中的短语为{(A),(A,B),(A,B,D)}。

一旦数据中的短语通过前向缓冲区,那么它将移动到滑动窗口中,并变成字典的一部分。为理解短语是如何在滑动窗口中表示的,首先,把窗口想象成S1,...,Sm的字符序列,且Pw是由这些字符组成的短语集合。从序列S1,...,Sm产生短语数据集合的过程如下:

Pw = {P1,P2,...,Pm},其中Pi = {(Si),(Si,Si+1),...,(Si,Si+1,...,Sm)}

例如,如果滑动窗口中包含符号(A,B,C),那么窗口和字典中的短语为{(A),(A,B),(A,B,C),(B),(B,C),(C)}。

LZ77算法的主要思想就是在前向缓冲区中不断寻找能够与字典中短语匹配的最长短语。以上面描述的前向缓冲区和滑动窗口为例,其最长的匹配短语为(A,B)。

压缩和解压缩数据

前向缓冲区和滑动窗口之间的匹配有两种情况:要么找到一个匹配短语,要么找不到匹配的短语。当找到最长的匹配时,将其编码成短语标记。

短语标记包含三个部分:1、滑动窗口中的偏移量(从头部到匹配开始的前一个字符);2、匹配中的符号个数;3、匹配结束后,前向缓冲区中的第一个符号。

当没有找到匹配时,将未匹配的符号编码成符号标记。这个符号标记仅仅包含符号本身,没有压缩过程。事实上,我们将看到符号标记实际上比符号多一位,所以会出现轻微的扩展。

一旦把n个符号编码并生成相应的标记,就将这n个符号从滑动窗口的一端移出,并用前向缓冲区中同样数量的符号来代替它们。然后,重新填充前向缓冲区。这个过程使滑动窗口中始终有最新的短语。滑动窗口和前向缓冲区具体维护的短语数量由它们自身的容量决定。

下图(1)展示了用LZ77算法压缩字符串的过程,其中滑动窗口大小为8个字节,前向缓冲区大小为4个字节。在实际中,滑动窗口典型的大小为4KB(4096字节)。前向缓冲区大小通常小于100字节。

图(1):使用LZ77算法对字符串ABABCBABABCAD进行压缩

我们通过解码标记和保持滑动窗口中符号的更新来解压缩数据,其过程类似于压缩过程。当解码每个标记时,将标记编码成字符拷贝到滑动窗口中。每当遇到一个短语标记时,就在滑动窗口中查找相应的偏移量,同时查找在那里发现的指定长度的短语。每当遇到一个符号标记时,就生成标记中保存的一个符号。下图(2)展示了解压缩图(1)中数据的过程。

图(2):使用LZ77算法对图(1)中压缩的字符串进行解压缩

LZ77的效率

用LZ77算法压缩的程度取决于很多因素,例如,选择滑动窗口的大小,为前向缓冲区设置的大小,以及数据本身的熵。最终,压缩的程度取决于能匹配的短语的数量和短语的长度。大多数情况下,LZ77比霍夫曼编码有着更高的压缩比,但是其压缩过程相对较慢。

用LZ77算法压缩数据是非常耗时的,国为要花很多时间寻找窗口中的匹配短语。然而在通常情况下,LZ77的解压缩过程要比霍夫曼编码的解压缩过程耗时要少。LZ77的解压缩过程非常快是因为每个标记都明确地告诉我们在缓冲区中哪个位置可以读取到所需要的符号。事实上,我们最终只从滑动窗口中读取了与原始数据数量相等的符号而已。

LZ77的接口定义

lz77_compress


int lz77_compress(const unsigned char *original, unsigned char **compressed, int size);

返回值:如果数据压缩成功,返回压缩后数据的字节数;否则返回-1;

描述:   用LZ77算法压缩缓冲区original中的数据,original包含size个字节的空间。压缩后的数据存入缓冲区compressed中。lz77_compress需要调用malloc来动态的为compressed分配存储空间,当这块空间不再使用时,由调用者调用函数free来释放空间。

复杂度:O(n),其中n是原始数据中符号的个数。

lz77_uncompress


int lz77_uncompress(const unsigned char *compressed, unsigned char **original);

返回值:如果解压缩数据成功,返回恢复后数据的字节数;否则返回-1;

描述:   用LZ77算法解压缩缓冲区compressed中的数据。假定缓冲区包含的数据之前由lz77_compress压缩。恢复后的数据存入缓冲区original中。lz77_uncompress函数调用malloc来动态的为original分配存储空间。当这块存储空间不再使用时,由调用者调用函数free来释放空间。

复杂度:O(n)其中n是原始数据中符号的个数。

LZ77的实现与分析

LZ77算法,通过一个滑动窗口将前向缓冲区中的短语编码成相应的标记,从而达到压缩的目的。在解压缩的过程中,将每个标记解码成短语或符号本身。要做到这些,必须要不断地更新窗口,这样,在压缩过程中的任何时刻,窗口都能按照规则进行编码。在本节所有的示例中,原始数据中的一个符号占一个字节。

lz77_compress

lz77_compress操作使用LZ77算法来压缩数据。首先,它将数据中的符号写入压缩数据的缓冲区中,并同时初始化滑动窗口和前向缓冲区。随后,前向缓冲区将用来加载符号。

压缩发生在一个循环中,循环会持续迭代直到处理完所有符号。使用ipos来保存原始数据中正在处理的当前字节,并用opos来保存向压缩数据缓冲区写入的当前位。在循环的每次迭代中,调用compare_win来确定前向缓冲区与滑动窗口中匹配的最长短语。函数compare_win返回最长匹配串的长度。

当找到一个匹配串时,compare_win设置offset为滑动窗口中匹配串的位置,同时设置next为前向缓冲区中匹配串后一位的符号。在这种情况下,向压缩数据中写入一个短语标记(如图3-a)。在本节展示的实现中,对于偏移量offset短语标记需要12位,这是因为滑动窗口的大小为4KB(4096字节)。此时短语标志需要5位来表示长度,因为在一个32字节的前向缓冲区中,不会有匹配串超过这个长度。当没有找到匹配串时,compare_win返回,并且设置next为前向缓冲区起始处未匹配的符号。在这种情况下,向压缩数据中写入一个符号(如图3-b)。无论向压缩数据中写入的是一个短语还是一个符号,在实际写入标记之前,都需要调用网络函数htonl来转换串,以保证标记是大端格式。这种格式是在实际压缩数据和解压缩数据时所要求的。

 

图3:LZ77中的短语标记(A)和符号标记(B)的结构

一旦将相应的标记写入压缩数据的缓冲区中,就调整滑动窗口和前向缓冲区。要使数据通过滑动窗口,将数据从右边滑入窗口,从左边滑出窗口。同样,在前向缓冲区中也是相同的滑动过程。移动的字节数与标记中编码的字符数相等。

lz77_compress的时间复杂度为O(n),其中n是原始数据中符号的个数。这是因为,对于数据中每个n/c个编码的标记,其中1/c是一个代表编码效率的常量因素,调用一次compare_win。函数compare_win运行一段固定的时间,因为滑动窗口和前向缓冲区的大小均为常数。然而,这些常量比较大,会对lz77_compress的总体运行时间产生较大的影响。所以,lz77_compress的时间复杂度是O(n),但其实际的复杂度会受其常量因子的影响。这就解释了为什么在用lz77进行数据压缩时速度非常慢。

lz77_uncompress

lz77_uncompress操作解压缩由lz77_compress压缩的数据。首先,该函数从压缩数据中读取字符,并初始化滑动窗口和前向缓冲区。

解压缩过程在一个循环中执行,此循环会持续迭代执行直到所有的符号处理完。使用ipos来保存向压缩数据中写入的当前位,并用opos来保存写入恢复数据缓冲区中当前字节。在循环的每次迭代过程中,首先从压缩数据读取一位来确定要解码的标记类型。

在解析一个标记时,如果读取的首位是1,说明遇到了一个短语标记。此时读取它的每个成员,查找滑动窗口中的短语,然后将短语写入恢复数据缓冲区中。当查找每个短语时,调用网络函数ntohl来保证窗口中的偏移量和长度的字节顺序是与操作系统匹配的。这个转换过程是必要的,因为从压缩数据中读取出来的偏移量和长度是大端格式的。在数据被拷贝到滑动窗口之前,前向缓冲区被用做一个临时转换区来保存数据。最后,写入该标记编码的匹配的符号。如果读取的标记的首位是0,说明遇到了一个符号标记。在这种情况下,将该标记编码的匹配符号写入恢复数据缓冲区中。

一旦将解码的数据写入恢复数据的缓冲区中,就调整滑动窗口。要将数据通过滑动窗口,将数据从右边滑入窗口,从左边滑出窗口。移动的字节数与从标记中解码的字符数相等。

lz77_uncompress的时间复杂度为O(n),其中n是原始数据中符号的个数。

示例:LZ77的实现文件

(示例所需要的头文件信息请查阅前面的文章:数据压缩的重要组成部分--位操作)

 

/*lz77.c*/
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>

#include "bit.h"
#include "compress.h"

/*compare_win 确定前向缓冲区中与滑动窗口中匹配的最长短语*/
static int compare_win(const unsigned char *window, const unsigned char *buffer,
                       int *offset, unsigned char *next)
{
    int match,longest,i,j,k;

    /*初始化偏移量*/
    *offset = 0;

    /*如果没有找到匹配,准备在前向缓冲区中返回0和下一个字符*/
    longest = 0;
    *next = buffer[0];

    /*在前向缓冲区和滑动窗口中寻找最佳匹配*/
    for(k=0; k<LZ77_WINDOW_SIZE; k++)
    {
        i = k;
        j = 0;
        match = 0;

        /*确定滑动窗口中k个偏移量匹配的符号数*/
        while(i<LZ77_WINDOW_SIZE && j<LZ77_BUFFER_SIZE - 1)
        {
            if(window[i] != buffer[j])
                break;

            match++;
            i++;
            j++;
        }

        /*跟踪最佳匹配的偏移、长度和下一个符号*/
        if(match > longest)
        {
            *offset = k;
            longest = match;
            *next = buffer[j];
        }
    }
    return longest;
}

/*lz77_compress  使用lz77算法压缩数据*/
int lz77_compress(const unsigned char *original,unsigned char **compressed,int size)
{
    unsigned char     window[LZ77_WINDOW_SIZE],
                      buffer[LZ77_BUFFER_SIZE],
                      *comp,
                      *temp,
                      next;
    int               offset,
                      length,
                      remaining,
                      hsize,
                      ipos,
                      opos,
                      tpos,
                      i;
    /*使指向压缩数据的指针暂时无效*/
    *compressed = NULL;

    /*写入头部信息*/
    hsize = sizeof(int);
    if((comp = (unsigned char *)malloc(hsize)) == NULL)
        return -1;
    memcpy(comp,&size,sizeof(int));

    /*初始化滑动窗口和前向缓冲区(用0填充)*/
    memset(window, 0 , LZ77_WINDOW_SIZE);
    memset(buffer, 0 , LZ77_BUFFER_SIZE);

    /*加载前向缓冲区*/
    ipos = 0;

    for(i=0; i<LZ77_BUFFER_SIZE && ipos < size; i++)
    {
        buffer[i] = original[ipos];
        ipos++;
    }

    /*压缩数据*/
    opos = hsize * 8;
    remaining = size;

    while(remaining > 0)
    {
        if((length = compare_win(window,buffer,&offset,&next)) != 0)
        {
            /*编码短语标记*/
            token = 0x00000001 << (LZ77_PHRASE_BITS - 1);

            /*设置在滑动窗口找到匹配的偏移量*/
            token = token | (offset << (LZ77_PHRASE_BITS - LZ77_TYPE_BITS - LZ77_WINOFF_BITS));

            /*设置匹配串的长度*/
            token = token | (length << (LZ77_PHRASE_BITS - LZ77_TYPE_BITS - LZ77_WINOFF_BITS - LZ77_BUFLEN_BITS));

            /*设置前向缓冲区中匹配串后面紧邻的字符*/
            token = token | next;

            /*设置标记的位数*/
            tbits = LZ77_PHRASE_BITS;
        }
        else
        {
            /*编码一个字符标记*/
            token = 0x00000000;

            /*设置未匹配的字符*/
            token = token | next;

            /*设置标记的位数*/
            tbits = LZ77_SYMBOL_BITS;
        }

        /*确定标记是大端格式*/
        token = htonl(token);

        /*将标记写入压缩缓冲区*/
        for(i=0; i<tbits; i++)
        {
            if(opos % 8 == 0)
            {
                /*为压缩缓冲区分配临时空间*/
                if((temp = (unsigned char *)realloc(comp,(opos / 8) + 1)) == NULL)
                {
                    free(comp);
                    return -1;
                }
                comp = temp;
            }

            tpos = (sizeof(unsigned long ) * 8) - tbits + i;
            bit_set(comp,opos,bit_get((unsigned char *)&token,tpos));
            opos++;
        }
        /*调整短语长度*/
        length++;

        /*从前向缓冲区中拷贝数据到滑动窗口中*/
        memmove(&window[0],&window[length],LZ77_WINDOW_SIZE - length);
        memmove(&window[LZ77_WINDOW_SIZE - length],&buffer[0],length);
        memmove(&buffer[0],&buffer[length],LZ77_BUFFER_SIZE - length);
        /*向前向缓冲区中读取更多数据*/
        for(i = LZ77_BUFFER_SIZE - length; i<LZ77_BUFFER_SIZE && ipos <size; i++)
        {
            buffer[i] = original[ipos];
            ipos++;
        }

        /*调整剩余未匹配的长度*/
        remaining = remaining - length;
    }
    /*指向压缩数据缓冲区*/
    *compressed = comp;
    /*返回压缩数据中的字节数*/
    return ((opos - 1) / 8) + 1;
}

/*lz77_uncompress  解压缩由lz77_compress压缩的数据*/
int lz77_uncompress(const unsigned char *compressed,unsigned char **original)
{
    unsigned char window[LZ77_WINDOW_SIZE],
                  buffer[LZ77_BUFFER_SIZE]
                  *orig,
                  *temp,
                  next;
    int           offset,
                  length,
                  remaining,
                  hsize,
                  size,
                  ipos,
                  opos,
                  tpos,
                  state,
                  i;
    /*使指向原始数据的指针暂时无效*/
    *original = orig = NULL;

    /*获取头部信息*/
    hsize = sizeof(int);
    memcpy(&size,compressed,sizeof(int));

    /*初始化滑动窗口和前向缓冲区*/
    memset(window, 0, LZ77_WINDOW_SIZE);
    memset(buffer, 0, LZ77_BUFFER_SIZE);

    /*解压缩数据*/
    ipos = hsize * 8;
    opos = 0;
    remaining = size;

    while(remaining > 0)
    {
        /*获取压缩数据中的下一位*/
        state = bit_get(compressed,ipos);
        ipos++;

        if(state == 1)
        {
            /*处理的是短语标记*/
            memset(&offset, 0, sizeof(int));

            for(i=0; i<LZ77_WINOFF_BITS; i++)
            {
                tpos = (sizeof(int)*8) - LZ77_WINOFF_BITS + i;
                bit_set((unsigned char *)&offset, tpos, bit_get(compressed,ipos));
                ipos++;
            }

            memset(&length, 0, sizeof(int));
            for(i=0; i<LZ77_BUFLEN_BITS; i++)
            {
                tpos = (sizeof(int)*8) - LZ77_BUFLEN_BITS + i;
                bit_set((unsigned char *)&length, tpos, bit_get(compressed,ipos));
                ipos++;
            }

            next = 0x00;
            for(i=0; i<LZ77_NEXT_BITS; i++)
            {
                tpos = (sizeof(unsigned char)*8) - LZ77_NEXT_BITS + i;
                bit_set((unsigned char *)&next, tpos, bit_get(compressed,ipos));
                ipos++;
            }

            /*确保偏移和长度对系统有正确的字节排序*/
            offset = ntohl(offset);
            length = ntohl(length);

            /*将短语从滑动窗口写入原始数据缓冲区*/
            i=0;
            if(opos>0)
            {
                if((temp = (unsigned char *)realloc(orig,opos+length+1)) == NULL)
                {
                    free(orig);
                    return 1;
                }
                orig = temp;
            }
            else
            {
                if((orig = (unsigned char *)malloc(length+1)) == NULL)
                    return -1;
            }

            while(i<length && remaining>0)
            {
                orig[opos] = window[offset + i];
                opos++;
                /*在前向缓冲区中记录每个符号,直到准备更新滑动窗口*/
                buffer[i] = window[offset + i];
                i++;

                /*调整剩余符号总数*/
                remaining --;
            }

            /*将不匹配的符号写入原始数据缓冲区*/
            if(remaining > 0)
            {
                orig[opos] = next;
                opos++;

                /*仍需在前向缓冲区中记录此符号*/
                buffer[i] = next;

                /*调整剩余字符总数*/
                remaining--;
            }
            /*调整短语长度*/
            length++;
        }
        else
        {
            /*处理的是字符标记*/
            next = 0x00;
            for(i=0; i<LZ77_NEXT_BITS; i++)
            {
                tpos = (sizeof(unsigned char)*8) - LZ77_NEXT_BITS + i;
                bit_get((unsigned char *)&next, tpos,bit_get(compressed,ipos));
                ipos++;
            }

            /*将字符写入原始数据缓冲区*/
            if(opos > 0)
            {
                if((temp = (unsigned char*)realloc(orig,opos+1)) == NULL)
                {
                    free(orig);
                    return -1;
                }
                orig = temp;
            }
            else
            {
                if((orig = (unsigned char *)malloc(1)) == NULL)
                return -1;
            }
            orig[opos] = next;
            opos++;

            /*在前向缓冲区中记录当前字符*/
            if(remaining > 0)
                buffer[0] = next;
            /*调整剩余数量*/
            remaining--;

            /*设置短语长度为1*/
            length = 1;
        }
        /*复制前向缓冲中的数据到滑动窗口*/
        memmove(&window[0], &window[length],LZ7_WINDOW_BITS - length);
        memmove(&window[LZ77_WINDOW_SIZE - length], &buffer[0], length);
    }
    /*指向原始数据缓冲区*/
    *original = orig;

    /*返回解压缩的原始数据中的字节数*/
    return  opos;
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

LZ77算法压缩和解压缩 的相关文章

  • CCF C³-23@阿里妈妈:数智商业技术丨开始报名

    CCF C 活动第二十三期主题是 数智商业技术 将于2023年9月14日周四 18 00 21 30 在北京市朝阳区阿里中心望京A座阿里妈妈举行 报名从速 2023年 可以说是属于生成式AI大模型的一年 从去年年底持续到现在的 以生成式AI
  • CVE-2023-23752:Joomla未授权访问漏洞复现

    CVE 2023 23752 Joomla未授权访问漏洞复现 前言 本次测试仅供学习使用 如若非法他用 与本文作者无关 需自行负责 一 Openfire简介 Joomla是一个免费的开源内容管理系统 CMS 允许用户构建网站和在线应用程序
  • VUE React Angular

    Typescript ES6 ES5 React 专注于使用 Javascript ES6 Vue 使用 Javascript ES5 或 ES6 Angular 依赖于 TypeScript 模板 JSX 还是 HTML React JS
  • 厉害,被马赛克的图片竟然还可以恢复

    文章首发于微信公众号 GitHub 精选 欢迎大家关注 大家好 我是章鱼猫 其实之前在我用马赛克对图片进行模糊处理的时候 我就思考过这个问题 这些被马赛克的地方能不能被恢复出来 万一能恢复 当时就觉得细思极恐 你说巧不巧还真能恢复 在介绍今
  • DataGrid后台实现选中某一行,并滚动到当前选中项

    定义一个DataGrid
  • Linux文件权限

    Linux文件权限 一 对shell外壳的理解 1 对shell外壳的理解 2 shell外壳的作用 3 shell与bash的关系 二 文件的权限 1 如何理解权限 2 如何查看文件的权限 3 文件都有哪些权限 4 角色的划分 5 权限操
  • 从URL到浏览器显示页面的流程

    一 URL解析 1 地址解析 浏览器会根据你的输入来判断该输入是一条合法的URL 还是需要被搜索的关键词 并且根据你输入的内容进行自动完成 字符编码等操作 2 其他操作 目前大部分浏览器都会强制客户端使用HTTPS协议以保证信息传输的安全性
  • 解决STM32引脚无法输出0V电压问题:深入探讨输出缓冲器(Output Buffer)

    在嵌入式系统开发中 精确控制引脚输出电压是至关重要的 然而 有时会遇到DAC模块无法输出0V 接近 电压的情况 这往往是因为默认开启了Output Buffer 输出缓冲器 导致的 在程序中直接赋值为0 DAC SetChannel1Dat
  • Windows Server 配置(七)VPN服务器的安装

    VPN服务器的安装 VPN服务器是双网卡或多网卡的配置 一块网卡连接内网 另一块连接外网 同时外网或远程的客户端可以通过建立VPN连接访问到内网资源 两块网卡分别设置好地址 外网网卡的地址是否能做的 或者是在路由器上做NAT需要进一步了解
  • 1.1.3 计算机网络的相关性能指标

    1 1 3 计算机网络的相关性能指标 1 速率 即数据率或称数据传输率或比特率 连接在计算机网络上的主机在数字信道上传输数据位数的速率 补充 速率的单位 1 Tb s 10 3 Gb s 10 6 Mb s 10 9 kb s 10 12
  • 基于springboot开发项目架构之ElasticSearch

    官方网址 https www elastic co cn products elasticsearch Github https github com elastic elasticsearch 总结 1 elasticsearch是一个基

随机推荐

  • pandas 数据聚合

    1 apply Series Series apply func convert dtype True args kwds func 要进行数据聚合的函数 自动对Series内的每个数据调用func gt gt gt import pand
  • Web开发学习(9)全局埋点

    size large 埋点的作用是把客户端每次访问服务端的操作记录下来 包括请求连接 请求者ip 请求参数 请求结果等等 以便于在出现异常的情况下排查 在用户量庞大的情况下还可以对这些记录做数据分析 这个功能我还是坚持一直以来的原则 一次封
  • 虚拟服务器怎么建站,虚拟主机建站流程

    虚拟主机 由于性价比高 易于操作 即开即用 管理方便等优点 成为众多站长创 首先 网站搭建流程一般是 这里 购买部分 不详细讲述 我们以 第二步 备案非常重要 千万不要抱侥幸心理 购买好产品后 及时申请备案 现在西部数码的域名备案方式是全程
  • vue-amap的简单使用线路、标记、水波纹

    vue cli3 vue amap的简单使用 开始 关于vue amap事件 其他不想说了 要下班了 项目需要用地图展示线路和标记位置 老板说用高德吧 开始入坑vue amap 开始 高德原生jsAPI已经很成熟 这回在vue项目中用高德v
  • S7-1500与两台S7-1200 Profinet 通讯

    警告 本方案实现的是S7 1500和2台S7 1200都组态在同一个博图软件中 然后实现S7 1500和2台S7 1200之间的Profinet通信 参见下图 并不是大家想像的博图软件中只组态一台S7 1500 然后通过网络和其它2台S7
  • 华为OD机试真题 Java 实现【获得完美走位】【2022Q4 200分】

    一 题目描述 在第一人称射击游戏中 玩家通过键盘的 A S D W 四个按键控制游戏人物分别向左 向后 向右 向前进行移动 从而完成走位假设玩家每按动一次键盘 游戏任务会向某个方向移动一步 如果玩家在操作一定次数的键盘并且各个方向的步数相同
  • 朋友干副业被发现,果断辞职!

    关注公众号 人工智能与大数据精选 点击 最有价值 拉你进群 经常有年轻程序员问我 工作之余可以干点什么副业 不影响工作还能赚点钱 其实像接外包 写作 讲课 知识付费啥的 有很多方式 身边也有朋友通过积累一些外包资源 接外包的收入是年薪的2倍
  • 华为od的一些算法题

    报数游戏 100个人围成一圈 每个人有一个编码 编号从1开始到100 他们从1开始依次报数 报到为M的人自动退出圈圈 然后下一个人接着从1开始报数 直到剩余的人数小于M 请问最后剩余的人在原先的编号为多少 输入描述 输入一个整数参数M 输出
  • Shell常用命令与工具(一)

    本章内容如下 11 1 ls 功能 列出目录内容 常用选项 a 显示所有文件 包括隐藏的 l 长格式列出信息 i 显示文件inode号 t 按修改时间排序 r 按修改时间倒序排序 示例 按修改时间排序 ls t 按修改时间倒序排序 ls r
  • CCF 202209-2 何以包邮? (01背包动态规划练习)

    一 先温习一下01背包问题 有N件物品和一个容量为V的背包 第i件物品的体积是c i 价值是w i 求解将哪些物品装入背包可使价值总和最大 条件汇总 背包限制容量 Z 此时背包容量 C 物品 1 i n 代表编号 重量 wight 1 wi
  • 日期、时间选择控件 - datetimepicker

    http www bootcss com p bootstrap datetimepicker demo htm 页面上添加控件
  • Grafana loki部署及使用及问题处理方法(超详细)

    一 下载软件 因为我是本地测试 所以用的windows版本的包 loki服务window版本的安装包下载地址 下载地址 选择 promtail windows版本的安装包下载地址 下载地址 Grafana服务的下载地址 下载地址 二 配置文
  • 能将阿里云盘挂载为webdav的webdav-aliyundriver

    虽然从内测开始就申请了阿里云盘 但是一直也没怎么用 网上一直强调的是阿里云盘的速度 但是在老苏看来天翼云也不算差 相对来说阿里云盘作为新的云盘 在功能上和百度云盘 天翼云盘这些成熟产品比还是存在很多欠缺的 直到我发现了 webdav ali
  • 【页面编号】假设页面从1开始连续编号,一共1000页。计算所有页码中的十进制数字的总个数

    题目 假设页面从1开始连续编号 一共1000页 计算所有页码中的十进制数字的总个数 这题很简单 就是求0 9出现次数的总和 public class text3 0 9为十进制数字 本题要求为统计1 1000中出现的十进制数字总数 分析 对
  • vscode安装使用教程

    一 什么是vscode Visual Studio Code 简称 VS Code VSC 是一款免费开源的现代化轻量级代码编辑器 支持几乎所有主流的开发语言的语法高亮 智能代码补全 自定义热键 括号匹配 代码片段 代码对比 Diff GI
  • 2022多益网络春招之最后一场--软件工程师笔试

    题型 选择 填空 简答 算法 凭借自己的回忆说一下题目类型 选择题 涉及到的题型主要是Java基础的题目 例如分析代码的时间复杂度 一些排序算法的时间复杂度 二叉排序树 sql语句 很简单 散列表 链表 什么数据结构具有记忆功能 连接查询
  • C++基础学习-29类模板概念,类模板定义、使用

    目录 一 概述 二 类模板的定义 三 类模板的成员函数 四 模板类名字的使用 五 非类模板参数 一 概述 用类模板来实例化一个特定的类 编译器不能为类模板推断参数类型 所以为了使用类模板 我们必须在类模板名后边用 lt gt 来提供额外的信
  • module.alias的更新

    一般通过hotplug的设备会通过module alias 中的信息来加载device对应的driver linux 1wlr lib modules 4 4 68 2 default modinfo ipmi ssif filename
  • vue3+vite+element plus+el-table中如何放置本地图片

    遇到bug 解决方案 vue组件 js代码
  • LZ77算法压缩和解压缩

    LZ77简介 Ziv和Lempel于1977年发表题为 顺序数据压缩的一个通用算法 A Universal Algorithm for Sequential Data Compression 的论文 论文中描述的算法被后人称为LZ77算法