前言

在计算机世界里,日志文件记录了发生在操作系统或其他软件运行时的事件或状态。技术人员可以通过日志记录进而判断系统的运行状态,寻找导致系统出错、崩溃的成因等。这是我们分析程序问题常用的手段。在研究log日志文件之前,先来看看日志是什么。

Centos 7系统里/var/log

/var目录是所有服务的登录的文件或错误信息文件(LOG FILES)都在/var/log下,此外,一些数据库如MySQL则在/var/lib下,还有,用户未读的邮件的默认存放地点为/var/spool/mail


系统日志一般都存在/var/log下,常用的系统日志如下:

核心启动日志:/var/log/dmesg
系统报错日志:/var/log/messages
邮件系统日志:/var/log/maillog
FTP系统日志:/var/log/xferlog
安全信息和系统登录与网络连接的信息:/var/log/secure
登录记录:/var/log/wtmp 记录登录者讯录,二进制文件,须用last来读取内容 who -u /var/log/wtmp 查看信息
News日志:/var/log/spooler
引导日志:/var/log/boot.log 记录开机启动讯息,dmesg | more
cron(定制任务日志)日志:/var/log/cron

那么Golang打印的日志长什么样?下面开始进入本篇的正题。

Golang的log日志包

Golang源码的日志目录结构是这样的:
其中包含了两个包,一个是syslog系统日志包,另一个是log包。这里本篇主要以log包讲解。说到log包,直接看源码:

log.go

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.// Package log implements a simple logging package. It defines a type, Logger,
// with methods for formatting output. It also has a predefined 'standard'
// Logger accessible through helper functions Print[f|ln], Fatal[f|ln], and
// Panic[f|ln], which are easier to use than creating a Logger manually.
// That logger writes to standard error and prints the date and time
// of each logged message.
// Every log message is output on a separate line: if the message being
// printed does not end in a newline, the logger will add one.
// The Fatal functions call os.Exit(1) after writing the log message.
// The Panic functions call panic after writing the log message.
package logimport ("fmt""io""os""runtime""sync""time"
)// These flags define which text to prefix to each log entry generated by the Logger.
// Bits are or'ed together to control what's printed.
// There is no control over the order they appear (the order listed
// here) or the format they present (as described in the comments).
// The prefix is followed by a colon only when Llongfile or Lshortfile
// is specified.
// For example, flags Ldate | Ltime (or LstdFlags) produce,
//  2009/01/23 01:23:23 message
// while flags Ldate | Ltime | Lmicroseconds | Llongfile produce,
//  2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
const (Ldate         = 1 << iota     // the date in the local time zone: 2009/01/23Ltime                         // the time in the local time zone: 01:23:23Lmicroseconds                 // microsecond resolution: 01:23:23.123123.  assumes Ltime.Llongfile                     // full file name and line number: /a/b/c/d.go:23Lshortfile                    // final file name element and line number: d.go:23. overrides LlongfileLUTC                          // if Ldate or Ltime is set, use UTC rather than the local time zoneLstdFlags     = Ldate | Ltime // initial values for the standard logger
)// A Logger represents an active logging object that generates lines of
// output to an io.Writer. Each logging operation makes a single call to
// the Writer's Write method. A Logger can be used simultaneously from
// multiple goroutines; it guarantees to serialize access to the Writer.
type Logger struct {mu     sync.Mutex // ensures atomic writes; protects the following fieldsprefix string     // prefix to write at beginning of each lineflag   int        // propertiesout    io.Writer  // destination for outputbuf    []byte     // for accumulating text to write
}// New creates a new Logger. The out variable sets the
// destination to which log data will be written.
// The prefix appears at the beginning of each generated log line.
// The flag argument defines the logging properties.
func New(out io.Writer, prefix string, flag int) *Logger {return &Logger{out: out, prefix: prefix, flag: flag}
}// SetOutput sets the output destination for the logger.
func (l *Logger) SetOutput(w io.Writer) {l.mu.Lock()defer l.mu.Unlock()l.out = w
}var std = New(os.Stderr, "", LstdFlags)// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
func itoa(buf *[]byte, i int, wid int) {// Assemble decimal in reverse order.var b [20]bytebp := len(b) - 1for i >= 10 || wid > 1 {wid--q := i / 10b[bp] = byte('0' + i - q*10)bp--i = q}// i < 10b[bp] = byte('0' + i)*buf = append(*buf, b[bp:]...)
}// formatHeader writes log header to buf in following order:
//   * l.prefix (if it's not blank),
//   * date and/or time (if corresponding flags are provided),
//   * file and line number (if corresponding flags are provided).
func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {*buf = append(*buf, l.prefix...)if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {if l.flag&LUTC != 0 {t = t.UTC()}if l.flag&Ldate != 0 {year, month, day := t.Date()itoa(buf, year, 4)*buf = append(*buf, '/')itoa(buf, int(month), 2)*buf = append(*buf, '/')itoa(buf, day, 2)*buf = append(*buf, ' ')}if l.flag&(Ltime|Lmicroseconds) != 0 {hour, min, sec := t.Clock()itoa(buf, hour, 2)*buf = append(*buf, ':')itoa(buf, min, 2)*buf = append(*buf, ':')itoa(buf, sec, 2)if l.flag&Lmicroseconds != 0 {*buf = append(*buf, '.')itoa(buf, t.Nanosecond()/1e3, 6)}*buf = append(*buf, ' ')}}if l.flag&(Lshortfile|Llongfile) != 0 {if l.flag&Lshortfile != 0 {short := filefor i := len(file) - 1; i > 0; i-- {if file[i] == '/' {short = file[i+1:]break}}file = short}*buf = append(*buf, file...)*buf = append(*buf, ':')itoa(buf, line, -1)*buf = append(*buf, ": "...)}
}// Output writes the output for a logging event. The string s contains
// the text to print after the prefix specified by the flags of the
// Logger. A newline is appended if the last character of s is not
// already a newline. Calldepth is used to recover the PC and is
// provided for generality, although at the moment on all pre-defined
// paths it will be 2.
func (l *Logger) Output(calldepth int, s string) error {now := time.Now() // get this early.var file stringvar line intl.mu.Lock()defer l.mu.Unlock()if l.flag&(Lshortfile|Llongfile) != 0 {// Release lock while getting caller info - it's expensive.l.mu.Unlock()var ok bool_, file, line, ok = runtime.Caller(calldepth)if !ok {file = "???"line = 0}l.mu.Lock()}l.buf = l.buf[:0]l.formatHeader(&l.buf, now, file, line)l.buf = append(l.buf, s...)if len(s) == 0 || s[len(s)-1] != '\n' {l.buf = append(l.buf, '\n')}_, err := l.out.Write(l.buf)return err
}// Printf calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Printf.
func (l *Logger) Printf(format string, v ...interface{}) {l.Output(2, fmt.Sprintf(format, v...))
}// Print calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Print.
func (l *Logger) Print(v ...interface{}) { l.Output(2, fmt.Sprint(v...)) }// Println calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Println.
func (l *Logger) Println(v ...interface{}) { l.Output(2, fmt.Sprintln(v...)) }// Fatal is equivalent to l.Print() followed by a call to os.Exit(1).
func (l *Logger) Fatal(v ...interface{}) {l.Output(2, fmt.Sprint(v...))os.Exit(1)
}// Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1).
func (l *Logger) Fatalf(format string, v ...interface{}) {l.Output(2, fmt.Sprintf(format, v...))os.Exit(1)
}// Fatalln is equivalent to l.Println() followed by a call to os.Exit(1).
func (l *Logger) Fatalln(v ...interface{}) {l.Output(2, fmt.Sprintln(v...))os.Exit(1)
}// Panic is equivalent to l.Print() followed by a call to panic().
func (l *Logger) Panic(v ...interface{}) {s := fmt.Sprint(v...)l.Output(2, s)panic(s)
}// Panicf is equivalent to l.Printf() followed by a call to panic().
func (l *Logger) Panicf(format string, v ...interface{}) {s := fmt.Sprintf(format, v...)l.Output(2, s)panic(s)
}// Panicln is equivalent to l.Println() followed by a call to panic().
func (l *Logger) Panicln(v ...interface{}) {s := fmt.Sprintln(v...)l.Output(2, s)panic(s)
}// Flags returns the output flags for the logger.
func (l *Logger) Flags() int {l.mu.Lock()defer l.mu.Unlock()return l.flag
}// SetFlags sets the output flags for the logger.
func (l *Logger) SetFlags(flag int) {l.mu.Lock()defer l.mu.Unlock()l.flag = flag
}// Prefix returns the output prefix for the logger.
func (l *Logger) Prefix() string {l.mu.Lock()defer l.mu.Unlock()return l.prefix
}// SetPrefix sets the output prefix for the logger.
func (l *Logger) SetPrefix(prefix string) {l.mu.Lock()defer l.mu.Unlock()l.prefix = prefix
}// Writer returns the output destination for the logger.
func (l *Logger) Writer() io.Writer {l.mu.Lock()defer l.mu.Unlock()return l.out
}// SetOutput sets the output destination for the standard logger.
func SetOutput(w io.Writer) {std.mu.Lock()defer std.mu.Unlock()std.out = w
}// Flags returns the output flags for the standard logger.
func Flags() int {return std.Flags()
}// SetFlags sets the output flags for the standard logger.
func SetFlags(flag int) {std.SetFlags(flag)
}// Prefix returns the output prefix for the standard logger.
func Prefix() string {return std.Prefix()
}// SetPrefix sets the output prefix for the standard logger.
func SetPrefix(prefix string) {std.SetPrefix(prefix)
}// Writer returns the output destination for the standard logger.
func Writer() io.Writer {return std.Writer()
}// These functions write to the standard logger.// Print calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Print.
func Print(v ...interface{}) {std.Output(2, fmt.Sprint(v...))
}// Printf calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Printf.
func Printf(format string, v ...interface{}) {std.Output(2, fmt.Sprintf(format, v...))
}// Println calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Println.
func Println(v ...interface{}) {std.Output(2, fmt.Sprintln(v...))
}// Fatal is equivalent to Print() followed by a call to os.Exit(1).
func Fatal(v ...interface{}) {std.Output(2, fmt.Sprint(v...))os.Exit(1)
}// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
func Fatalf(format string, v ...interface{}) {std.Output(2, fmt.Sprintf(format, v...))os.Exit(1)
}// Fatalln is equivalent to Println() followed by a call to os.Exit(1).
func Fatalln(v ...interface{}) {std.Output(2, fmt.Sprintln(v...))os.Exit(1)
}// Panic is equivalent to Print() followed by a call to panic().
func Panic(v ...interface{}) {s := fmt.Sprint(v...)std.Output(2, s)panic(s)
}// Panicf is equivalent to Printf() followed by a call to panic().
func Panicf(format string, v ...interface{}) {s := fmt.Sprintf(format, v...)std.Output(2, s)panic(s)
}// Panicln is equivalent to Println() followed by a call to panic().
func Panicln(v ...interface{}) {s := fmt.Sprintln(v...)std.Output(2, s)panic(s)
}// Output writes the output for a logging event. The string s contains
// the text to print after the prefix specified by the flags of the
// Logger. A newline is appended if the last character of s is not
// already a newline. Calldepth is the count of the number of
// frames to skip when computing the file name and line number
// if Llongfile or Lshortfile is set; a value of 1 will print the details
// for the caller of Output.
func Output(calldepth int, s string) error {return std.Output(calldepth+1, s) // +1 for this frame.
}

