Клас ByteArrayInputStream пакету java.io можна використовувати для читання масиву вхідних даних (у байтах).

Щоб створити вхідний потік масиву байтів, ми повинні спочатку імпортувати пакет java.io.ByteArrayInputStream. Як тільки ми імпортуємо пакет, у нас є два конструктори для створення вхідного потоку:


ByteArrayInputStream input = new ByteArrayInputStream(arr);
ByteArrayInputStream input = new ByteArrayInputStream(arr, 2, 2);
    

Усередині класу є 4 поля:


//Масив байтів, наданий творцем потоку
protected byte buf[];

//Індекс наступного символу для читання з буфера вхідного потоку
protected int pos;

//Поточна зазначена позиція в потоці
protected int mark = 0;

//Індекс на одиницю більше, ніж останній допустимий символ у буфері вхідного потоку
protected int count;
    

І ось наші конструктори:


public ByteArrayInputStream(byte buf[]) {
    this.buf = buf;
    this.pos = 0;
    this.count = buf.length;
}

public ByteArrayInputStream(byte buf[], int offset, int length) {
    this.buf = buf;
    this.pos = offset;
    this.count = Math.min(offset + length, buf.length);
    this.mark = offset;
}
    

Методи ByteArrayInputStream

Метод Дія
int read() Зчитує наступний байт даних із цього вхідного потоку.
int read(byte b[], int off, int len) Зчитує деяку кількість байтів із вхідного потоку та зберігає їх у буферному масиві b.
off – початкове зміщення в цільовому масиві b.
len – максимальна кількість прочитаних байтів.
long skip(long n) Пропускає n байтів введення із цього вхідного потоку. Повертає кількість пропущених байтів (може бути меншим, якщо досягнутий кінець вхідного потоку).
int available() Повертає кількість байтів, що залишилися, які можна прочитати (або пропустити) із цього вхідного потоку.
void reset() Скидає буфер у відмічену позицію. Відмічена позиція дорівнює 0, якщо не було відмічено іншу позицію або вказано інше зміщення в конструкторі.
boolean markSupported() Перевіряє, чи підтримує цей InputStream відмітку/скидання. Повертає true для ByteArrayInputStream.
void close() Нічого не робить.
void mark(int readAheadLimit) Встановлює поточну позицію в поле mark. Якщо буде викликаний метод reset, подальше читання розпочнеться саме з цієї позиції. Значення параметра методу readAheadLimit не впливає на його роботу і просто не використовується.

Давайте розберемо ці методи докладніше і подивимося на практиці, як вони працюють.

read()

Коли потрібно читати байти з ByteArrayInputStream так само, як і зі звичайного InputStream, можна використовувати метод read().


public static void main(String[] args) {
   byte[] array = {1, 2, 3, 4};

   try (ByteArrayInputStream input = new ByteArrayInputStream(array)) {
       for (int i = 0; i < array.length; i++) {
           int data = input.read();
           System.out.print(data + ", ");
       }
   } catch (IOException e) {
       e.printStackTrace();
   }
}
    

available()

Якщо ви хочете перевірити, чи є щось у вашому буфері, можна викликати метод avalible().


public static void main(String[] args) {
   byte[] array = {1, 2, 3, 4};

   try (ByteArrayInputStream input = new ByteArrayInputStream(array)) {
       System.out.println("байтів, доступних для читання  " + input.available());

       input.read();
       System.out.println("байтів, вільних для читання " + input.available());

       input.read();
       System.out.println("байтів, вільних для читання " + input.available());
   } catch (IOException e) {
       e.printStackTrace();
   }
}
    

В результаті ми побачимо, що кількість доступних для читання байтів змінюється після кожного читання з буфера.

Виведення програми:

байтів, доступних для читання 4
байтів, вільних для читання 3
байтів, вільних для читання 2

skip(long n)

За допомогою методу skip() можна пропустити певну кількість байтів і не зчитувати їх.


public static void main(String[] args) {
   byte[] array = {1, 2, 3, 4};

   try (ByteArrayInputStream input = new ByteArrayInputStream(array)) {
       input.skip(2);

       while (input.available() != 0) {
           int data = input.read();
           System.out.print(data + ", ");
       }
   } catch (IOException e) {
       e.printStackTrace();
   }
}
    

Виведення програми:

3, 4,

reset()

Метод скидає положення буферизованого потоку до останньої відміченої позиції. Він займає позицію мітки 0, якщо явно не вказана інша мітка.


