Maison >développement back-end >Golang >Bitmasking in Go : une technique puissante pour la gestion des options
Le bitmasking est une technique efficace et puissante utilisée en programmation pour représenter et manipuler des ensembles d'options à l'aide d'opérations au niveau du bit. Cette technique vous permet de stocker plusieurs états booléens dans une seule valeur numérique, où chaque bit représente une option différente. Bien que j'ai commencé mon parcours de programmation avec PHP, où le masquage de bits est largement utilisé, j'ai découvert que cette technique est tout aussi puissante dans d'autres langages comme C, Java et même dans des langages plus modernes comme Go.
Dans cet article, je vais partager comment implémenter le masquage de bits dans Go et discuter de quelques exemples pratiques basés sur mon expérience.
Le masquage de bits implique l'utilisation d'opérations au niveau du bit pour gérer des ensembles d'indicateurs ou d'options. Chaque option est représentée par un bit dans une valeur entière, permettant de combiner et de vérifier efficacement plusieurs options grâce à la compression des données, économisant ainsi de l'espace mémoire et améliorant les performances des programmes critiques.
Les opérateurs au niveau du bit les plus couramment utilisés dans le masquage de bits sont :
Créons une implémentation de masquage de bits dans Go, en utilisant un exemple de système de configuration pour une structure appelée Service.
Nous utiliserons le type iota pour définir des constantes d'option, où chaque constante représente une option spécifique sous la forme d'un seul bit.
package main import ( "fmt" ) type ServiceOption int const ( WITH_OPTION_A ServiceOption = 1 << iota WITH_OPTION_B WITH_OPTION_C )
Mais attention, avec le type int on ne peut définir qu'un maximum de 32 options de flag. Par conséquent, lors de la définition d'un drapeau, soyez conscient de la possibilité de croissance de cet ensemble.
Si vous devez surmonter la limitation de 32 indicateurs autorisée par un type int, vous pouvez envisager des alternatives prenant en charge plus de bits. Voici quelques options :
Dans Go, vous pouvez utiliser le type int64 pour représenter jusqu'à 64 drapeaux.
type ServiceOption int64
Si vous avez besoin d'un nombre encore plus grand d'indicateurs, vous pouvez utiliser un tableau ou une tranche d'entiers. Chaque élément du tableau peut stocker 32 ou 64 drapeaux, selon le type d'entier utilisé (int32 ou 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 }
Vous pouvez également créer un type personnalisé qui utilise des tranches ou des tableaux en interne pour stocker des bits, mais cela rend tout un peu plus complexe, j'ai donc ajouté un exemple d'implémentation dans Go Playground
Lors de la définition de notre masque de bits, nous allons maintenant l'attacher à une structure appelée Service qui comprendra un champ flags pour stocker les options combinées, nous utiliserons le Bitwise| OU pour définir des bits spécifiques lors de la création d'objets.
type Service struct { flags ServiceOption } func NewService(flags ...ServiceOption) *Service { var opts ServiceOption for _, flag := range flags { opts |= flag } return &Service{ flags: opts, } }
Avec le constructeur complet, il nous suffit maintenant de créer un moyen de vérifier si une certaine option est définie, implémentons la méthode HasOption avec l'opérateur bitwise &AND pour renvoyer l'existence du drapeau dans notre masque de bits de drapeaux.
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)) }
Maintenant, notre exemple est terminé, https://go.dev/play/p/rcHwLs-rUaA
Exemple d'utilisation d'Iota pour définir des constantes Enum qui représentent les jours de la semaine source
Dans l'exemple ci-dessus, nous avons créé deux instances d'un service sans grande fonction, juste pour démontrer comment nous pouvons appliquer différents indicateurs et avec les options modifiées en fonction des valeurs définies dans son constructeur, éliminant ainsi le besoin de plusieurs booléens. drapeaux et création de l'ensemble des modificateurs extensibles.
Un exemple classique d'utilisation du masquage de bits est celui des systèmes d'autorisation, où différents niveaux d'accès (lecture, écriture, exécution) sont représentés par différents bits.
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)) }
Dans cet exemple, nous pouvons voir à quel point il est simple et efficace de vérifier plusieurs autorisations en les combinant en une seule valeur entière.
Supposons que je souhaite inclure de nouvelles autorisations telles que Supprimer et Partager,
J'ai juste besoin de définir de nouvelles autorisations pour mes constantes :
const ( Read Permission = 1 << iota Write Execute Delete Share )
Ces autorisations peuvent toujours être stockées dans une base de données par exemple
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.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!