log包是go语言提供的一个简单的日志记录功能,其中定义了一个结构体类型 Logger,是整个包的基础部分,包中的其他方法都是围绕这整个结构体创建的.

Logger结构

// A Logger represents an active logging object that generates lines of
// output to an io.Writer. Each logging operation makes a single call to
// the Writer's Write method. A Logger can be used simultaneously from
// multiple goroutines; it guarantees to serialize access to the Writer.
type Logger struct {mu     sync.Mutex // ensures atomic writes; protects the following fieldsprefix string     // prefix to write at beginning of each lineflag   int        // propertiesout    io.Writer  // destination for outputbuf    []byte     // for accumulating text to write
}

它表示一个活动的日志对象,给io.Writer生成多行输出。每次记录都简单地调用io.Writer的write方法。一个Logger可以被多个goroutines同步执行。

对各个成员含义解析:

mu :是sync.Mutex,它是一个同步互斥锁,用于保证日志记录的原子性.
prefix :是输入的日志每一行的前缀
flag :是一个标志,用于设置日志的打印格式
out :日志的输出目标,需要是一个实现了 io.Writer接口的对象,如: os.Stdout, os.Stderr, os.File等等
buf :用于缓存数据

flag可选值

在log包里首先定义了一些常量,它们是日志输出前缀的标识:

