Go项目实现日志按时间及文件大小切割并压缩


关于日志的一些问题:

单个文件过大会影响写入效率,所以会做拆分,但是到多大拆分? 最多保留几个日志文件?最多保留多少天,要不要做压缩处理?

一般都使用 lumberjack[1]这个库完成上述这些操作


lumberjack


 //info文件writeSyncer
 infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
  Filename:   "./log/info.log"//日志文件存放目录,如果文件夹不存在会自动创建
  MaxSize:    2,                //文件大小限制,单位MB
  MaxBackups: 100,              //最大保留日志文件数量
  MaxAge:     30,               //日志文件保留天数
  Compress:   false,            //是否压缩处理
 })
 infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer, zapcore.AddSync(os.Stdout)), lowPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志
 //error文件writeSyncer
 errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
  Filename:   "./log/error.log"//日志文件存放目录
  MaxSize:    1,                 //文件大小限制,单位MB
  MaxBackups: 5,                 //最大保留日志文件数量
  MaxAge:     30,                //日志文件保留天数
  Compress:   false,             //是否压缩处理
 })

测试日志到达指定大小后自动会切分


alt

例如,当info级别的日志文件到达2M时,会根据当时的时间戳,切分出一个info-2023-04-13T05-27-18.296.log。 后续新写入的info级别的日志将写入到info.log,直到又到达2M,继续会切分。


测试日志到达指定最大保留日志文件数量后,将作何操作


清掉log文件夹,修改error日志配置:

 errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
  Filename:   "./log/error.log"//日志文件存放目录
  MaxSize:    1,                 //文件大小限制,单位MB
  MaxBackups: 3,                 //最大保留日志文件数量
  MaxAge:     30,                //日志文件保留天数
  Compress:   false,             //是否压缩处理
 })

代码中只打印error日志,执行代码 进行观察

alt

继续执行

alt

继续执行

alt
alt

可见最早拆分出的那个error-2023-04-13T05-40-48.715.log文件不见了~

继续执行,切分出来的文件数量,也会始终保持3个


完整变化图:

alt

测试压缩处理的效果


清掉log文件夹,修改error日志配置:

 errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
  Filename:   "./log/error.log"//日志文件存放目录
  MaxSize:    5,                 //文件大小限制,单位MB
  MaxBackups: 10,                 //最大保留日志文件数量
  MaxAge:     30,                //日志文件保留天数
  Compress:   false,             //是否压缩处理
 })

代码中只打印error日志,执行代码,循环10000000次, 进行观察

alt

不压缩共占用814M存储空间


清掉log文件夹,修改Compress字段为true,执行代码:

alt

启用压缩后,仅占用了30M磁盘空间!

不太好的地方就是不方便直接查看了,需要解压后查看。但大大省了所占用的空间


golang zap日志库使用[2]


lumberjack这个库目前只支持按文件大小切割(按时间切割效率低且不能保证日志数据不被破坏,详情见https://github.com/natefinch/lumberjack/issues/54)

想按日期切割可以使用github.com/lestrrat-go/file-rotatelogs[3]这个库(目前不维护了)




file-rotatelogs实现按时间的切割


注意:

github.com/lestrrat-go/file-rotatelogs[4](2021年后不更新了) 和 github.com/lestrrat/go-file-rotatelogs[5](2018年以后就不更新了) 两个不一样。。前面那个是更新的,作者是一个人...

(有一个linux系统上的日志工具,也叫logrotate)

logrotate 是一个用于日志文件轮换的 Go 语言库,支持按时间轮换、按文件大小轮换和按行数轮换。还支持在轮换时压缩文件、删除旧文件、给文件添加时间戳等功能

用zap和go-file-rotatelogs实现日志的记录和日志按时间分割[6]


WithRotationCount和WithMaxAge两个选项不能共存,只能设置一个(都设置编译时不会出错,但运行时会报错。也是为了防止影响切分的处理逻辑):

panic: options MaxAge and RotationCount cannot be both set

package main

import (
 "fmt"
 "io"
 "net/http"
 "time"

 rotatelogs "github.com/lestrrat-go/file-rotatelogs"
 "go.uber.org/zap"
 "go.uber.org/zap/zapcore"
)

// 使用file-rotatelogs做切分

var sugarLogger *zap.SugaredLogger

func main() {
 fmt.Println("shuang提示:begin main")
 InitLogger()
 defer sugarLogger.Sync()

 for i := 0; i < 100000; i++ {
  simpleHttpGet("www.cnblogs.com")
  simpleHttpGet("https://www.baidu.com")
 }

}

// 例子,http访问url,返回状态
func simpleHttpGet(url string) {
 fmt.Println("begin simpleHttpGet:" + url)
 sugarLogger.Debugf("Trying to hit GET request for %s", url)
 resp, err := http.Get(url)
 if err != nil {
  sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
 } else {
  sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url)
  resp.Body.Close()
 }
}

