ビットマスクは、ビット単位の演算を使用してオプションのセットを表現および操作するプログラミングで使用される効率的かつ強力な手法です。この手法を使用すると、複数のブール状態を 1 つの数値に格納でき、各ビットが異なるオプションを表します。私はビットマスキングが広く使用されている PHP からプログラミングの旅を始めましたが、このテクニックは C や Java などの他の言語、さらには Go などのより現代的な言語でも同様に強力であることがわかりました。
この記事では、Go でビットマスキングを実装する方法を共有し、私の経験に基づいていくつかの実践例について説明します。
ビットマスクには、ビット単位の操作を使用してフラグまたはオプションのセットを管理することが含まれます。各オプションは整数値のビットで表されるため、データ圧縮を通じて複数のオプションを組み合わせて効率的にチェックできるため、メモリ領域が節約され、重要なプログラムのパフォーマンスが向上します。
ビットマスキングで使用される最も一般的なビット演算子は次のとおりです。
Service という構造のサンプル構成システムを使用して、Go でビットマスク実装を作成してみましょう。
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
さらに多くのフラグが必要な場合は、整数配列またはスライスを使用できます。各配列要素には、使用される整数のタイプ (int32 または int64) に応じて、32 または 64 のフラグを格納できます。
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 を使用して曜日を表す Enum 定数を定義する例
上記の例では、多くの機能を持たないサービスの 2 つのインスタンスを作成しました。これは、さまざまなフラグを適用する方法を示すためだけであり、コンストラクターで定義された値に従ってオプションが変更され、いくつかのブール値の必要性がなくなりました。フラグを設定し、展開可能な修飾子のセットを作成します。
ビットマスキングの典型的な使用例は、アクセス許可システムであり、さまざまなレベルのアクセス (読み取り、書き込み、実行) が異なるビットで表されます。
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)) }
この例では、複数の権限を 1 つの整数値に結合することでチェックすることがいかに簡単で効率的であるかがわかります。
削除や共有などの新しい権限を追加するとします。
定数に対して新しい権限を定義する必要があるだけです:
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 中国語 Web サイトの他の関連記事を参照してください。