位元遮罩是一種高效且強大的程式設計技術,用於使用位元運算來表示和操作選項集。此技術可讓您在單一數值中儲存多個布林狀態,其中每個位元代表一個不同的選項。雖然我的程式設計之旅是從廣泛使用位元遮罩的 PHP 開始的,但我發現這種技術在其他語言(如 C、Java)甚至更現代的語言(如 Go)中同樣強大。
在這篇文章中,我將分享如何在 Go 中實現位元掩碼,並根據我的經驗討論一些實際範例。
位元遮罩涉及使用位元運算來管理標誌或選項集。每個選項都由整數值中的一位表示,允許透過資料壓縮有效地組合和檢查多個選項,節省記憶體空間並提高關鍵程式的效能。
位元遮罩中最常見的位元運算子是:
讓我們在 Go 中建立一個位元遮罩實現,使用名為 Service 的結構的範例配置系統。
我們將使用 iota 類型來定義選項常數,其中每個常數代表一個特定的選項。
package main import ( "fmt" ) type ServiceOption int const ( WITH_OPTION_A ServiceOption = 1 << iota WITH_OPTION_B WITH_OPTION_C )
但是要小心,對於 int 類型,我們最多只能定義 32 個標誌選項。因此,在定義標誌時,要注意該集合增長的可能性。
如果您需要克服 int 類型允許的 32 個標誌的限制,您可以考慮一些支援更多位元的替代方案。以下是一些選項:
在 Go 中,您可以使用 int64 類型來表示最多 64 個標誌。
type ServiceOption int64
如果您需要更多數量的標誌,您可以使用整數數組或切片。每個陣列元素可以儲存 32 或 64 個標誌,具體取決於所使用的整數類型(int32 或 int64)。
type ServiceOption int64 type ServiceOptions [2]int64 // 2 * 64 = 128 flags const ( WITH_OPTION_A ServiceOption = 1 << iota WITH_OPTION_B WITH_OPTION_C // Continue até 127 (2 * 64 - 1) ) func (p *ServiceOptions) Set(flag ServiceOption) { index := flag / 64 bit := flag % 64 p[index] |= 1 << bit } func (p *ServiceOptions) Clear(flag ServiceOption) { index := flag / 64 bit := flag % 64 p[index] &^= 1 << bit } func (p *ServiceOptions) Has(flag ServiceOption) bool { index := flag / 64 bit := flag % 64 return p[index]&(1<<bit) != 0 }
您也可以建立一個自訂類型,在內部使用切片或陣列來儲存位,但這會使一切變得更加複雜,所以我在 Go Playground 中新增了一個範例實作
定義位元遮罩時,我們現在將其附加到一個名為 Service 的結構,該結構將包含一個標誌欄位來儲存組合選項,我們將使用 Bitwise|或在物件建立中設定特定位元。
type Service struct { flags ServiceOption } func NewService(flags ...ServiceOption) *Service { var opts ServiceOption for _, flag := range flags { opts |= flag } return &Service{ flags: opts, } }
有了完整的建構函數,現在我們只需要建立一種方法來檢查是否定義了某個選項,讓我們使用位元&AND 運算子實作HasOption 方法,以傳回標誌位元遮罩中標誌的存在性。
func (s *Service) HasOption(flag ServiceOption) bool { return s.flags&flag != 0 } func main() { defaultService := NewService() fmt.Println("Default Service") fmt.Println("Has Option A:", defaultService.HasOption(WITH_OPTION_A)) fmt.Println("Has Option B:", defaultService.HasOption(WITH_OPTION_B)) modifiedService := NewService(WITH_OPTION_A | WITH_OPTION_B) fmt.Println("\nModified Service") fmt.Println("Has Option A:", modifiedService.HasOption(WITH_OPTION_A)) fmt.Println("Has Option B:", modifiedService.HasOption(WITH_OPTION_B)) }
現在我們的範例完成,https://go.dev/play/p/rcHwLs-rUaA
使用 Iota 定義表示星期幾的枚舉常數的範例來源
在上面的範例中,我們創建了兩個沒有太多功能的服務實例,只是為了演示如何應用不同的標誌並根據其構造函數中定義的值修改選項,從而消除對多個布林值的需要標誌並製作一組可擴展修飾符。
使用位元遮罩的一個典型範例是在權限系統中,其中不同層級的存取(讀取、寫入、執行)由不同的位元表示。
type Permission int const ( Read Permission = 1 << iota Write Execute ) type User struct { permissions Permission } func (u *User) HasPermission(p Permission) bool { return u.permissions&p != 0 } func main() { user := &User{permissions: Read | Write} fmt.Println("Can Read:", user.HasPermission(Read)) fmt.Println("Can Write:", user.HasPermission(Write)) fmt.Println("Can Execute:", user.HasPermission(Execute)) }
在這個範例中,我們可以看到透過將多個權限組合成一個整數值來檢查多個權限是多麼簡單和有效率。
假設我想新增權限,例如刪除和共享,
我只需要為我的常數定義新的權限:
const ( Read Permission = 1 << iota Write Execute Delete Share )
這些權限仍然可以儲存在資料庫中
Vamos assumir que temos uma tabela chamada users com um campo permissions que armazena o valor das permissões usando bitmask.
CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT, permissions INTEGER );
Como o bitmask é um inteiro, ele será armazenado no banco de dados de forma bem direta, sem muitas complicações, reduzindo tamanhos de tabelas e dados armazenados.
Um Porém cuidado, caso uma permissão seja renomeada ou movida de posição na constante irá mudar o valor inteiro, tornando initulizável o valor armazenado.
No exemplo acima a permissão Read | Write irá imprimir o valor inteiro 3. Porém vamos supor que você queira melhorar a legibilidade do seu código adicionando a primeira declaração do iota como um valor vazio, sumindo um usuário sem permissão alguma.
const ( _ Permission = 1 << iota Read Write Execute )
A permissão Read | Write agorá irá imprimir o valor 10 ao invés de 3.
Configurações de inicialização ou opções de sistema podem ser combinadas e verificadas usando bitmasking para determinar o comportamento do sistema.
type SystemOption int const ( EnableLogging SystemOption = 1 << iota EnableDebugging EnableMetrics ) type SystemConfig struct { options SystemOption } func (s *SystemConfig) IsEnabled(option SystemOption) bool { return s.options&option != 0 } func main() { config := &SystemConfig{options: EnableLogging | EnableMetrics} fmt.Println("Logging Enabled:", config.IsEnabled(EnableLogging)) fmt.Println("Debugging Enabled:", config.IsEnabled(EnableDebugging)) fmt.Println("Metrics Enabled:", config.IsEnabled(EnableMetrics)) }
O uso de bitwise e bitmasking pode ser encontrado em operações de gráficos computacionais, onde frequentemente manipulamos pixels e cores.
Em gráficos computacionais, as cores são frequentemente representadas por valores RGBA (Red, Green, Blue, Alpha), onde cada componente da cor é armazenado em um byte (8 bits). Podemos usar operações bitwise para manipular essas cores.
O exemplo abaixo mostra como um programa que inverte as cores de uma imagem usando operações bitwise.
package main import ( "image" "image/color" "image/draw" "image/jpeg" "image/png" "log" "os" ) // Inverte a cor de um pixel usando operações bitwise func invertColor(c color.Color) color.Color { r, g, b, a := c.RGBA() return color.RGBA{ R: uint8(^r >> 8), G: uint8(^g >> 8), B: uint8(^b >> 8), A: uint8(a >> 8), // Alpha não é invertido } } // Função para inverter as cores de uma imagem func invertImageColors(img image.Image) image.Image { bounds := img.Bounds() invertedImg := image.NewRGBA(bounds) draw.Draw(invertedImg, bounds, img, bounds.Min, draw.Src) for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { originalColor := img.At(x, y) invertedColor := invertColor(originalColor) invertedImg.Set(x, y, invertedColor) } } return invertedImg } func main() { // Abre o arquivo de imagem file, err := os.Open("input.png") if err != nil { log.Fatalf("failed to open: %s", err) } defer file.Close() // Decodifica a imagem img, err := png.Decode(file) if err != nil { log.Fatalf("failed to decode: %s", err) } // Inverte as cores da imagem invertedImg := invertImageColors(img) // Salva a imagem invertida outputFile, err := os.Create("output.png") if err != nil { log.Fatalf("failed to create: %s", err) } defer outputFile.Close() err = png.Encode(outputFile, invertedImg) if err != nil { log.Fatalf("failed to encode: %s", err) } log.Println("Image inversion completed successfully") }
Nesse código a invertColor recebe uma cor (color.Color) e inverte seus componentes RGB usando a operação bitwise NOT (^). O componente Alpha (A) não é invertido.
c.RGBA() retorna os componentes de cor como valores de 16 bits (0-65535), por isso os componentes são deslocados 8 bits para a direita (>> 8) para serem convertidos para a faixa de 8 bits (0-255).
Embora o bitmasking seja extremamente eficiente em termos de desempenho e uso de memória, suas desvantagens em termos de complexidade, legibilidade e manutenção devem ser cuidadosamente consideradas.
Bitmasking é uma técnica valiosa para representar e manipular conjuntos de opções de maneira eficiente. Em Go, essa técnica pode ser implementada de forma simples e eficaz, como demonstrado nos exemplos acima. Seja para sistemas de permissões, configurações de sistema ou estados de jogo, bitmasking oferece uma maneira poderosa de gerenciar múltiplas opções com operações bitwise rápidas e eficientes.
Para projetos onde a legibilidade e a facilidade de manutenção são prioridades, ou onde o número de opções é grande, outras técnicas, como estruturas de dados customizadas ou mapas, podem ser mais apropriadas. No entanto, para sistemas onde o desempenho é crítico e o número de opções é manejável, bitmasking continua sendo uma ferramenta poderosa e eficiente.
Se você está vindo de um background em PHP, C, Java ou qualquer outra linguagem, experimentar bitmasking em Go pode oferecer uma nova perspectiva, somando a eficiência e a simplicidade desta técnia ao arsenal de qualquer programador.
以上是Go 中的位元遮罩:選項管理的強大技術的詳細內容。更多資訊請關注PHP中文網其他相關文章!