func InitLogger() {
 encoder := getEncoder()

 //两个interface,判断日志等级
 //warnlevel以下归到info日志
 infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  return lvl < zapcore.WarnLevel
 })
 //warnlevel及以上归到warn日志
 warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  return lvl >= zapcore.WarnLevel
 })

 infoWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/info")
 warnWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/warn")

 //创建zap.Core,for logger
 core := zapcore.NewTee(
  zapcore.NewCore(encoder, infoWriter, infoLevel),
  zapcore.NewCore(encoder, warnWriter, warnLevel),
 )
 //生成Logger
 logger := zap.New(core, zap.AddCaller())
 sugarLogger = logger.Sugar()
}

// getEncoder
func getEncoder() zapcore.Encoder {
 encoderConfig := zap.NewProductionEncoderConfig()
 encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
 encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
 return zapcore.NewConsoleEncoder(encoderConfig)
}

// 得到LogWriter
func getLogWriter(filePath string) zapcore.WriteSyncer {
 warnIoWriter := getWriter(filePath)
 return zapcore.AddSync(warnIoWriter)
}

// 日志文件切割
func getWriter(filename string) io.Writer {

 //保存日志30天,每1分钟分割一次日志
 hook, err := rotatelogs.New(
  filename+"_%Y-%m-%d %H:%M:%S.log",
  // 为最新的日志建立软连接,指向最新日志文件
  rotatelogs.WithLinkName(filename),

  // 清理条件: 将已切割的日志文件按条件(数量or时间)直接删除
  //--- MaxAge and RotationCount cannot be both set  两者不能同时设置
  //--- RotationCount用来设置最多切割的文件数(超过的会被 从旧到新 清理)
  //--- MaxAge 是设置文件清理前的最长保存时间 最小分钟为单位
  //--- if both are 0, give maxAge a default 7 * 24 * time.Hour
  // WithRotationCount和WithMaxAge两个选项不能共存,只能设置一个(都设置编译时不会出错,但运行时会报错。也是为了防止影响切分的处理逻辑)
  //rotatelogs.WithRotationCount(10),       // 超过这个数的文件会被清掉
  rotatelogs.WithMaxAge(time.Hour*24*30), // 保存多久(设置文件清理前的最长保存时间 最小分钟为单位)

  // 切分条件(将日志文件做切割;WithRotationTime and WithRotationSize ~~两者任意一个条件达到都会切割~~)
  // 经过亲测后发现,如果日志没有持续增加,WithRotationTime设置较小(如10s),并不会按WithRotationTime频次切分文件。当日志不停增加时,会按照WithRotationTime设置来切分(即便WithRotationTime设置的很小)
  rotatelogs.WithRotationTime(time.Second*10),     // 10秒分割一次(设置日志切割时间间隔,默认 24 * time.Hour)
  rotatelogs.WithRotationSize(int64(1*1024*1024*1024)), // 文件达到多大则进行切割,单位为 bytes;
 )
 if err != nil {
  panic(err)
 }
 return hook
}

验证其切分功能:


将触发切分的文件大小设置得很大(110241024*1024 Byte即1 GB),切分时间设置得较小(10秒分割一次),执行代码,可以观察到日志文件的变化:

alt
alt

再将触发切分的文件大小设置得很小(1102450 Byte即50 KB),切分时间设置得较大(24h分割一次),执行代码,清掉之前的日志,再观察到日志文件的变化:

alt
alt

将触发切分的文件大小设置得很小(1102435 Byte即35 KB),同时切分时间也设置得很小(10s分割一次),执行代码,清掉之前的日志,再观察到日志文件的变化:

alt

