mirror of
https://github.com/golang/go
synced 2024-11-22 00:14:42 -07:00
ZLIB deflater.
R=rsc APPROVED=rsc DELTA=222 (219 added, 0 deleted, 3 changed) OCL=35031 CL=35129
This commit is contained in:
parent
0ba28329e4
commit
c51e184538
@ -7,5 +7,6 @@ include $(GOROOT)/src/Make.$(GOARCH)
|
|||||||
TARG=compress/zlib
|
TARG=compress/zlib
|
||||||
GOFILES=\
|
GOFILES=\
|
||||||
reader.go\
|
reader.go\
|
||||||
|
writer.go\
|
||||||
|
|
||||||
include $(GOROOT)/src/Make.pkg
|
include $(GOROOT)/src/Make.pkg
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// The zlib package implements reading (and eventually writing) of
|
// The zlib package implements reading and writing of zlib
|
||||||
// zlib format compressed files, as specified in RFC 1950.
|
// format compressed files, as specified in RFC 1950.
|
||||||
package zlib
|
package zlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -87,6 +87,10 @@ func (z *reader) Read(p []byte) (n int, err os.Error) {
|
|||||||
|
|
||||||
// Calling Close does not close the wrapped io.Reader originally passed to NewInflater.
|
// Calling Close does not close the wrapped io.Reader originally passed to NewInflater.
|
||||||
func (z *reader) Close() os.Error {
|
func (z *reader) Close() os.Error {
|
||||||
return z.inflater.Close();
|
if z.err != nil {
|
||||||
|
return z.err;
|
||||||
|
}
|
||||||
|
z.err = z.inflater.Close();
|
||||||
|
return z.err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
src/pkg/compress/zlib/testdata/e.txt
vendored
Normal file
1
src/pkg/compress/zlib/testdata/e.txt
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/pkg/compress/zlib/testdata/pi.txt
vendored
Normal file
1
src/pkg/compress/zlib/testdata/pi.txt
vendored
Normal file
File diff suppressed because one or more lines are too long
107
src/pkg/compress/zlib/writer.go
Normal file
107
src/pkg/compress/zlib/writer.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package zlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/flate";
|
||||||
|
"hash";
|
||||||
|
"hash/adler32";
|
||||||
|
"io";
|
||||||
|
"os";
|
||||||
|
)
|
||||||
|
|
||||||
|
// These constants are copied from the flate package, so that code that imports
|
||||||
|
// "compress/zlib" does not also have to import "compress/flate".
|
||||||
|
const (
|
||||||
|
NoCompression = flate.NoCompression;
|
||||||
|
BestSpeed = flate.BestSpeed;
|
||||||
|
BestCompression = flate.BestCompression;
|
||||||
|
DefaultCompression = flate.DefaultCompression;
|
||||||
|
)
|
||||||
|
|
||||||
|
type writer struct {
|
||||||
|
w io.Writer;
|
||||||
|
deflater io.WriteCloser;
|
||||||
|
digest hash.Hash32;
|
||||||
|
err os.Error;
|
||||||
|
scratch [4]byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDeflater calls NewDeflaterLevel with the default compression level.
|
||||||
|
func NewDeflater(w io.Writer) (io.WriteCloser, os.Error) {
|
||||||
|
return NewDeflaterLevel(w, DefaultCompression);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDeflater creates a new io.WriteCloser that satisfies writes by compressing data written to w.
|
||||||
|
// It is the caller's responsibility to call Close on the WriteCloser when done.
|
||||||
|
// level is the compression level, which can be DefaultCompression, NoCompression,
|
||||||
|
// or any integer value between BestSpeed and BestCompression (inclusive).
|
||||||
|
func NewDeflaterLevel(w io.Writer, level int) (io.WriteCloser, os.Error) {
|
||||||
|
z := new(writer);
|
||||||
|
// ZLIB has a two-byte header (as documented in RFC 1950).
|
||||||
|
// The first four bits is the CINFO (compression info), which is 7 for the default deflate window size.
|
||||||
|
// The next four bits is the CM (compression method), which is 8 for deflate.
|
||||||
|
z.scratch[0] = 0x78;
|
||||||
|
// The next two bits is the FLEVEL (compression level). The four values are:
|
||||||
|
// 0=fastest, 1=fast, 2=default, 3=best.
|
||||||
|
// The next bit, FDICT, is unused, in this implementation.
|
||||||
|
// The final five FCHECK bits form a mod-31 checksum.
|
||||||
|
switch level {
|
||||||
|
case 0, 1:
|
||||||
|
z.scratch[1] = 0x01;
|
||||||
|
case 2, 3, 4, 5:
|
||||||
|
z.scratch[1] = 0x5e;
|
||||||
|
case 6, -1:
|
||||||
|
z.scratch[1] = 0x9c;
|
||||||
|
case 7, 8, 9:
|
||||||
|
z.scratch[1] = 0xda;
|
||||||
|
default:
|
||||||
|
return nil, os.NewError("level out of range");
|
||||||
|
}
|
||||||
|
_, err := w.Write(z.scratch[0:2]);
|
||||||
|
if err != nil {
|
||||||
|
return nil, err;
|
||||||
|
}
|
||||||
|
z.w = w;
|
||||||
|
z.deflater = flate.NewDeflater(w, level);
|
||||||
|
z.digest = adler32.New();
|
||||||
|
return z, nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *writer) Write(p []byte) (n int, err os.Error) {
|
||||||
|
if z.err != nil {
|
||||||
|
return 0, z.err;
|
||||||
|
}
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, nil;
|
||||||
|
}
|
||||||
|
n, err = z.deflater.Write(p);
|
||||||
|
if err != nil {
|
||||||
|
z.err = err;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
z.digest.Write(p);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calling Close does not close the wrapped io.Writer originally passed to NewDeflater.
|
||||||
|
func (z *writer) Close() os.Error {
|
||||||
|
if z.err != nil {
|
||||||
|
return z.err;
|
||||||
|
}
|
||||||
|
z.err = z.deflater.Close();
|
||||||
|
if z.err != nil {
|
||||||
|
return z.err;
|
||||||
|
}
|
||||||
|
checksum := z.digest.Sum32();
|
||||||
|
// ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952).
|
||||||
|
z.scratch[0] = uint8(checksum>>24);
|
||||||
|
z.scratch[1] = uint8(checksum>>16);
|
||||||
|
z.scratch[2] = uint8(checksum>>8);
|
||||||
|
z.scratch[3] = uint8(checksum>>0);
|
||||||
|
_, z.err = z.w.Write(z.scratch[0:4]);
|
||||||
|
return z.err;
|
||||||
|
}
|
||||||
|
|
105
src/pkg/compress/zlib/writer_test.go
Normal file
105
src/pkg/compress/zlib/writer_test.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package zlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io";
|
||||||
|
"os";
|
||||||
|
"testing";
|
||||||
|
)
|
||||||
|
|
||||||
|
var filenames = []string {
|
||||||
|
"testdata/e.txt",
|
||||||
|
"testdata/pi.txt",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that compressing and then decompressing the given file at the given compression level
|
||||||
|
// yields equivalent bytes to the original file.
|
||||||
|
func testFileLevel(t *testing.T, fn string, level int) {
|
||||||
|
// Read the file, as golden output.
|
||||||
|
golden, err := os.Open(fn, os.O_RDONLY, 0444);
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s (level=%d): %v", fn, level, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
defer golden.Close();
|
||||||
|
|
||||||
|
// Read the file again, and push it through a pipe that compresses at the write end, and decompresses at the read end.
|
||||||
|
raw, err := os.Open(fn, os.O_RDONLY, 0444);
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s (level=%d): %v", fn, level, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
piper, pipew := io.Pipe();
|
||||||
|
defer piper.Close();
|
||||||
|
go func() {
|
||||||
|
defer raw.Close();
|
||||||
|
defer pipew.Close();
|
||||||
|
zlibw, err := NewDeflaterLevel(pipew, level);
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s (level=%d): %v", fn, level, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
defer zlibw.Close();
|
||||||
|
var b [1024]byte;
|
||||||
|
for {
|
||||||
|
n, err0 := raw.Read(&b);
|
||||||
|
if err0 != nil && err0 != os.EOF {
|
||||||
|
t.Errorf("%s (level=%d): %v", fn, level, err0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_, err1 := zlibw.Write(b[0:n]);
|
||||||
|
if err1 == os.EPIPE {
|
||||||
|
// Fail, but do not report the error, as some other (presumably reportable) error broke the pipe.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if err1 != nil {
|
||||||
|
t.Errorf("%s (level=%d): %v", fn, level, err1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if err0 == os.EOF {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
zlibr, err := NewInflater(piper);
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s (level=%d): %v", fn, level, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
defer zlibr.Close();
|
||||||
|
|
||||||
|
// Compare the two.
|
||||||
|
b0, err0 := io.ReadAll(golden);
|
||||||
|
b1, err1 := io.ReadAll(zlibr);
|
||||||
|
if err0 != nil {
|
||||||
|
t.Errorf("%s (level=%d): %v", fn, level, err0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if err1 != nil {
|
||||||
|
t.Errorf("%s (level=%d): %v", fn, level, err1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if len(b0) != len(b1) {
|
||||||
|
t.Errorf("%s (level=%d): length mismatch %d versus %d", fn, level, len(b0), len(b1));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for i := 0; i < len(b0); i++ {
|
||||||
|
if b0[i] != b1[i] {
|
||||||
|
t.Errorf("%s (level=%d): mismatch at %d, 0x%02x versus 0x%02x\n", fn, level, i, b0[i], b1[i]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriter(t *testing.T) {
|
||||||
|
for _, fn := range filenames {
|
||||||
|
testFileLevel(t, fn, DefaultCompression);
|
||||||
|
testFileLevel(t, fn, NoCompression);
|
||||||
|
for level := BestSpeed; level <= BestCompression; level++ {
|
||||||
|
testFileLevel(t, fn, level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user