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.
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.
Pengendali bitwise yang paling biasa digunakan dalam bitmasking ialah:
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:
Dalam Go, anda boleh menggunakan jenis int64 untuk mewakili sehingga 64 bendera.
type ServiceOption int64
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
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, } }
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
Contoh penggunaan Iota untuk menentukan pemalar Enum yang mewakili sumber hari dalam seminggu
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.
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.
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!