当前日志容量大于配置的容量时,会生成新的日志文件,如果时间一样,在时间后缀后面会自动加上一个数字后缀,以此区分同一时间的不同日志文件,如果时间不一样,则生成新的时间后缀文件 (golang实现分割日志[7])

日志文件中是会出现有的命中时间规则,有的命中文件大小规则的情况,两者命名格式不同,参考上图


切分之后执行压缩命令


alt

默认是没有的,不像lumberjack那样提供Compress选项

前面所提的还支持在轮换时压缩文件、删除旧文件、给文件添加时间戳等功能需要自己实现。 提供了一个WithHandler回调函数,发生切分后会触发该函数,可以在其中进项压缩等操作

alt

改一下代码(不再请求网站因为速度太慢,直接在for里面写日志)

不启用压缩:

alt

启用压缩,效果显著:

alt

相关代码:

package main

import (
 "archive/zip"
 "fmt"
 "io"
 "net/http"
 "os"
 "path/filepath"
 "reflect"
 "time"

 "github.com/davecgh/go-spew/spew"
 rotatelogs "github.com/lestrrat-go/file-rotatelogs"
 "go.uber.org/zap"
 "go.uber.org/zap/zapcore"
)

// 使用file-rotatelogs做切分

var sugarLogger *zap.SugaredLogger

func main() {
 fmt.Println("shuang提示:begin main")
 InitLogger()
 defer sugarLogger.Sync()

 for i := 0; i < 10000000; i++ {

  sugarLogger.Infof("测试压缩后少占用的空间,这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本,i is %d", i)

  //simpleHttpGet("www.cnblogs.com", i)
  //simpleHttpGet("https://www.baidu.com", i)
 }

 time.Sleep(10000e9)

}

// 例子,http访问url,返回状态
func simpleHttpGet(url string, i int) {
 //fmt.Println("begin simpleHttpGet:" + url)
 sugarLogger.Debugf("Trying to hit GET request for %s, i is %d", url, i)
 resp, err := http.Get(url)
 if err != nil {
  sugarLogger.Errorf("Error fetching URL %s : Error = %s, i is %d", url, err, i)
 } else {
  sugarLogger.Infof("Success! statusCode = %s for URL %s,i is %d", resp.Status, url, i)
  resp.Body.Close()
 }
}

func InitLogger() {
 encoder := getEncoder()

 //两个interface,判断日志等级
 //warnlevel以下归到info日志
 infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  return lvl < zapcore.WarnLevel
 })
 //warnlevel及以上归到warn日志
 warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  return lvl >= zapcore.WarnLevel
 })

 infoWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/info")
 warnWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/warn")

 //创建zap.Core,for logger
 core := zapcore.NewTee(
  zapcore.NewCore(encoder, infoWriter, infoLevel),
  zapcore.NewCore(encoder, warnWriter, warnLevel),
 )
 //生成Logger
 logger := zap.New(core, zap.AddCaller())
 sugarLogger = logger.Sugar()
}

// getEncoder
func getEncoder() zapcore.Encoder {
 encoderConfig := zap.NewProductionEncoderConfig()
 encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
 encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
 return zapcore.NewConsoleEncoder(encoderConfig)
}

// 得到LogWriter
func getLogWriter(filePath string) zapcore.WriteSyncer {
 warnIoWriter := getWriter(filePath)
 return zapcore.AddSync(warnIoWriter)
}

