GO 语言处理多个布尔选项的实现方案

一、需求背景

在 go 语言开发中,经常会遇到类似下面需要定义多个布尔类型参数的场景:

  • 日志打印方式配置参数,包含是否打印文件名、是否打印事件、是否打印函数名等
  • 打开文件的方式参数,包含是否只读、是否只写、是否创建、是否追加等

实现上述需求,常规的做法是定义多个布尔类型的参数,如:

Go 复制代码
// 文件操作配置类
type Config struct {
    IsReadOnly   bool
    IsWriteOnly  bool
    IsCreate     bool
    // ... 每个标志都需要一个字段
}

上述方案的缺点:

  • 占用内存多
  • 不便于传参

二、实现方案

2.1 方案思路

在 go 语言中,一种更高效的实现方案是通过定义常量并使用位运算组合多个布尔选项,每个选项对应一个二进制位(bit),即一个布尔标志

Go 复制代码
// 文件操作类型常量,按 2 的幂次赋值,每个类型占用一个二进制中的位
const (
    O_RDONLY = 1 << iota // 1 (0b0001)
    O_WRONLY             // 2 (0b0010)
    O_CREAT              // 4 (0b0100)
)

// 文件操作配置类
type Config struct {
    Flags int // 文件操作类型标识位
}

// 设置多个标志
config.Flags = O_CREAT | O_WRONLY

// 清除标识
config.Flags &^= O_CREAT  // 清除 O_CREAT 标志

func Process(flags int) {
    // 检查多个标志
    if flags & (O_CREAT | O_WRONLY) != 0 {
        // 处理逻辑...
    }

    // ...    
}

2.2 工作原理

假设 Flags = 3(二进制 00000011),则 Flags & O_WRONLY 的结果如下:

00000011 (Flags = 3)

& 00000010 (O_WRONLY = 2)


00000010 (结果 = 2)

结果不为 0, 即 Flags & O_WRONLY != 0,说明标识位中含有 O_WRONLY

2.3 方案优点

①高效存储:使用一个整数(如uint8, uint16, uint32等)的各个二进制位来存储多个布尔标志,可以节省内存。例如,一个32位整数可以存储32个不同的标志,而如果每个标志都用一个bool变量(通常占1个字节)存储,则需要32字节。

②快速组合和检查:可以同时设置和检查多个标志。

Go 复制代码
// 同时设置多个标志
config.Flags = O_CREAT | O_WRONLY

③性能高:位运算CPU原生支持的操作,因此检查标志位的速度很快,比哈希表查找、字符串比较等快得多。

④便于参数传递:由于所有标志都存储在一个整数中,因此可以方便地作为函数参数传递、存储在结构体中或序列化到文件中。

三、实际案例

3.1 GO 源码中文件操作配置的相关源码

Go 复制代码
// 源码位置:src/os/file.go

// Flags to OpenFile wrapping those of the underlying system. Not all
// flags may be implemented on a given system.
const (
	// Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
	O_RDONLY int = syscall.O_RDONLY // open the file read-only.
	O_WRONLY int = syscall.O_WRONLY // open the file write-only.
	O_RDWR   int = syscall.O_RDWR   // open the file read-write.
	// The remaining values may be or'ed in to control behavior.
	O_APPEND int = syscall.O_APPEND // append data to the file when writing.
	O_CREATE int = syscall.O_CREAT  // create a new file if none exists.
	O_EXCL   int = syscall.O_EXCL   // used with O_CREATE, file must not exist.
	O_SYNC   int = syscall.O_SYNC   // open for synchronous I/O.
	O_TRUNC  int = syscall.O_TRUNC  // truncate regular writable file when opened.
)

const (
	// Invented values to support what package os expects.
	O_RDONLY       = 0x00000
	O_WRONLY       = 0x00001
	O_RDWR         = 0x00002
	O_CREAT        = 0x00040
	O_EXCL         = 0x00080
	O_NOCTTY       = 0x00100
	O_TRUNC        = 0x00200
	O_NONBLOCK     = 0x00800
	O_APPEND       = 0x00400
	O_SYNC         = 0x01000
	O_ASYNC        = 0x02000
	O_CLOEXEC      = 0x80000
	o_DIRECTORY    = 0x100000   // used by internal/syscall/windows
	o_NOFOLLOW_ANY = 0x20000000 // used by internal/syscall/windows
	o_OPEN_REPARSE = 0x40000000 // used by internal/syscall/windows
)

