FilterOutputStream 和FilterInputStream

本文介绍如何使用Java自定义过滤输出流和过滤输入流实现文件的简单加密与解密过程。通过创建ScrambledOutputStream和ScrambledInputStream类,文章详细展示了如何在文件读写过程中进行字节的重新映射,实现数据的加密与解密。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

FilterOutputStream and FilterInputStream
Byte array, file, and piped streams pass bytes unchanged to their destinations. Java also supports
filter streams that buffer, compress/uncompress, encrypt/decrypt, or otherwise manipulate a

stream’s byte sequence (that is input to the filter) before it reaches its destination.

字节数组、文件和管道流传递未改变字节到目的地。Java还支持过滤-流缓、压缩/解压缩、加密/解密,或操作流的字节(即输入到过滤器的)序列后才到达目的地。


A filter output stream takes the data passed to its write() methods (the input stream), filters it, and
writes the filtered data to an underlying output stream, which might be another filter output stream or
a destination output stream such as a file output stream.
Filter output streams are created from subclasses of the concrete FilterOutputStream class, an
OutputStream subclass. FilterOutputStream declares a single FilterOutputStream(OutputStream

out) constructor that creates a filter output stream built on top of out, the underlying output stream.

Listing 11-13 reveals that it’s easy to subclass FilterOutputStream. At minimum, you declare
a constructor that passes its OutputStream argument to FilterOutputStream’s constructor and
override FilterOutputStream’s write(int) method.
Listing 11-13. Scrambling a Stream of Bytes
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class ScrambledOutputStream extends FilterOutputStream
{
private int[] map;
public ScrambledOutputStream(OutputStream out, int[] map)
{
super(out);
if (map == null)
throw new NullPointerException("map is null");
if (map.length != 256)
throw new IllegalArgumentException("map.length != 256");
this.map = map;
}
@Override
public void write(int b) throws IOException
{
out.write(map[b]);
}
}
Listing 11-13 presents a ScrambledOutputStream class that performs trivial encryption on its input
stream by scrambling the input stream’s bytes via a remapping operation. This constructor takes a
pair of arguments:
 out identifies the output stream on which to write the scrambled bytes.
 map identifies an array of 256 byte-integer values to which input stream bytes
map.
The constructor first passes its out argument to the FilterOutputStream parent via a super(out);
call. It then verifies its map argument’s integrity (map must be nonnull and have a length of 256: a byte
stream offers exactly 256 bytes to map) before saving map.
The write(int) method is trivial: it calls the underlying output stream’s write(int) method with the
byte to which argument b maps. FilterOutputStream declares out to be protected (for performance),
which is why I can directly access this field.
Note It’s only essential to override write(int) because FilterOutputStream’s other two write()
methods are implemented via this method.

Listing 11-14 presents the source code to a Scramble application for experimenting with scrambling a

catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
static int[] makeMap()
{
int[] map = new int[256];
for (int i = 0; i < map.length; i++)
map[i] = i;
// Shuffle map.
Random r = new Random(0);
for (int i = 0; i < map.length; i++)
{
int n = r.nextInt(map.length);
int temp = map[i];
map[i] = map[n];
map[n] = temp;
}
return map;
}
}
Scramble’s main() method first verifies the number of command-line arguments: the first argument
identifies the source path of the file with unscrambled content; the second argument identifies the
destination path of the file that stores scrambled content.
Assuming that two command-line arguments have been specified, main() instantiates
FileInputStream, creating a file input stream that’s connected to the file identified by args[0].
Continuing, main() instantiates FileOutputStream, creating a file output stream that’s connected to
the file identified by args[1]. It then instantiates ScrambledOutputStream and passes the
FileOutputStream instance to ScrambledOutputStream’s constructor.
Note When a stream instance is passed to another stream class’s constructor, the two streams are chained
together. For example, the scrambled output stream is chained to the file output stream.
main() now enters a loop, reading bytes from the file input stream and writing them to the scrambled
output stream by calling ScrambledOutputStream’s write(int) method. This loop continues until
FileInputStream’s read() method returns -1 (end of file).
The finally block closes the file input stream and scrambled output stream by calling their close()
methods. It doesn’t call the file output stream’s close() method because FilterOutputStream
automatically calls the underlying output stream’s close() method.
The makeMap() method is responsible for creating the map array that’s passed to
ScrambledOutputStream’s constructor. The idea is to populate the array with all 256 byte-integer
values, storing them in random order.
source file’s bytes via ScrambledOutputStream and writing these scrambled bytes to a destination file.
Listing 11-14. Scrambling a File’s Bytes
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;
public class Scramble
{
public static void main(String[] args)
{
if (args.length != 2)
{
System.err.println("usage: java Scramble srcpath destpath");
return;
}
FileInputStream fis = null;
ScrambledOutputStream sos = null;
try
{
fis = new FileInputStream(args[0]);
FileOutputStream fos = new FileOutputStream(args[1]);
sos = new ScrambledOutputStream(fos, makeMap());
int b;
while ((b = fis.read()) != -1)
sos.write(b);
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
finally
{
if (fis != null)
try
{
fis.close();
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
if (sos != null)
try
{
sos.close();
}

Note I pass 0 as the seed argument when creating the java.util.Random object in order to return


a predictable sequence of random numbers. I need to use the same sequence of random numbers when
creating the complementary map array in the Unscramble application, which I will present shortly.
Unscrambling will not work without the same sequence.
Suppose you have a simple 15-byte file named hello.txt that contains “Hello, World!” (followed
by a carriage return and a line feed). If you execute java Scramble hello.txt hello.out on a
Windows 7 platform, you’ll observe Figure 11-5’s scrambled output.
Figure 11-5. Different fonts yield different-looking scrambled output
A filter input stream takes the data obtained from its underlying input stream—which might be
another filter input stream or a source input stream such as a file input stream—filters it, and makes
this data available via its read() methods (the output stream).
Filter input streams are created from subclasses of the concrete FilterInputStream class, an
InputStream subclass. FilterInputStream declares a single FilterInputStream(InputStream in)
constructor that creates a filter input stream built on top of in, the underlying input stream.
Listing 11-15 shows that it’s easy to subclass FilterInputStream. At minimum, declare a
constructor that passes its InputStream argument to FilterInputStream’s constructor and override
FilterInputStream’s read() and read(byte[], int, int) methods.
Listing 11-15. Unscrambling a Stream of Bytes
import java.io.FilterInputStream;
import java.io.InputStream;
import java.io.IOException;
public class ScrambledInputStream extends FilterInputStream
{
private int[] map;

public ScrambledInputStream(InputStream in, int[] map)
{
super(in);
if (map == null)
throw new NullPointerException("map is null");
if (map.length != 256)
throw new IllegalArgumentException("map.length != 256");
this.map = map;
}
@Override
public int read() throws IOException
{
int value = in.read();
return (value == -1) ? -1 : map[value];
}
@Override
public int read(byte[] b, int off, int len) throws IOException
{
int nBytes = in.read(b, off, len);
if (nBytes <= 0)
return nBytes;
for (int i = 0; i < nBytes; i++)
b[off + i] = (byte) map[off + i];
return nBytes;
}
}
Listing 11-15 presents a ScrambledInputStream class that performs trivial decryption on its
underlying input stream by unscrambling the underlying input stream’s scrambled bytes via a
remapping operation.
The read() method first reads the scrambled byte from its underlying input stream. If the returned
value is -1 (end of file), this value is returned to its caller. Otherwise, the byte is mapped to its
unscrambled value, which is returned.
The read(byte[], int, int) method is similar to read(), but stores bytes read from the underlying
input stream in a byte array, taking an offset into this array and a length (number of bytes to read)
into account.
Once again, -1 might be returned from the underlying read() method call. If so, this value must be
returned. Otherwise, each byte in the array is mapped to its unscrambled value, and the number of
bytes read is returned.
Note It’s only essential to override read() and read(byte[], int, int) because
FilterInputStream’s read(byte[]) method is implemented via the latter method.

Listing 11-16 presents the source code to an Unscramble application for experimenting with
ScrambledInputStream by unscrambling a source file’s bytes and writing these unscrambled bytes to
a destination file.
Listing 11-16. Unscrambling a File’s Bytes
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;
public class Unscramble
{
public static void main(String[] args)
{
if (args.length != 2)
{
System.err.println("usage: java Unscramble srcpath destpath");
return;
}
ScrambledInputStream sis = null;
FileOutputStream fos = null;
try
{
FileInputStream fis = new FileInputStream(args[0]);
sis = new ScrambledInputStream(fis, makeMap());
fos = new FileOutputStream(args[1]);
int b;
while ((b = sis.read()) != -1)
fos.write(b);
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
finally
{
if (sis != null)
try
{
sis.close();
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
if (fos != null)
try
{
fos.close();
}

catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
static int[] makeMap()
{
int[] map = new int[256];
for (int i = 0; i < map.length; i++)
map[i] = i;
// Shuffle map.
Random r = new Random(0);
for (int i = 0; i < map.length; i++)
{
int n = r.nextInt(map.length);
int temp = map[i];
map[i] = map[n];
map[n] = temp;
}
int[] temp = new int[256];
for (int i = 0; i < temp.length; i++)
temp[map[i]] = i;
return temp;
}
}
Unscramble’s main() method first verifies the number of command-line arguments: the first argument
identifies the source path of the file with scrambled content; the second argument identifies the
destination path of the file that stores unscrambled content.
Assuming that two command-line arguments have been specified, main() instantiates
FileInputStream, creating a file input stream that’s connected to the file identified by args[1].
Continuing, main() instantiates FileInputStream, creating a file input stream that’s connected to the
file identified by args[0]. It then instantiates ScrambledInputStream, and passes the FileInputStream
instance to ScrambledInputStream’s constructor.
Note When a stream instance is passed to another stream class’s constructor, the two streams are chained
together. For example, the scrambled input stream is chained to the file input stream.
main() now enters a loop, reading bytes from the scrambled input stream and writing them to the file
output stream. This loop continues until ScrambledInputStream’s read() method returns -1 (end of file).
The finally block closes the scrambled input stream and file output stream by calling their close()
methods. It doesn’t call the file input stream’s close() method because FilterOutputStream
automatically calls the underlying input stream’s close() method.

The makeMap() method is responsible for creating the map array that’s passed to
ScrambledInputStream’s constructor. The idea is to duplicate Listing 11-14’s map array and then
invert it so that unscrambling can be performed.
Continuing from the previous hello.txt/hello.out example, execute java Unscramble hello.out
hello.bak and you’ll see the same unscrambled content in hello.bak that’s present in hello.txt.
Note For an additional example of a filter output stream and its complementary filter input stream, check
out the “Extending Java Streams to Support Bit Streams” article (www.drdobbs.com/184410423) on the
Dr. Dobb’s web site. This article introduces BitStreamOutputStream and BitStreamInputStream
classes that are useful for outputting and inputting bit streams. The article then demonstrates these classes
in a Java implementation of the Lempel-Zif-Welch (LZW) data compression and decompression algorithm.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值