// 日志文件切割
func getWriter(filename string) io.Writer {

 //保存日志30天,每1分钟分割一次日志
 hook, err := rotatelogs.New(
  filename+"_%Y-%m-%d %H:%M:%S.log",
  // 为最新的日志建立软连接,指向最新日志文件
  rotatelogs.WithLinkName(filename),

  // 清理条件: 将已切割的日志文件按条件(数量or时间)直接删除
  //--- MaxAge and RotationCount cannot be both set  两者不能同时设置
  //--- RotationCount用来设置最多切割的文件数(超过的会被 从旧到新 清理)
  //--- MaxAge 是设置文件清理前的最长保存时间 最小分钟为单位
  //--- if both are 0, give maxAge a default 7 * 24 * time.Hour
  // WithRotationCount和WithMaxAge两个选项不能共存,只能设置一个(都设置编译时不会出错,但运行时会报错。也是为了防止影响切分的处理逻辑)
  //rotatelogs.WithRotationCount(10),       // 超过这个数的文件会被清掉
  rotatelogs.WithMaxAge(time.Hour*24*30), // 保存多久(设置文件清理前的最长保存时间 最小分钟为单位)

  // 切分条件(将日志文件做切割;WithRotationTime and WithRotationSize ~~两者任意一个条件达到都会切割~~)
  // 经过亲测后发现,如果日志没有持续增加,WithRotationTime设置较小(如10s),并不会按WithRotationTime频次切分文件。当日志不停增加时,会按照WithRotationTime设置来切分(即便WithRotationTime设置的很小)
  rotatelogs.WithRotationTime(time.Second*10),           // 10秒分割一次(设置日志切割时间间隔,默认 24 * time.Hour)
  rotatelogs.WithRotationSize(int64(1*1024*35000*1024)), // 文件达到多大则进行切割,单位为 bytes;

  // 其他可选配置
  //default: rotatelogs.Local ,you can set rotatelogs.UTC
  //rotatelogs.WithClock(rotatelogs.UTC),
  //rotatelogs.WithLocation(time.Local),
  //--- 当rotatelogs.New()创建的文件存在时,强制创建新的文件 命名为原文件的名称+序号,如a.log存在,则创建创建 a.log.1
  //rotatelogs.ForceNewFile(),

  rotatelogs.WithHandler(rotatelogs.Handler(rotatelogs.HandlerFunc(func(e rotatelogs.Event) {
   if e.Type() != rotatelogs.FileRotatedEventType {
    return
   }

   fmt.Println("切割完成,进行打包压缩操作")

   spew.Dump("e is:", e)

   prevFile := e.(*rotatelogs.FileRotatedEvent).PreviousFile()

   if prevFile != "" {
    // 进行压缩
    paths, fileName := filepath.Split(prevFile)
    //_ = paths
    //err := Zip("archive.zip", paths, prevFile)
    err := ZipFiles(paths+fileName+".zip", []string{prevFile})
    fmt.Println("err is", err)

    if err == nil {
     os.RemoveAll(prevFile)
    }

   }

   fmt.Println("e的类型为:", reflect.TypeOf(e))

   fmt.Println("------------------")
   fmt.Println()
   fmt.Println()
   fmt.Println()

   //ctx := CleanContext{
   // Dir:         LogsConfig.LogOutputDir,
   // DirMaxSizeG: LogsConfig.LogDirMaxSizeG,
   // DirMaxCount: LogsConfig.LogDirMaxFileCount,
   //}
   //strategyOne := CleanStrategyOne{}
   //result, err := NewCleanStrategy(&ctx, &strategyOne).
   // Clean().
   // Result()
   //Warn("文件切割,清理文件策略one已经执行完毕; 结果:%v; 错误:%v", result, err)
  }))),
 )

 if err != nil {
  panic(err)
 }
 return hook
}

// ZipFiles compresses one or many files into a single zip archive file.
// Param 1: filename is the output zip file's name.
// Param 2: files is a list of files to add to the zip.
func ZipFiles(filename string, files []string) error {

 newZipFile, err := os.Create(filename)
 if err != nil {
  return err
 }
 defer newZipFile.Close()

 zipWriter := zip.NewWriter(newZipFile)
 defer zipWriter.Close()

 // Add files to zip
 for _, file := range files {
  if err = AddFileToZip(zipWriter, file); err != nil {
   return err
  }
 }
 return nil
}

func AddFileToZip(zipWriter *zip.Writer, filename string) error {

 fileToZip, err := os.Open(filename)
 if err != nil {
  return err
 }
 defer fileToZip.Close()

 // Get the file information
 info, err := fileToZip.Stat()
 if err != nil {
  return err
 }

 header, err := zip.FileInfoHeader(info)
 if err != nil {
  return err
 }

 // Using FileInfoHeader() above only uses the basename of the file. If we want
 // to preserve the folder structure we can overwrite this with the full path.
 header.Name = filename

 // Change to deflate to gain better compression
 // see http://golang.org/pkg/archive/zip/#pkg-constants
 header.Method = zip.Deflate

 writer, err := zipWriter.CreateHeader(header)
 if err != nil {
  return err
 }
 _, err = io.Copy(writer, fileToZip)
 return err
}

