Rumah >pembangunan bahagian belakang >Golang >Bitmasking dalam Go: Teknik Berkuasa untuk Pengurusan Pilihan

Bitmasking dalam Go: Teknik Berkuasa untuk Pengurusan Pilihan

PHPz
PHPzasal
2024-07-18 13:06:40763semak imbas

pengenalan

Bitmasking ialah teknik yang cekap dan berkuasa yang digunakan dalam pengaturcaraan untuk mewakili dan memanipulasi set pilihan menggunakan operasi bitwise. Teknik ini membolehkan anda menyimpan berbilang keadaan Boolean dalam satu nilai angka, di mana setiap bit mewakili pilihan yang berbeza. Walaupun saya memulakan perjalanan pengaturcaraan saya dengan PHP, di mana bitmasking digunakan secara meluas, saya mendapati bahawa teknik ini adalah sama berkuasa dalam bahasa lain seperti C, Java dan juga dalam bahasa yang lebih moden seperti Go.

Dalam artikel ini, saya akan berkongsi cara melaksanakan bitmasking dalam Go dan membincangkan beberapa contoh praktikal berdasarkan pengalaman saya.

Konsep Asas

Apakah itu Bitmasking?

Bitmasking melibatkan penggunaan operasi bitwise untuk mengurus set bendera atau pilihan. Setiap pilihan diwakili oleh sedikit dalam nilai integer, membolehkan berbilang pilihan digabungkan dan disemak dengan cekap melalui pemampatan data, menjimatkan ruang memori dan meningkatkan prestasi program kritikal.

Operator Bitwise

Pengendali bitwise yang paling biasa digunakan dalam bitmasking ialah:

  • DAN (&): Digunakan untuk menyemak sama ada bit tertentu ditetapkan.
  • ATAU (|): Digunakan untuk menetapkan bit tertentu.
  • XOR (^): Digunakan untuk menogol bit tertentu.
  • BUKAN (~): Digunakan untuk menyongsangkan semua bit.

Pelaksanaan dalam Go

Mari kita buat pelaksanaan bitmasking dalam Go, menggunakan sistem konfigurasi contoh untuk struktur yang dipanggil Perkhidmatan.

Kami akan menggunakan jenis iota untuk menentukan pemalar pilihan, di mana setiap pemalar mewakili pilihan tertentu sebagai bit tunggal.

package main

import (
    "fmt"
)

type ServiceOption int

const (
    WITH_OPTION_A ServiceOption = 1 << iota
    WITH_OPTION_B
    WITH_OPTION_C
)

Tetapi berhati-hati, dengan jenis int kami hanya boleh menentukan maksimum 32 pilihan bendera. Oleh itu, apabila mentakrifkan bendera, ambil perhatian tentang kemungkinan pertumbuhan set ini.

Jika anda perlu mengatasi had 32 bendera yang dibenarkan oleh jenis int, anda boleh mempertimbangkan beberapa alternatif yang menyokong lebih banyak bit. Berikut adalah beberapa pilihan:

Integer 64-Bit

Dalam Go, anda boleh menggunakan jenis int64 untuk mewakili sehingga 64 bendera.

type ServiceOption int64
Gunakan Tatasusunan Integer

Jika anda memerlukan bilangan bendera yang lebih besar, anda boleh menggunakan tatasusunan atau hirisan integer. Setiap elemen tatasusunan boleh menyimpan 32 atau 64 bendera, bergantung pada jenis integer yang digunakan (int32 atau 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
}

Anda juga boleh membuat jenis tersuai yang menggunakan hirisan atau tatasusunan secara dalaman untuk menyimpan bit, tetapi ia menjadikan segala-galanya lebih kompleks, jadi saya menambah contoh pelaksanaan dalam Go Playground

Menetapkan bendera dalam struktur data

Apabila mentakrifkan bitmask kami, kami kini akan melampirkannya pada struktur yang dipanggil Perkhidmatan yang akan menyertakan medan bendera untuk menyimpan pilihan gabungan, kami akan menggunakan Bitwise| ATAU untuk menetapkan bit tertentu dalam penciptaan objek.

type Service struct {
    flags ServiceOption
}

func NewService(flags ...ServiceOption) *Service {
    var opts ServiceOption
    for _, flag := range flags {
        opts |= flag
    }
    return &Service{
        flags: opts,
    }
}
Menyemak sama ada bendera wujud dalam bitmask

Dengan pembina yang lengkap sekarang, kita hanya perlu mencipta cara untuk menyemak sama ada pilihan tertentu ditakrifkan, mari kita laksanakan kaedah HasOption dengan operator &AND bitwise untuk mengembalikan kewujudan bendera dalam bitmask bendera kita.

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

Sekarang contoh kami sudah lengkap, https://go.dev/play/p/rcHwLs-rUaA

Image description
Contoh penggunaan Iota untuk menentukan pemalar Enum yang mewakili sumber hari dalam seminggu

Contoh Penggunaan Dunia Sebenar

Dalam contoh di atas kami mencipta dua contoh perkhidmatan tanpa banyak fungsi, hanya untuk menunjukkan cara kami boleh menggunakan bendera yang berbeza dan dengan pilihan diubah suai mengikut nilai yang ditentukan dalam pembinanya, menghapuskan keperluan untuk beberapa Boolean bendera dan membuat set pengubah suai boleh dikembangkan.

Contoh klasik penggunaan bitmasking adalah dalam sistem kebenaran, di mana tahap akses yang berbeza (baca, tulis, laksana) diwakili oleh bit yang berbeza.

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

Dalam contoh ini, kita dapat melihat betapa mudah dan cekapnya untuk menyemak berbilang kebenaran dengan menggabungkannya menjadi satu nilai integer.
Katakan saya ingin memasukkan kebenaran baharu seperti Padam dan Kongsi,

Saya hanya perlu mentakrifkan kebenaran baharu untuk pemalar saya:

const (
    Read Permission = 1 << iota
    Write
    Execute
    Delete
    Share
)

Kebenaran ini masih boleh disimpan dalam pangkalan data contohnya

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.

Atas ialah kandungan terperinci Bitmasking dalam Go: Teknik Berkuasa untuk Pengurusan Pilihan. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn