Реализация собственных Input/Output Stream

от
Java

В Java очень богатый набор классов по работе с потоками: InputStream, DataInputStream, ByteArrayOutputStream, PrintStream и т.д. Каждый из них полезен в той или иной ситуации. Поэтому важно уметь работать с ними.

Например, нужно в игре защитить графику. Можно считать весь файл в массив, провести над ним операции по раскодированию и потом из массива байт создать картинку. Такой способ не очень хорош из-за повышенного потребления памяти и лишних операций. А почему бы нам сразу не читать байты уже раскодированными? Вот тут нам и поможет возможность создания своего Input/OutputStream'а.

Начнём с OutputStream. Первым делом, нужно создать свой класс, наследуемый от OutputStream. На вход он будет принимать существующий OutputStream.
  1. public class EncryptedOutputStream extends OutputStream {
  2.     
  3.     protected OutputStream os;
  4.     private byte key;
  5.     
  6.     public EncryptedOutputStream(OutputStream os, byte key) {
  7.         this.os = os;
  8.         this.key = key;
  9.     }
  10.  
  11.     public void write(int b) throws IOException {
  12.         os.write(b ^ key);
  13.     }
  14.  
  15.     // ...
  16.  
  17.     public void flush() throws IOException {
  18.         os.flush();
  19.     }
  20.  
  21.     public void close() throws IOException {
  22.         os.close();
  23.     }
  24. }

Для примера взят простейший метод шифрования, XOR, но при желании можно усложнить алгоритм.
Как видно, мы просто обернули переданный в конструктор OutputStream.

InputStream создаётся тем же способом. Учтите, алгоритм шифрования должен быть в этом случае обратный. Если в OutputStream мы прибавляли к каждому байту 5, то в InputStream нужно отнять 5 и т.д. В случае с XOR ничего делать не надо, он сам по себе инверсный.
  1. public class EncryptedInputStream extends InputStream {
  2.     
  3.     protected InputStream is;
  4.     private byte key;
  5.     
  6.     public EncryptedInputStream(InputStream is, byte key) {
  7.         this.is = is;
  8.         this.key = key;
  9.     }
  10.  
  11.     public int read() throws IOException {
  12.         return is.read() ^ key;
  13.     }
  14.  
  15.     // ...
  16.  
  17.     public void close() throws IOException {
  18.         is.close();
  19.     }
  20.  
  21.     public synchronized void mark(int readlimit) {
  22.         is.mark(readlimit);
  23.     }
  24. }

Теперь проверим.
Запишем какие-нибудь данные в массив байт
  1. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  2. DataOutputStream dos = new DataOutputStream(new EncryptedOutputStream(baos, (byte) 101));
  3. dos.writeUTF("Hello world");
  4. dos.writeInt(9379992);
  5. dos.writeBoolean(true);
  6. dos.writeFloat(1.618f);
  7. dos.flush();
  8. byte[] data = baos.toByteArray();
  9. baos.close();

В закодированном виде это выглядит так:
101, 110, 45, 0, 9, 9, 10, 69, 18, 10, 23, 9, 1, 101, -22, 69, -3, 100, 90, -86, 127, -59
В обычном так:
0, 11, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 0, -113, 32, -104, 1, 63, -49, 26, -96

Также, теперь очень легко загружать зашифрованные картинки из файла/ресурсов/интернета
  1. InputStream is = getClass().getResourceAsStream(path);
  2. EncryptedInputStream eis = new EncryptedInputStream(is, (byte) 0x77);
  3. Image img = Image.createImage(eis);


Помимо шифрования, можно добавить какие-нибудь вспомогательные методы, например побитовое чтение.
  1. public class BitInputStream extends InputStream {
  2.     
  3.     protected InputStream is;
  4.     private int bitshift = -1;
  5.     private int bits;
  6.     
  7.     public BitInputStream(InputStream is) {
  8.         this.is = is;
  9.     }
  10.     
  11.     public boolean readBit() throws IOException {
  12.         if (bitshift == -1) {
  13.             bits = read() & 0xFF;
  14.             bitshift = 7;
  15.         }
  16.         boolean bit = (((bits >> bitshift) & 1) != 0);
  17.         bitshift--;
  18.         return bit;
  19.     }
  20.     
  21.     public int read() throws IOException {
  22.         return is.read();
  23.     }
  24.  
  25.     // ...
  26. }
  27.  
  28. ByteArrayInputStream bais = new ByteArrayInputStream("test".getBytes());
  29. BitInputStream bis = new BitInputStream(bais);
  30. int bitCount = bis.available() * 8;
  31. for (int i = 0; i < bitCount; i++) {
  32.     System.out.print(bis.readBit() ? '1' : '0');
  33. }
  34. bis.close();

Выведет
01110100011001010111001101110100

  screen.png
Исходники + бинарники: Java ME | Java SE
+9   9   0
1892