Ring Buffer (Buffer Circular) em Zig — Implementação Completa
Um Ring Buffer (buffer circular) é uma estrutura de dados de tamanho fixo que funciona como se o final do array estivesse conectado ao início. Quando o buffer está cheio e um novo elemento é escrito, ele sobrescreve o mais antigo. É extremamente eficiente para filas de produtores/consumidores, streaming de dados, logs rotativos e comunicação entre threads.
Conceito
Ring Buffer com capacidade 6:
Estado após inserir A, B, C, D:
[A][B][C][D][ ][ ]
^ ^
leitura escrita
Após ler A e B:
[ ][ ][C][D][ ][ ]
^ ^
leit escrita
Após inserir E, F, G (wrap around):
[G][ ][C][D][E][F]
^ ^
es leit → Escrita voltou ao início!
Índices circulares: pos = (pos + 1) % capacidade
Implementação em Zig
const std = @import("std");
/// Ring Buffer genérico de tamanho fixo (comptime).
pub fn RingBuffer(comptime T: type, comptime capacidade: usize) type {
return struct {
const Self = @This();
dados: [capacidade]T = undefined,
leitura: usize = 0, // Índice do próximo elemento a ler
escrita: usize = 0, // Índice do próximo espaço a escrever
contagem: usize = 0, // Elementos atualmente no buffer
/// Escreve um elemento no buffer.
/// Retorna o elemento sobrescrito se o buffer estava cheio.
pub fn escrever(self: *Self, item: T) ?T {
var sobrescrito: ?T = null;
if (self.contagem == capacidade) {
sobrescrito = self.dados[self.leitura];
self.leitura = (self.leitura + 1) % capacidade;
} else {
self.contagem += 1;
}
self.dados[self.escrita] = item;
self.escrita = (self.escrita + 1) % capacidade;
return sobrescrito;
}
/// Lê e remove o elemento mais antigo.
pub fn ler(self: *Self) ?T {
if (self.contagem == 0) return null;
const item = self.dados[self.leitura];
self.leitura = (self.leitura + 1) % capacidade;
self.contagem -= 1;
return item;
}
/// Espia o próximo elemento sem removê-lo.
pub fn espiar(self: *const Self) ?T {
if (self.contagem == 0) return null;
return self.dados[self.leitura];
}
/// Verifica se está cheio.
pub fn cheio(self: *const Self) bool {
return self.contagem == capacidade;
}
/// Verifica se está vazio.
pub fn vazio(self: *const Self) bool {
return self.contagem == 0;
}
/// Retorna número de elementos.
pub fn tamanho(self: *const Self) usize {
return self.contagem;
}
/// Retorna espaço disponível.
pub fn espacoLivre(self: *const Self) usize {
return capacidade - self.contagem;
}
/// Limpa o buffer.
pub fn limpar(self: *Self) void {
self.leitura = 0;
self.escrita = 0;
self.contagem = 0;
}
/// Itera sobre os elementos (do mais antigo ao mais recente).
pub fn iterar(self: *const Self) Iterator {
return .{ .buffer = self, .pos = self.leitura, .restante = self.contagem };
}
pub const Iterator = struct {
buffer: *const Self,
pos: usize,
restante: usize,
pub fn next(it: *Iterator) ?T {
if (it.restante == 0) return null;
const item = it.buffer.dados[it.pos];
it.pos = (it.pos + 1) % capacidade;
it.restante -= 1;
return item;
}
};
};
}
/// Ring Buffer com alocação dinâmica.
pub fn RingBufferDinamico(comptime T: type) type {
return struct {
const Self = @This();
dados: []T,
leitura: usize,
escrita: usize,
contagem: usize,
capacidade: usize,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator, capacidade: usize) !Self {
return .{
.dados = try allocator.alloc(T, capacidade),
.leitura = 0,
.escrita = 0,
.contagem = 0,
.capacidade = capacidade,
.allocator = allocator,
};
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.dados);
}
pub fn escrever(self: *Self, item: T) ?T {
var sobrescrito: ?T = null;
if (self.contagem == self.capacidade) {
sobrescrito = self.dados[self.leitura];
self.leitura = (self.leitura + 1) % self.capacidade;
} else {
self.contagem += 1;
}
self.dados[self.escrita] = item;
self.escrita = (self.escrita + 1) % self.capacidade;
return sobrescrito;
}
pub fn ler(self: *Self) ?T {
if (self.contagem == 0) return null;
const item = self.dados[self.leitura];
self.leitura = (self.leitura + 1) % self.capacidade;
self.contagem -= 1;
return item;
}
};
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
// Ring buffer estático
var buf = RingBuffer(u8, 5){};
try stdout.writeAll("=== Ring Buffer (capacidade=5) ===\n");
// Escreve dados
for ("HELLO WORLD") |c| {
const sobrescrito = buf.escrever(c);
if (sobrescrito) |s| {
try stdout.print(" Escreveu '{c}', sobrescreveu '{c}'\n", .{ c, s });
} else {
try stdout.print(" Escreveu '{c}'\n", .{c});
}
}
try stdout.print("\nTamanho: {d}/{d}\n", .{ buf.tamanho(), @as(usize, 5) });
// Lê todos os elementos
try stdout.writeAll("Conteúdo: ");
var iter = buf.iterar();
while (iter.next()) |c| {
try stdout.print("{c}", .{c});
}
try stdout.writeAll("\n");
// Exemplo prático: log rotativo
try stdout.writeAll("\n=== Log Rotativo ===\n");
var log_buf = RingBuffer([]const u8, 4){};
const mensagens = [_][]const u8{
"Sistema iniciado",
"Conexão recebida",
"Query executada",
"Cache atualizado",
"Erro de timeout", // Vai sobrescrever a mais antiga
"Reconexão OK", // Vai sobrescrever a segunda mais antiga
};
for (mensagens) |msg| {
_ = log_buf.escrever(msg);
}
try stdout.writeAll("Últimas mensagens no log:\n");
var log_iter = log_buf.iterar();
while (log_iter.next()) |msg| {
try stdout.print(" > {s}\n", .{msg});
}
}
Análise de Complexidade
| Operação | Tempo | Espaço |
|---|---|---|
| Escrever | O(1) | O(1) |
| Ler | O(1) | O(1) |
| Espiar | O(1) | O(1) |
| Espaço total | — | O(n) |
Casos de Uso
- Audio/Video streaming — buffer de reprodução
- Logging — últimas N mensagens
- Comunicação entre threads — fila lock-free
- Rede — buffer de pacotes
- Sensores/IoT — últimas N leituras