// 源码位置:src/syscall/syscall_windows.go
func Open(name string, flag int, perm uint32) (fd Handle, err error) {
	if len(name) == 0 {
		return InvalidHandle, ERROR_FILE_NOT_FOUND
	}
	namep, err := UTF16PtrFromString(name)
	if err != nil {
		return InvalidHandle, err
	}
	var access uint32
	switch flag & (O_RDONLY | O_WRONLY | O_RDWR) {
	case O_RDONLY:
		access = GENERIC_READ
	case O_WRONLY:
		access = GENERIC_WRITE
	case O_RDWR:
		access = GENERIC_READ | GENERIC_WRITE
	}
	if flag&O_CREAT != 0 {
		access |= GENERIC_WRITE
	}

    // ...

	return h, nil
}

3.2 goframe 框架中记录日志内容的配置项相关源码

Go 复制代码
// 源码位置:pkg/mod/github.com/gogf/gf@v1.16.9/os/glog/glog_logger.go

// 设置日志打印选项常量值
const (
	F_ASYNC      = 1 << iota // Print logging content asynchronously。
	F_FILE_LONG              // Print full file name and line number: /a/b/c/d.go:23.
	F_FILE_SHORT             // Print final file name element and line number: d.go:23. overrides F_FILE_LONG.
	F_TIME_DATE              // Print the date in the local time zone: 2009-01-23.
	F_TIME_TIME              // Print the time in the local time zone: 01:23:23.
	F_TIME_MILLI             // Print the time with milliseconds in the local time zone: 01:23:23.675.
	F_CALLER_FN              // Print Caller function name and package: main.main
	F_TIME_STD   = F_TIME_DATE | F_TIME_MILLI
)


// 源码位置:pkg/mod/github.com/gogf/gf@v1.16.9/os/glog/glog_logger_config.go
// Config is the configuration object for logger.
type Config struct {
	Handlers             []Handler      `json:"-"`                    // Logger handlers which implement feature similar as middleware.
	Writer               io.Writer      `json:"-"`                    // Customized io.Writer.
    // 日志打印选项标识位
	Flags                int            `json:"flags"`                // Extra flags for logging output features.
    // ...
}

func DefaultConfig() Config {
	c := Config{
		File:                defaultFileFormat,
		Flags:               F_TIME_STD,  // 打印内容默认设置为 标准时间格式
		Level:               LEVEL_ALL,
		CtxKeys:             []interface{}{gctx.CtxKey},
		// ...
	}
	
    // ...

	return c
}

// SetAsync enables/disables async logging output feature.
func (l *Logger) SetAsync(enabled bool) {
	if enabled {
		l.config.Flags = l.config.Flags | F_ASYNC   // 设置 F_ASYNC 标识位
	} else {
		l.config.Flags = l.config.Flags & ^F_ASYNC  // 清除 F_ASYNC 标识位
	}
}

// 源码位置:pkg/mod/github.com/gogf/gf@v1.16.9/os/glog/glog_logger.go
// print prints `s` to defined writer, logging file or passed `std`.
func (l *Logger) print(ctx context.Context, level int, values ...interface{}) {
	// ...
	if l.config.HeaderPrint {
		// Time.
		timeFormat := ""
		if l.config.Flags&F_TIME_DATE > 0 {  // 判断日志打印选项标识位中是否含有 F_TIME_DATE
			timeFormat += "2006-01-02"
		}
		if l.config.Flags&F_TIME_TIME > 0 {
			if timeFormat != "" {
				timeFormat += " "
			}
			timeFormat += "15:04:05"
		}
		if l.config.Flags&F_TIME_MILLI > 0 {
			if timeFormat != "" {
				timeFormat += " "
			}
			timeFormat += "15:04:05.000"
		}

		// ...
	}

	// ...
}
相关推荐
毕设源码-钟学长1 小时前
【开题答辩全过程】以 基于Springboot vue肢体残疾人就业服务网站的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
lkbhua莱克瓦242 小时前
进阶-InnoDB引擎-后台线程
开发语言·mysql·innodb
源代码•宸2 小时前
Golang原理剖析(map面试与分析)
开发语言·后端·算法·面试·职场和发展·golang·map
WenJGo2 小时前
基于 IPIDEA 的 GitHub 代码文件抓取与数据可视化实践(Python 实现)
后端
黎雁·泠崖2 小时前
Java数组入门:定义+静态/动态初始化全解析(隐式转换+案例+避坑指南)
java·开发语言·python
m0_748252382 小时前
JavaScript 基本语法
开发语言·javascript·ecmascript
froginwe112 小时前
传输对象模式(Object Transfer Pattern)
开发语言
上进小菜猪2 小时前
基于 YOLOv8 的高压输电线路(绝缘子、电缆)故障自动识别 [目标检测完整源码]
后端