// These flags define which text to prefix to each log entry generated by the Logger.
// Bits are or'ed together to control what's printed.
// There is no control over the order they appear (the order listed
// here) or the format they present (as described in the comments).
// The prefix is followed by a colon only when Llongfile or Lshortfile
// is specified.
// For example, flags Ldate | Ltime (or LstdFlags) produce,
//  2009/01/23 01:23:23 message
// while flags Ldate | Ltime | Lmicroseconds | Llongfile produce,
//  2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
const (Ldate         = 1 << iota     // the date in the local time zone: 2009/01/23 //1 << 0 当地时区的日期: 2009/01/23Ltime                         // the time in the local time zone: 01:23:23 //1 << 1 当地时区的时间: 01:23:23Lmicroseconds                 // microsecond resolution: 01:23:23.123123.  assumes Ltime. //1 << 2 显示精度到微秒: 01:23:23.123123 (应该和Ltime一起使用)Llongfile                     // full file name and line number: /a/b/c/d.go:23   // 1 << 3 显示完整文件路径和行号: /a/b/c/d.go:23Lshortfile                    // final file name element and line number: d.go:23. overrides Llongfile  // 1 << 4 显示当前文件名和行号: d.go:23 (如果与Llongfile一起出现,此项优先) LUTC                          // if Ldate or Ltime is set, use UTC rather than the local time zone  // 1 << 5如果设置了Ldata或者Ltime, 最好使用 UTC 时间而不是当地时区LstdFlags     = Ldate | Ltime // initial values for the standard logger    // 标准日志器的初始值
)

这是log包定义的一些抬头信息,有日期、时间、毫秒时间、绝对路径和行号、文件名和行号等。

主要函数讲解

辅助函数

在 log 包中,定义了下面几组方法:

func (l *Logger) Printf(format string, v ...interface{})
func (l *Logger) Print(v ...interface{})
func (l *Logger) Println(v ...interface{}) func (l *Logger) Fatal(v ...interface{})
func (l *Logger) Fatalf(format string, v ...interface{})
func (l *Logger) Fatalln(v ...interface{})func (l *Logger) Panic(v ...interface{})
func (l *Logger) Panicf(format string, v ...interface{})
func (l *Logger) Panicln(v ...interface{})

即 Print*, Fatal*, Painc*, 这里方法结尾的 f 或者 ln 就跟 fmt.Print 的含义是相同的,因此上面这九个方法的使用方式其实与 fmt.Print/f/ln 是一样的。
fmt.Print/f/ln 示例:


package mainimport ("log"
)
func main() {log.Print("欢迎关注\n")log.Printf("微信公众号\n")log.Println("程序猿编码")
}

编译输出:

log.Fatal/f/ln 示例:

package mainimport ("fmt""log"
)func test() {fmt.Println("first defer")fmt.Println("second defer")
}func main() {defer test()log.Fatal("this is log fatal test\n")log.Fatalf("this is log fatalf test\n")log.Fatalln("this is log fatalln test\n")}

编译输出:
Panic 示例:


package mainimport ("fmt""log"
)func test(){fmt.Println("first defer")if err := recover(); err != nil {fmt.Println(err)}}func main() {defer test()log.Panic("this is log panic")defer func() {fmt.Println("second defer")}()
}

编译输出:

我们直接以没有 f 或 ln 的方法为例来看看三组方法的代码:

func (l *Logger) Print(v ...interface{}) { l.Output(2, fmt.Sprint(v...))
}func (l *Logger) Fatal(v ...interface{}) {l.Output(2, fmt.Sprint(v...))os.Exit(1)
}func (l *Logger) Panic(v ...interface{}) {s := fmt.Sprint(v...)l.Output(2, s)panic(s)
}

可以看到其实三个方法 都调用了接收者(也就是Logger类型的实例或指针)的 Output 方法。

New

// New creates a new Logger. The out variable sets the
// destination to which log data will be written.
// The prefix appears at the beginning of each generated log line.
// The flag argument defines the logging properties.
func New(out io.Writer, prefix string, flag int) *Logger {return &Logger{out: out, prefix: prefix, flag: flag}
}

New创建一个Logger。参数out设置日志信息写入的目的地。
参数prefix会添加到生成的每一条日志前面。参数flag定义日志的属性(时间、文件等等)。

SetOutput

// SetOutput sets the output destination for the logger.
func (l *Logger) SetOutput(w io.Writer) {l.mu.Lock()defer l.mu.Unlock()l.out = w
}

它用来将Logger的out域赋值为w。io.Writer是个接口类型,任何实现了方法Write(p []byte)(n int, err error)的类型都可以在这里使用。