//
 Zip compresses the specified files or dirs to zip archive.
 If a path is a dir don't need to specify the trailing path separator.
 For example calling Zip("archive.zip", "dir", "csv/baz.csv") will get archive.zip and the content of which is
 baz.csv
 dir
 ├── bar.txt
 └── foo.txt
 Note that if a file is a symbolic link it will be skipped.
//
 https://blog.csdn.net/K346K346/article/details/122441250
//func Zip(zipPath string, paths ...string) error {
// // Create zip file and it's parent dir.
// if err := os.MkdirAll(filepath.Dir(zipPath), os.ModePerm); err != nil {
//  return err
// }
// archive, err := os.Create(zipPath)
// if err != nil {
//  return err
// }
// defer archive.Close()
//
// // New zip writer.
// zipWriter := zip.NewWriter(archive)
// defer zipWriter.Close()
//
// // Traverse the file or directory.
// for _, rootPath := range paths {
//  // Remove the trailing path separator if path is a directory.
//  rootPath = strings.TrimSuffix(rootPath, string(os.PathSeparator))
//
//  // Visit all the files or directories in the tree.
//  err = filepath.Walk(rootPath, walkFunc(rootPath, zipWriter))
//  if err != nil {
//   return err
//  }
// }
// return nil
//}
//
//func walkFunc(rootPath string, zipWriter *zip.Writer) filepath.WalkFunc {
// return func(path string, info fs.FileInfo, err error) error {
//  if err != nil {
//   return err
//  }
//
//  // If a file is a symbolic link it will be skipped.
//  if info.Mode()&os.ModeSymlink != 0 {
//   return nil
//  }
//
//  // Create a local file header.
//  header, err := zip.FileInfoHeader(info)
//  if err != nil {
//   return err
//  }
//
//  // Set compression method.
//  header.Method = zip.Deflate
//
//  // Set relative path of a file as the header name.
//  header.Name, err = filepath.Rel(filepath.Dir(rootPath), path)
//  if err != nil {
//   return err
//  }
//  if info.IsDir() {
//   header.Name += string(os.PathSeparator)
//  }
//
//  // Create writer for the file header and save content of the file.
//  headerWriter, err := zipWriter.CreateHeader(header)
//  if err != nil {
//   return err
//  }
//  if info.IsDir() {
//   return nil
//  }
//  f, err := os.Open(path)
//  if err != nil {
//   return err
//  }
//  defer f.Close()
//  _, err = io.Copy(headerWriter, f)
//  return err
// }
//}


完整demo项目代码 以zap为例,展示如何切割日志文件。 使用Go生态两个使用最高的切分库[8]


关于压缩:

压缩解压文件[9]

Golang 学习笔记(五)- archive/zip 实现压缩及解压[10]

Golang zip 压缩与解压[11]




更多参考:

zap日志切割,同时支持按日期拆分,也支持按日志固定大小拆分,支持定时清理[12]

go-logrus 日志框架封装使用[13]

Go zap日志[14]

设计自用的golang日志模块[15]

golang log rotate file[16]

golang高性能日志库zap的使用[17]

参考资料

[1]

lumberjack: https://github.com/natefinch/lumberjack

[2]

golang zap日志库使用: https://segmentfault.com/a/1190000040443996

[3]

github.com/lestrrat-go/file-rotatelogs: https://github.com/lestrrat/go-file-rotatelogs

[4]

github.com/lestrrat-go/file-rotatelogs: https://github.com/lestrrat-go/file-rotatelogs

[5]

github.com/lestrrat/go-file-rotatelogs: https://github.com/lestrrat/go-file-rotatelogs

[6]

用zap和go-file-rotatelogs实现日志的记录和日志按时间分割: https://blog.csdn.net/weixin_43881017/article/details/110200176

[7]

golang实现分割日志: https://blog.csdn.net/qq_42119514/article/details/121372416

[8]

以zap为例,展示如何切割日志文件。 使用Go生态两个使用最高的切分库: https://github.com/cuishuang/zap-demo/tree/main

[9]

压缩解压文件: https://www.topgoer.com/%E5%85%B6%E4%BB%96/%E5%8E%8B%E7%BC%A9%E8%A7%A3%E5%8E%8B%E6%96%87%E4%BB%B6.html

[10]

