io: add a BufferedWriter and supporting methods (#22265)

This commit is contained in:
Einar Hjortdal 2024-09-22 14:57:46 +02:00 committed by GitHub
parent 295807d7ef
commit 64d1770cf1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 220 additions and 0 deletions

87
vlib/io/buffered_writer.v Normal file
View file

@ -0,0 +1,87 @@
module io
pub struct BufferedWriter {
mut:
n int
wr Writer
pub mut:
buf []u8
}
@[params]
pub struct BufferedWriterConfig {
pub:
writer Writer
cap int = 128 * 1024
}
// new_buffered_writer creates a new BufferedWriter with the specified BufferedWriterConfig.
// Returns an error when cap is 0 or negative.
pub fn new_buffered_writer(o BufferedWriterConfig) !&BufferedWriter {
if o.cap < 1 {
return error('`o.cap` must be a positive integer')
}
return &BufferedWriter{
buf: []u8{len: o.cap}
wr: o.writer
}
}
// reset resets the buffer to its initial state.
pub fn (mut b BufferedWriter) reset() {
cap := b.buf.len
b.buf = []u8{len: cap}
b.n = 0
}
// buffered returns the number of bytes currently stored in the buffer.
pub fn (b BufferedWriter) buffered() int {
return b.n
}
// flush writes the buffered data to the underlying writer and clears the buffer, ensures all data is
// written.
// Returns an error if the writer fails to write all buffered data.
pub fn (mut b BufferedWriter) flush() ! {
if b.buffered() == 0 {
return
}
n := b.wr.write(b.buf[0..b.n])!
if n < b.n {
return error('Writer accepted less bytes than expected without returning any explicit error.')
}
b.n = 0
return
}
// available returns the amount of available space left in the buffer.
pub fn (b BufferedWriter) available() int {
return b.buf.len - b.n
}
// write writes `src` in the buffer, flushing it to the underlying writer as needed, and returns the
// number of bytes written.
pub fn (mut b BufferedWriter) write(src []u8) !int {
mut p := src.clone()
mut nn := 0
for p.len > b.available() {
mut n := 0
if b.buffered() == 0 {
n = b.wr.write(p)!
} else {
n = copy(mut b.buf[b.n..], p)
b.n += n
b.flush()!
}
nn += n
p = p[n..].clone()
}
n := copy(mut b.buf[b.n..], p)
b.n += n
nn += n
return nn
}

View file

@ -0,0 +1,133 @@
import io
import rand
struct ArrayWriter {
pub mut:
result []u8
}
// implement io.Writer
fn (mut aw ArrayWriter) write(buf []u8) !int {
len := buf.len
mut res := 0
for i := 0; i < len; i++ {
aw.result << buf[i]
res++
}
return res
}
// write less than max bytes, returns number of bytes written
// data is written in chunks.
fn write_random_data(mut aw ArrayWriter, mut bw io.BufferedWriter, max int) !int {
less_than_max := max - 255 // guarantee 1 full u8 less than max
mut total := 0
for total < less_than_max {
r := rand.u8()
d := rand.bytes(r)!
w := bw.write(d)!
total += w
}
return total
}
fn test_flush() {
mut aw := ArrayWriter{}
max := 65536
mut bw := io.new_buffered_writer(writer: aw, cap: max)!
// write less data than buffer capacity, the underlying writer should receive no data.
written := write_random_data(mut aw, mut bw, 65536)!
assert written == bw.buffered()
assert aw.result.len == 0
bw.flush()!
assert aw.result.len == written
assert bw.buffered() == 0
assert bw.available() == max
}
fn test_write() {
mut aw := ArrayWriter{}
max := 65536
mut bw := io.new_buffered_writer(writer: aw, cap: max)!
// write less data than buffer capacity, the underlying writer should receive no data.
written := write_random_data(mut aw, mut bw, 65536)!
// now exceed buffer capacity by a little
little := rand.u8()
excess := bw.available() + little
excess_data := rand.bytes(excess)!
w := bw.write(excess_data)!
assert bw.buffered() == little
assert bw.available() == max - little
assert aw.result.len == max
}
fn test_write_big() {
mut aw := ArrayWriter{}
max := 65536
mut bw := io.new_buffered_writer(writer: aw, cap: max)!
more_than_max := max + rand.u8()
big_source := rand.bytes(more_than_max)!
w := bw.write(big_source)!
assert w == more_than_max
assert bw.buffered() == 0
assert bw.available() == max
assert aw.result.len == more_than_max
}
// create_data returns an array with `n` elements plus `\n` as last character.
fn create_data(n int) []u8 {
mut res := []u8{}
for i := 0; i < n; i++ {
res << `X`
}
res << `\n`
return res
}
fn test_simple_write() {
mut aw := ArrayWriter{}
mut bw := io.new_buffered_writer(writer: aw, cap: 10)!
mut data := create_data(6)
w1 := bw.write(data)!
// add data to buffer, less than cap
// data is written to buffer, not to underlying writer.
assert w1 == 7 // 6*x + \n
assert bw.buffered() == 7
assert aw.result.len == 0
// add more data, exceed cap
// data is flushed to underlying writer when buffer is full, then more data is written to buffer.
w2 := bw.write(data)!
assert w2 == 7
assert bw.buffered() == 4
assert aw.result.len == 10
// exceed cap immediately
// all data is written without buffering
aw.result = []u8{}
data = create_data(33)
bw.reset()
w3 := bw.write(data)!
assert bw.buffered() == 0
assert aw.result.len == 34 // 33*x + \n
}
fn test_simple_flush() {
mut aw := ArrayWriter{}
mut bw := io.new_buffered_writer(writer: aw, cap: 10)!
data := create_data(6)
w := bw.write(data)!
assert w == 7 // 6*x + \n
assert bw.buffered() == 7
bw.flush()!
assert bw.buffered() == 0
assert aw.result.len == 7
}