itoa

// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
func itoa(buf *[]byte, i int, wid int) {// Assemble decimal in reverse order.var b [20]bytebp := len(b) - 1for i >= 10 || wid > 1 {wid--q := i / 10b[bp] = byte('0' + i - q*10)bp--i = q}// i < 10b[bp] = byte('0' + i)*buf = append(*buf, b[bp:]...)
}

有辅助函数,将整型转换为定长十进制ASCII码。赋予负数宽度来防止左侧补0(zero-padding)

Output

// Output writes the output for a logging event. The string s contains
// the text to print after the prefix specified by the flags of the
// Logger. A newline is appended if the last character of s is not
// already a newline. Calldepth is used to recover the PC and is
// provided for generality, although at the moment on all pre-defined
// paths it will be 2.
func (l *Logger) Output(calldepth int, s string) error {now := time.Now() // get this early.var file string//加锁,保证多goroutine下的安全var line intl.mu.Lock()defer l.mu.Unlock()//如果配置了获取文件和行号的话if l.flag&(Lshortfile|Llongfile) != 0 {// Release lock while getting caller info - it's expensive.l.mu.Unlock()var ok bool_, file, line, ok = runtime.Caller(calldepth)if !ok {file = "???"line = 0}//获取到行号等信息后,再加锁,保证安全l.mu.Lock()}//把我们的日志信息和设置的日志抬头进行拼接l.buf = l.buf[:0]l.formatHeader(&l.buf, now, file, line)l.buf = append(l.buf, s...)if len(s) == 0 || s[len(s)-1] != '\n' {l.buf = append(l.buf, '\n')}//输出拼接好的缓冲buf里的日志信息到目的地_, err := l.out.Write(l.buf)return err
}

在 Output 方法中,做了下面这些事情:

1、 获取当前事件
2、对 Logger实例进行加锁操作
3、 判断Logger的标志位是否包含 Lshortfile 或 Llongfile, 如果包含进入步骤4, 如果不包含进入步骤5
4、获取当前函数调用所在的文件和行号信息
5、格式化数据,并将数据写入到 l.out 中,完成输出
6、解锁操作

最后,来一个生成日志(log)文件例子,对上述的函数做一个总结:

package mainimport ("fmt""log""os"
)func Debug(logName string) {logFile, err := os.OpenFile(logName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)if err != nil {fmt.Printf("create ./test.log err : %v\n", err)}if logFile != nil {defer func(file *os.File) { file.Close() }(logFile)}debugLog := log.New(logFile, "[Debug]", log.Ldate)debugLog.SetPrefix("[Debug]")debugLog.SetFlags(log.Lshortfile)debugLog.Println("this is Debug log")
}
func Waring(logName string) {logFile, err := os.OpenFile(logName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)if err != nil {fmt.Printf("create ./test.log err : %v\n", err)}if logFile != nil {defer func(file *os.File) { file.Close() }(logFile)}debugLog := log.New(logFile, "[Waring]", log.Ldate)debugLog.SetPrefix("[Waring]")debugLog.SetFlags(log.Lshortfile)debugLog.Println("this is Waring log")
}func main() {logName := "./test.log"Debug(logName)Waring(logName)
}

编译输出:


(微信公众号【程序猿编码】)

(添加本人微信号,备注加群,进入程序猿编码交流群,领取学习资料,获取每日干货)

微信公众号【程序猿编码】,这里Linux c/c++ 、Python、Go语言、数据结构与算法、网络编程相关知识,常用的程序员工具。还有汇聚精炼每日时政、民生、文化、娱乐新闻简报,即刻知晓天下事!

参考:Go1.13.6 源代码