Golang 学习笔记(五)- archive/zip 实现压缩及解压: https://learnku.com/articles/23434/golang-learning-notes-five-archivezip-to-achieve-compression-and-decompression

[11]

Golang zip 压缩与解压: https://blog.csdn.net/K346K346/article/details/122441250

[12]

zap日志切割,同时支持按日期拆分,也支持按日志固定大小拆分,支持定时清理: https://blog.csdn.net/qq_22186119/article/details/122003691

[13]

go-logrus 日志框架封装使用: https://www.jianshu.com/p/722250f0b609

[14]

Go zap日志: https://blog.csdn.net/qq_41004932/article/details/119760061

[15]

设计自用的golang日志模块: https://studygolang.com/articles/12537

[16]

golang log rotate file: https://juejin.cn/s/golang%20log%20rotate%20file

[17]

golang高性能日志库zap的使用: https://www.jianshu.com/p/910b626f67d9

本文由 mdnice 多平台发布

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

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

相关文章

uniapp实现地图点聚合

点聚合的最重要的一个地方是在 markers 中添加 joinCluster true 这个重要的属性&#xff0c;否则将无法开启点聚合功能。 其实在uniapp的官方文档里体现的不是那么清楚&#xff0c;但是在小程序文档提示的就相当清楚。 实现效果如下&#xff1a; 重点&#xff1a;需要编译在小…

【密码学】四、SM4分组密码算法

SM4分组密码算法 1、概述1.1初始变量算法1.2密钥扩展算法1.3轮函数F1.3.1合成置换T1.3.2S盒 2、算法设计原理2.1非平衡Feistel网络2.2T变换2.2.1非线性变换τ2.2.2线性变换L2.2.3基础置换 2.3密钥扩展算法的设计 1、概述 SM4分组密码算法是一种迭代分组密码算法&#xff0c;采…

SERDES关键技术

目录 一、SERDES介绍 二、SERDES关键技术 2.1 多重相位技术 2.2 线路编解码技术 2.2.1 8B/10B编解码 2.2.2 控制字符&#xff08;Control Characters&#xff09; 2.2.3 Comma检测 2.2.4 扰码&#xff08;Scrambling&#xff09; 2.2.5 4B/5B与64B/66B编解码技术 2.3 包传…

【C++】-二叉搜索树的详解(递归和非递归版本以及巧用引用)

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

LAXCUS分布式操作系统引领科技潮流,进入百度首页

信息源自某家网络平台&#xff0c;以下原样摘抄贴出。 随着科技的飞速发展&#xff0c;分布式操作系统做为通用基础平台&#xff0c;为大数据、高性能计算、人工智能提供了强大的数据和算力支持&#xff0c;已经成为了当今计算机领域的研究热点。近日&#xff0c;一款名为LAXCU…

一起学算法(栈篇)

1.栈的概念 1.栈的定义 栈是仅限在表尾进行插入和删除的线性表&#xff0c;栈又被称为先进后出的线性表&#xff0c;简称“LIFO” 我们这次用数组作为我们栈的底层数据结构&#xff0c;代码会放到结尾供大家参考使用 2.栈顶的定义 栈是一个线性表&#xff0c;我们允许插入…

Coremail中睿天下|2023年第二季度企业邮箱安全态势观察

7月24日&#xff0c;Coremail邮件安全联合中睿天下发布《2023第二季度企业邮箱安全性研究报告》&#xff0c;对2023第二季度和2023上半年的企业邮箱的安全风险进行了分析。 一、垃圾邮件同比下降16.38% 根据Coremail邮件安全人工智能实验室&#xff08;以下简称AI实验室&#…

【云原生-制品管理】制品管理的优势

制品介绍制品管理-DevOps制品管理优势总结 制品介绍 制品管理指的是存储、版本控制和跟踪在软件开发过程中产生的二进制文件或“制品”的过程。这些制品可以包括编译后的源代码、库和文档&#xff0c;包括操作包、NPM 和 Maven 包&#xff08;或像 Docker 这样的容器镜像&…

机器学习(一)---概述

文章目录 1.人工智能、机器学习、深度学习2.机器学习的工作流程2.1 获取数据集2.2 数据基本处理2.3 特征工程2.3.1 特征提取2.3.2 特征预处理2.3.3 特征降维 2.4 机器学习2.5 模型评估 3.机器学习的算法分类3.1 监督学习3.1.1 回归问题3.1.2 分类问题 3.2 无监督学习 1.人工智能…