public static void main(String[] args) {
   byte[] buf = {65, 66, 67, 68, 69};
   try (ByteArrayInputStream input = new ByteArrayInputStream(buf)) {
       System.out.println("прочитали: " + input.read());
       System.out.println("прочитали: " + input.read());
       System.out.println("прочитали: " + input.read());
       System.out.println("прочитали: " + input.read());

       System.out.println("виклик методу reset()");
       input.reset();
       System.out.println("прочитали: " + input.read());
       System.out.println("прочитали: " + input.read());
   } catch (IOException e) {
       e.printStackTrace();
   }
}
    

В результаті ми побачимо, як після виклику методу reset() ми потрапимо на початкову точку нашого потоку.

Виведення програми:

прочитали: 65
прочитали: 66
прочитали: 67
прочитали: 68
виклик методу reset()
прочитали: 65
прочитали: 66

mark(int readAheadLimit)

Метод mark() класу ByteArrayInputStream встановлює внутрішню мітку в поточній позиції байта, тобто відразу після читання попереднього байта. Цей метод приймає параметр, що вказує, скільки байтів можна прочитати після цієї мітки, перш ніж вона стане недійсною. За замовчуванням, якщо мітка не була встановлена явно, ByteArrayInputStream відмічає позицію 0 або позицію зі зміщенням, переданим його конструктору. Важливо відзначити, що для цього класу відмітка readAheadLimit недійсна.


/* Note: The {@code readAheadLimit} for this class
*  has no meaning.
*
* @since   1.1
*/
public void mark(int readAheadLimit) {
   mark = pos;
}
    

Ось приклад встановлення мітки в ByteArrayInputStream за допомогою його метода mark() та метода reset(). Ми додамо до попереднього прикладу виклик методу mark():


public static void main(String[] args) {
   byte[] buf = {65, 66, 67, 68, 69};
   try (ByteArrayInputStream input = new ByteArrayInputStream(buf)) {
       System.out.println("прочитали: " + input.read());
       System.out.println("прочитали: " + input.read());
       System.out.println("прочитали: " + input.read());
       input.mark(5);

       System.out.println("прочитали: " + input.read());
       System.out.println("прочитали: " + input.read());

       System.out.println("виклик методу reset()");
       input.reset();

       System.out.println("прочитали: " + input.read());
       System.out.println("прочитали: " + input.read());

   } catch (IOException e) {
       e.printStackTrace();
   }
}
    

В результаті ми можемо побачити, що позиція поточного потоку вже змінилася.

Виведення програми:

прочитали: 65
прочитали: 66
прочитали: 67
прочитали: 68
прочитали: 69
виклик методу reset()
прочитали: 68
прочитали: 69

markSupported()

Метод markSupported() дозволяє перевірити доступ для встановлення мітки. Щоб зрозуміти, на основі чого видається результат, перейдемо в метод:


/**
* Tests if this {@code InputStream} supports mark/reset. The
* {@code markSupported} method of {@code ByteArrayInputStream}
* always returns {@code true}.
*
* @since   1.1
*/
public boolean markSupported() {
   return true;
}
    

Наш метод завжди повертає true. Перевіримо це на прикладі:


public static void main(String[] args) {
   byte[] buf = {65, 66, 67, 68, 69};
   try (ByteArrayInputStream bais = new ByteArrayInputStream(buf)) {
       boolean isMarkSupported = bais.markSupported();

       System.out.println("isMarkSupported: " + isMarkSupported);
       System.out.println("прочитали: " + bais.read());
       System.out.println("прочитали: " + bais.read());

       bais.mark(1);
       System.out.println("прочитали: " + bais.read());
       isMarkSupported = bais.markSupported();
       System.out.println("isMarkSupported: " + isMarkSupported);

       bais.reset();
       isMarkSupported = bais.markSupported();
       System.out.println("isMarkSupported: " + isMarkSupported);
   } catch (IOException e) {
       e.printStackTrace();
   }
}
    

В результаті після виконання методу mark() та методу reset() наш потік завжди готовий і може встановлювати відмітки:

Виведення програми:

isMarkSupported: true
прочитали: 65
прочитали: 66
прочитали: 67
isMarkSupported: true
isMarkSupported: true

close()

Щоб зрозуміти роботу методу close, ми також подивимося, що у нього всередині:


/**
* Closing a {@code ByteArrayInputStream} has no effect. The methods in
* this class can be called after the stream has been closed without
* generating an {@code IOException}.
*/
public void close() throws IOException {
}
    

У документації про метод close сказано: закриття ByteArrayInputStream не має жодного ефекту. Методи класу ByteArrayInputStream можна викликати після закриття потоку без створення винятку IOException.

Який висновок можемо зробити?

ByteArrayInputStream потрібний нам, коли необхідно читати дані з масиву байт. Зазвичай є сенс використовувати цей клас не сам по собі, а в комбінації з іншим кодом, який вміє працювати з InputStream.