Go语言log日志包详解及使用相关推荐

  1. R语言文本挖掘tm包详解(附代码实现)

    文本挖掘相关介绍 1什么是文本挖掘 2NLP 3 分词 4 OCR 5 常用算法 6 文本挖掘处理流程 7 相应R包简介 8 文本处理 词干化stemming snowball包 记号化Tokeniz ...

  2. 10-R语言文本挖掘tm包详解

    0.美图 文本挖掘相关介绍 1.什么是文本挖掘 文本挖掘是 抽取有效.新颖.有用.可理解的.散布在文本文件中的有价值知识,并且利用这些知识更好地组织信息的过程. 在文本挖掘领域中,文本自动分类,判同, ...

  3. R语言图像处理EBImage包详解

    > 本文摘自<Keras深度学习:入门.实战及进阶>第四章部分章节. ## 什么是EBImage EBImage是R的一个扩展包,提供了用于读取.写入.处理和分析图像的通用功能,非常 ...

  4. R语言:ggplot2包详解及各类精美图形绘制

    文章目录 1.1 Ggplot2介绍 1.2 Ggplot2特点 默认值 迭代 高级元素 1.3 Ggplot2映射组件 layer Scale coord theme 1.4 Ggplot2自带数据 ...

  5. R语言机器学习之caret包详解(一)

    R语言机器学习caret包trainControl函数详解 R语言机器学习之caret包详解(一) 简介 数据预处理 各种数据变换 近零方差变量 创建虚拟变量 重抽样技术 k折交叉验证 留一交叉验证 ...

  6. dns日志级别 linux,linux下DNS服务器视图view及日志系统详解

    linux下DNS服务器视图view及日志系统详解DNS服务器ACL:在named.conf文件中定义ACL功能如同bash当中定义变量,便于后续引用 ACL格式: acl ACL名称 { IP地址1 ...

  7. linux日志配置含义,Linux操作系统中的日志功能详解

    日志系统将我们系统运行的每一个状况信息都使用文字记录下来,这些信息有助我们观察系统运行过程中正常状态和系统运行错误时快速定位错误位置的途径等;下面学习啦小编主要概述一下Linux操作系统中的日志功能. ...

  8. Log4j日志配置详解(Log4j2)

    Log4j日志配置详解 一.Log4j升级Log4j2 首先来说一下日志升级,log4j配置的变化,配置文件从log4j.xml变成了log4j2.xml,配置文件的内容也有很大不同,log file ...

  9. 项目log4j日志管理详解

    项目log4j日志管理详解 项目log4j日志管理详解 log4j日志系统在项目中重要性在这里就不再累述,我们在平时使用时如果没有特定要求,只需在log4j.properties文件中顶入输出级别就行 ...

  10. Java的常用日志技术详解(一)

    日志文件 日志文件是用于记录系统操作事件的文件集合. 日志文件它具有处理历史数据.诊断问题的追踪以及理解系统的活动等重要的作用. 日志种类 调试日志 调试程序,或者做一些状态的输出,便于我们查询程序的 ...

最新文章

  1. 基于Python的自动特征工程——教你如何自动创建机器学习特征
  2. 手机扫一扫就能“隔空移物”?AR炫酷新玩法,快来解锁新技能吧!
  3. 【转载】快速升职加薪的10个方法
  4. JZOJ 5609. 【NOI2018模拟3.28】Tree BZOJ 4919: [Lydsy1706月赛]大根堆
  5. 初级测试开发面试题_初级开发人员在编写单元测试时常犯的错误
  6. php 一句话木马、后门
  7. 复练-关于面试的科技树-简历的提升、问答环节
  8. linux fortran 内存不足,内存不够不用怕! 虚拟内存不足的十种解决办法
  9. python---pass和continue和break和exit()区别
  10. [原]超快速搞定linux的vnc
  11. jtds 连接mysql_JAVA 使用jtds 连接sql server数据库
  12. 烽火服务器查询服务器型号,烽火服务器应该起的进程
  13. C语言中心对称图形定义,中心对称图形的定义
  14. 计算机专业英语教学工作总结,2020大学英语教师上学期教学工作总结
  15. 微信小程序用户隐私保护指引设置怎么填?
  16. Jmeter录制手机app脚本
  17. 广义表的头尾链表存储表示(第五章 P115 算法5.5,5.6,5.8)
  18. R:数据分析-----汽车数据可视化
  19. Blackhat 2017Defcon 25学习笔记
  20. SVM学习总结(一)如何学习SVM

热门文章

  1. Kali exiftool图片信息工具
  2. 网页证书错误怎么回事? 证书错误如何解决
  3. 2021-2027中国高效空气过滤器市场现状及未来发展趋势
  4. 西北大学计算机学院考博真题,2014年西北大学西方经济学专业考博试题,真题解析,考博心得,复试真题,真题笔记...
  5. APM::Rover下GCS_MAVLink的逻辑梳理
  6. ttk progress bar的显示
  7. 微信会员卡如何为不同用户设置不同有效期?
  8. python迷宫问题代码_Python解决走迷宫问题算法示例
  9. MIRACL大数运算库使用手册
  10. win7电脑提升开机速度方法