【高级数据结构】并查集

目录 修复公路&#xff08;带扩展域的并查集&#xff09; 食物链&#xff08;带边权的并查集&#xff09; 修复公路&#xff08;带扩展域的并查集&#xff09; 洛谷&#xff1a;修复公路https://www.luogu.com.cn/problem/P1111 题目背景 A 地区在地震过后&#xff0c;连接…

数控机床主轴品牌选择及选型,如何维护和保养?

数控机床主轴品牌选择及选型&#xff0c;如何维护和保养&#xff1f; 数控机床是一种高精度、高效率、高自动化的机床。其中&#xff0c;主轴是数控机床的核心部件&#xff0c;承担着转动工件、切削加工的任务&#xff0c;决定了加工的转速、切削力度和加工效率。因此&#xff…

深空物联网通信中视频流的智能多路TCP拥塞控制|文献阅读|文献分析和学习|拥塞控制|MPTCP|SVC

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量博客汇总https://blog.csdn.net/yu_cblog/categ…

前端构建(打包)工具发展史

大多同学的前端学习路线&#xff1a;三件套框架慢慢延伸到其他&#xff0c;在这个过程中&#xff0c;有一个词出现的频率很高&#xff1a;webpack 。 作为一个很出名的前端构建工具我们在网上随便一搜&#xff0c;就会有各种教程&#xff1a;loader plugin entry吧啦吧啦。 但…

企业可以申请DV https证书吗

DV https证书是有基础认证的数字证书&#xff0c;所以DV https证书也可以叫DV基础型https证书。DV基础型https证书是众多https证书中既支持个人&#xff0c;也支持企事业单位申请的https证书&#xff0c;所以企事业单位都可以申请DV基础型https证书&#xff0c;不论是企业门户网…

边写代码边学习之卷积神经网络CNN

1. 卷积神经网络CNN 卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;是一种深度学习神经网络的架构&#xff0c;主要用于图像识别、图像分类和计算机视觉等任务。它是由多层神经元组成的神经网络&#xff0c;其中包含卷积层、池化层和全连接…

【数据结构】实验十:哈夫曼编码

实验十 哈夫曼编码 一、实验目的与要求 1&#xff09;掌握树、森林与二叉树的转换&#xff1b; 2&#xff09;掌握哈夫曼树和哈夫曼编码算法的实现&#xff1b; 二、 实验内容 1. 请编程实现如图所示的树转化为二叉树。 2. 编程实现一个哈夫曼编码系统&#xff0c;系统功能…

C语言预备

安装Visual studio 官方网址 https://visualstudio.microsoft.com/zh-hans/ 选择第一个社区版本&#xff08;免费&#xff09; 下载完成后打开安装包 安装完成后会自动打开程序选择c项目然后安装即可&#xff08;c兼容c&#xff09; 安装完成后启动程序注意这里需要注册也可…

scrapy框架简单实现豆瓣评分爬取案例

豆瓣网址&#xff1a;https://movie.douban.com/top250 1.创建scrapy框架 scrapy startproject 项目名(scrapy_test_one)创建好以后的目录是这样的 2.创建spider文件 在spiders目录下创建一个spider_one.py文件&#xff0c;可以随意命名&#xff0c;该文件主要是让我们进行数…

树、二叉树(C语言版)详解

&#x1f355;博客主页&#xff1a;️自信不孤单 &#x1f36c;文章专栏&#xff1a;数据结构与算法 &#x1f35a;代码仓库&#xff1a;破浪晓梦 &#x1f36d;欢迎关注&#xff1a;欢迎大家点赞收藏关注 文章目录 &#x1f34a;树的概念及结构1. 树的概念2. 树的相关概念3.树…

解决sonar的单元测试的覆盖率会为0问题

今天做项目遇到一个问题&#xff0c;明明做单元测试时覆盖率已经百分百了&#xff0c;然后传到Jenkin上&#xff0c;构建也成功了&#xff0c;但偏偏覆盖率就是为零&#xff0c;非常确定代码没有问题&#xff0c;所以唯一的问题就是出现在配置上了。 一开始的结果如下&#xf…