Maison >développement back-end >Golang >Bitmasking in Go : une technique puissante pour la gestion des options

Bitmasking in Go : une technique puissante pour la gestion des options

PHPz
PHPzoriginal
2024-07-18 13:06:40763parcourir

Introduction

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.

Concepts de base

Qu’est-ce que le masquage de bits ?

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.

Opérateurs au niveau du bit

Les opérateurs au niveau du bit les plus couramment utilisés dans le masquage de bits sont :

  • AND (&) : Utilisé pour vérifier si un bit spécifique est défini.
  • OR (|) : utilisé pour définir des bits spécifiques.
  • XOR (^) : utilisé pour basculer des bits spécifiques.
  • NON (~) : Utilisé pour inverser tous les bits.

Implémentation en Go

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 :

Entiers 64 bits

Dans Go, vous pouvez utiliser le type int64 pour représenter jusqu'à 64 drapeaux.

type ServiceOption int64
Utiliser un tableau d'entiers

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

Attribution d'indicateurs dans la structure de données

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,
    }
}
Vérifier si un indicateur existe dans le masque de bits

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

Image description
Exemple d'utilisation d'Iota pour définir des constantes Enum qui représentent les jours de la semaine source

Exemples d'utilisation du monde réel

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.

Exemplo permissões de sistema

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))
}

Um exemplo um pouco mais avançado...

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).

Desvantagens dessa abodagem

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.

  • Complexidade: Bitmasking pode ser confuso para programadores iniciantes ou para aqueles que não estão familiarizados com operações bitwise. A manipulação de bits diretamente exige uma compreensão sólida de operações binárias.
  • Legibilidade do Código: O código que utiliza bitmasking pode ser menos legível e intuitivo em comparação com outras abordagens. Por exemplo, verificar se um bit específico está definido pode não ser tão claro quanto verificar um campo booleano em uma estrutura de banco de dados.
  • Manutenção: Remover as opções ou modificar opções existentes pode ser propenso a erros, especialmente se não houver documentação adequada ou se os valores dos bits não forem gerenciados cuidadosamente.
  • Limitações de Tamanho: Dependendo do tipo de dado utilizado (por exemplo, int), há um limite no número de flags que podem ser representadas. Por exemplo, um int de 32 bits pode representar até 32 flags diferentes. Isso pode ser uma limitação em sistemas que necessitam de um grande número de opções.
  • Erros Silenciosos: Erros na manipulação de bits podem ser difíceis de diagnosticar e podem não resultar em falhas imediatas ou óbvias. Por exemplo, definir ou limpar o bit errado pode alterar inadvertidamente múltiplas flags, levando a comportamentos inesperados que podem ser difíceis de rastrear.

Conclusão

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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn