Skip to content

Commit 1d4651b

Browse files
Merge pull request #21763 from giuseppe/sparse-file
machine: add sparse file writer
2 parents d9c706e + 0b86135 commit 1d4651b

2 files changed

Lines changed: 242 additions & 0 deletions

File tree

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package compression
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"io"
7+
)
8+
9+
type state int
10+
11+
const (
12+
zerosThreshold = 1024
13+
14+
stateData = iota
15+
stateZeros
16+
)
17+
18+
type WriteSeekCloser interface {
19+
io.Closer
20+
io.WriteSeeker
21+
}
22+
23+
type sparseWriter struct {
24+
state state
25+
file WriteSeekCloser
26+
zeros int64
27+
lastIsZero bool
28+
}
29+
30+
func NewSparseWriter(file WriteSeekCloser) *sparseWriter {
31+
return &sparseWriter{
32+
file: file,
33+
state: stateData,
34+
zeros: 0,
35+
lastIsZero: false,
36+
}
37+
}
38+
39+
func (sw *sparseWriter) createHole() error {
40+
zeros := sw.zeros
41+
if zeros == 0 {
42+
return nil
43+
}
44+
sw.zeros = 0
45+
sw.lastIsZero = true
46+
_, err := sw.file.Seek(zeros, io.SeekCurrent)
47+
return err
48+
}
49+
50+
func findFirstNotZero(b []byte) int {
51+
for i, v := range b {
52+
if v != 0 {
53+
return i
54+
}
55+
}
56+
return -1
57+
}
58+
59+
// Write writes data to the file, creating holes for long sequences of zeros.
60+
func (sw *sparseWriter) Write(data []byte) (int, error) {
61+
written, current := 0, 0
62+
totalLen := len(data)
63+
for current < len(data) {
64+
switch sw.state {
65+
case stateData:
66+
nextZero := bytes.IndexByte(data[current:], 0)
67+
if nextZero < 0 {
68+
_, err := sw.file.Write(data[written:])
69+
sw.lastIsZero = false
70+
return totalLen, err
71+
} else {
72+
current += nextZero
73+
sw.state = stateZeros
74+
}
75+
case stateZeros:
76+
nextNonZero := findFirstNotZero(data[current:])
77+
if nextNonZero < 0 {
78+
// finish with a zero, flush any data and keep track of the zeros
79+
if written != current {
80+
if _, err := sw.file.Write(data[written:current]); err != nil {
81+
return -1, err
82+
}
83+
sw.lastIsZero = false
84+
}
85+
sw.zeros += int64(len(data) - current)
86+
return totalLen, nil
87+
}
88+
// do not bother with too short sequences
89+
if sw.zeros == 0 && nextNonZero < zerosThreshold {
90+
sw.state = stateData
91+
current += nextNonZero
92+
continue
93+
}
94+
if written != current {
95+
if _, err := sw.file.Write(data[written:current]); err != nil {
96+
return -1, err
97+
}
98+
sw.lastIsZero = false
99+
}
100+
sw.zeros += int64(nextNonZero)
101+
current += nextNonZero
102+
if err := sw.createHole(); err != nil {
103+
return -1, err
104+
}
105+
written = current
106+
}
107+
}
108+
return totalLen, nil
109+
}
110+
111+
// Close closes the SparseWriter's underlying file.
112+
func (sw *sparseWriter) Close() error {
113+
if sw.file == nil {
114+
return errors.New("file is already closed")
115+
}
116+
if err := sw.createHole(); err != nil {
117+
sw.file.Close()
118+
return err
119+
}
120+
if sw.lastIsZero {
121+
if _, err := sw.file.Seek(-1, io.SeekCurrent); err != nil {
122+
sw.file.Close()
123+
return err
124+
}
125+
if _, err := sw.file.Write([]byte{0}); err != nil {
126+
sw.file.Close()
127+
return err
128+
}
129+
}
130+
err := sw.file.Close()
131+
sw.file = nil
132+
return err
133+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package compression
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"io"
7+
"testing"
8+
)
9+
10+
type memorySparseFile struct {
11+
buffer bytes.Buffer
12+
pos int64
13+
}
14+
15+
func (m *memorySparseFile) Seek(offset int64, whence int) (int64, error) {
16+
var newPos int64
17+
switch whence {
18+
case io.SeekStart:
19+
newPos = offset
20+
case io.SeekCurrent:
21+
newPos = m.pos + offset
22+
case io.SeekEnd:
23+
newPos = int64(m.buffer.Len()) + offset
24+
default:
25+
return 0, errors.New("unsupported seek whence")
26+
}
27+
28+
if newPos < 0 {
29+
return 0, errors.New("negative position is not allowed")
30+
}
31+
32+
m.pos = newPos
33+
return newPos, nil
34+
}
35+
36+
func (m *memorySparseFile) Write(b []byte) (n int, err error) {
37+
if int64(m.buffer.Len()) < m.pos {
38+
padding := make([]byte, m.pos-int64(m.buffer.Len()))
39+
_, err := m.buffer.Write(padding)
40+
if err != nil {
41+
return 0, err
42+
}
43+
}
44+
45+
m.buffer.Next(int(m.pos) - m.buffer.Len())
46+
47+
n, err = m.buffer.Write(b)
48+
m.pos += int64(n)
49+
return n, err
50+
}
51+
52+
func (m *memorySparseFile) Close() error {
53+
return nil
54+
}
55+
56+
func testInputWithWriteLen(t *testing.T, input []byte, chunkSize int) {
57+
m := &memorySparseFile{}
58+
sparseWriter := NewSparseWriter(m)
59+
60+
for i := 0; i < len(input); i += chunkSize {
61+
end := i + chunkSize
62+
if end > len(input) {
63+
end = len(input)
64+
}
65+
_, err := sparseWriter.Write(input[i:end])
66+
if err != nil {
67+
t.Fatalf("Expected no error, got %v", err)
68+
}
69+
}
70+
err := sparseWriter.Close()
71+
if err != nil {
72+
t.Fatalf("Expected no error, got %v", err)
73+
}
74+
if !bytes.Equal(input, m.buffer.Bytes()) {
75+
t.Fatalf("Incorrect output")
76+
}
77+
}
78+
79+
func testInput(t *testing.T, inputBytes []byte) {
80+
currentLen := 1
81+
for {
82+
testInputWithWriteLen(t, inputBytes, currentLen)
83+
currentLen <<= 1
84+
if currentLen > len(inputBytes) {
85+
break
86+
}
87+
}
88+
}
89+
90+
func TestSparseWriter(t *testing.T) {
91+
testInput(t, []byte("hello"))
92+
testInput(t, append(make([]byte, 100), []byte("hello")...))
93+
testInput(t, []byte(""))
94+
95+
// add "hello" at the beginning
96+
largeInput := make([]byte, 1024*1024)
97+
copy(largeInput, []byte("hello"))
98+
testInput(t, largeInput)
99+
100+
// add "hello" at the end
101+
largeInput = make([]byte, 1024*1024)
102+
copy(largeInput[1024*1024-5:], []byte("hello"))
103+
testInput(t, largeInput)
104+
105+
// add "hello" in the middle
106+
largeInput = make([]byte, 1024*1024)
107+
copy(largeInput[len(largeInput)/2:], []byte("hello"))
108+
testInput(t, largeInput)
109+
}

0 commit comments

Comments
 (0)