2016-10-06 5 views
1

У меня есть библиотека, которая возвращает некоторые двоичные данные в виде списка двоичных массивов. Эти байты [] должны быть объединены в InputStream.Java 8 - Самый эффективный способ слияния Список <byte[]> to byte []

Это моя текущая реализация:

public static InputStream foo(List<byte[]> binary) { 
    byte[] streamArray = null; 
    binary.forEach(bin -> { 
     org.apache.commons.lang.ArrayUtils.addAll(streamArray, bin); 
    }); 
    return new ByteArrayInputStream(streamArray); 
} 

, но это довольно процессор интенсивно. Есть ли способ лучше?

Спасибо за все ответы. Я сделал тест производительности. Таковы мои результаты:

  • Функция: 'NicolasFilotto' => 68,04 мс в среднем на 100 звонков
  • Функция: 'NicolasFilottoEstSize' => 65,24 мс в среднем на 100 звонков
  • Функция: ' NicolasFilottoSequenceInputStream»=> 63,09 мс в среднем на 100 звонков
  • Функция: 'Saka1029_1' => 63,06 мс в среднем на 100 звонков
  • Функция: 'Saka1029_2' => 0,79 мс в среднем на 100 звонков
  • Функция: «Coco» => 541,60 мс в среднем по 10 вызовам

Я не уверен, если 'Saka1029_2' измеряется правильно ...

это функция выполнения:

private static double execute(Callable<InputStream> funct, int times) throws Exception { 
    List<Long> executions = new ArrayList<>(times); 

    for (int idx = 0; idx < times; idx++) { 
     BufferedReader br = null; 
     long startTime = System.currentTimeMillis(); 
     InputStream is = funct.call(); 
     br = new BufferedReader(new InputStreamReader(is)); 
     String line = null; 
     while ((line = br.readLine()) != null) {} 
     executions.add(System.currentTimeMillis() - startTime); 
    } 

    return calculateAverage(executions); 
} 

обратите внимание, что я прочитал каждый входной поток

тех являются используемые варианты осуществления:

public static class NicolasFilotto implements Callable<InputStream> { 

    private final List<byte[]> binary; 

    public NicolasFilotto(List<byte[]> binary) { 
     this.binary = binary; 
    } 

    @Override 
    public InputStream call() throws Exception { 
     ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
     for (byte[] bytes : binary) { 
      baos.write(bytes, 0, bytes.length); 
     } 
     return new ByteArrayInputStream(baos.toByteArray()); 
    } 

} 

public static class NicolasFilottoSequenceInputStream implements Callable<InputStream> { 

    private final List<byte[]> binary; 

    public NicolasFilottoSequenceInputStream(List<byte[]> binary) { 
     this.binary = binary; 
    } 

    @Override 
    public InputStream call() throws Exception { 
     return new SequenceInputStream(
       Collections.enumeration(
         binary.stream().map(ByteArrayInputStream::new).collect(Collectors.toList()))); 
    } 

} 

public static class NicolasFilottoEstSize implements Callable<InputStream> { 

    private final List<byte[]> binary; 
    private final int lineSize; 

    public NicolasFilottoEstSize(List<byte[]> binary, int lineSize) { 
     this.binary = binary; 
     this.lineSize = lineSize; 
    } 

    @Override 
    public InputStream call() throws Exception { 
     ByteArrayOutputStream baos = new ByteArrayOutputStream(binary.size() * lineSize); 
     for (byte[] bytes : binary) { 
      baos.write(bytes, 0, bytes.length); 
     } 
     return new ByteArrayInputStream(baos.toByteArray()); 
    } 

} 

public static class Saka1029_1 implements Callable<InputStream> { 

    private final List<byte[]> binary; 

    public Saka1029_1(List<byte[]> binary) { 
     this.binary = binary; 
    } 

    @Override 
    public InputStream call() throws Exception { 
     byte[] all = new byte[binary.stream().mapToInt(a -> a.length).sum()]; 
     int pos = 0; 
     for (byte[] bin : binary) { 
      int length = bin.length; 
      System.arraycopy(bin, 0, all, pos, length); 
      pos += length; 
     } 
     return new ByteArrayInputStream(all); 
    } 

} 

public static class Saka1029_2 implements Callable<InputStream> { 

    private final List<byte[]> binary; 

    public Saka1029_2(List<byte[]> binary) { 
     this.binary = binary; 
    } 

    @Override 
    public InputStream call() throws Exception { 
     int size = binary.size(); 
     return new InputStream() { 
      int i = 0, j = 0; 

      @Override 
      public int read() throws IOException { 
       if (i >= size) return -1; 
       if (j >= binary.get(i).length) { 
        ++i; 
        j = 0; 
       } 
       if (i >= size) return -1; 
       return binary.get(i)[j++]; 
      } 
     }; 
    } 

} 

public static class Coco implements Callable<InputStream> { 

    private final List<byte[]> binary; 

    public Coco(List<byte[]> binary) { 
     this.binary = binary; 
    } 

    @Override 
    public InputStream call() throws Exception { 
     byte[] streamArray = new byte[0]; 
     for (byte[] bin : binary) { 
      streamArray = org.apache.commons.lang.ArrayUtils.addAll(streamArray, bin); 
     } 
     return new ByteArrayInputStream(streamArray); 
    } 

} 
+0

Вы можете использовать InputStream с чтением из списка , не производя байт [] всего первого. –

+1

Как насчет моего второго предложения на основе 'SequenceInputStream'? –

+0

для микро-бенчмарка, используйте JMH http://openjdk.java.net/projects/code-tools/jmh/ –

ответ

2

Попробуйте это.

public static InputStream foo(List<byte[]> binary) { 
    byte[] all = new byte[binary.stream().mapToInt(a -> a.length).sum()]; 
    int pos = 0; 
    for (byte[] bin : binary) { 
     int length = bin.length; 
     System.arraycopy(bin, 0, all, pos, length); 
     pos += length; 
    } 
    return new ByteArrayInputStream(all); 
} 

Или

public static InputStream foo(List<byte[]> binary) { 
    int size = binary.size(); 
    return new InputStream() { 
     int i = 0, j = 0; 
     @Override 
     public int read() throws IOException { 
      if (i >= size) return -1; 
      if (j >= binary.get(i).length) { 
       ++i; 
       j = 0; 
      } 
      if (i >= size) return -1; 
      return binary.get(i)[j++]; 
     } 
    }; 
} 
+0

@NicolasFilotto Но это выделяет массив байтов только один раз. – saka1029

3

Вы может использовать ByteArrayOutputStream для хранения содержимого каждого массива байтов вашего списка, но чтобы он был эффективным, нам нужно создать экземпляр ByteArrayOutputStreamс начальным размером, который соответствует максимально возможному размеру цели, поэтому, если вы знать размер или, по крайней мере, средний размер массива байтов, вы должны использовать его, код будет:

public static InputStream foo(List<byte[]> binary) { 
    ByteArrayOutputStream baos = new ByteArrayOutputStream(ARRAY_SIZE * binary.size()); 
    for (byte[] bytes : binary) { 
     baos.write(bytes, 0, bytes.length); 
    } 
    return new ByteArrayInputStream(baos.toByteArray()); 
} 

Другой подход заключается в использовании SequenceInputStream для того, чтобы логически объединить все в ByteArrayInputStream экземпляры, представляющие один элемент вашего списка, следующим образом:

public static InputStream foo(List<byte[]> binary) { 
    return new SequenceInputStream(
     Collections.enumeration(
      binary.stream().map(ByteArrayInputStream::new).collect(Collectors.toList()) 
     ) 
    ); 
} 

Интересный аспект этого подхода заключается в том, что вам нет необходимости копировать что-либо, вы создаете только экземпляры ByteArrayInputStream, которые будут использовать массив байтов как есть.

Чтобы избежать сбора результата как List, который имеет цену, особенно если ваш первоначальный List большой, то вы можете напрямую позвонить iterator(), предложенный @Holger, то нужно будет просто преобразовать iterator в enumeration, который может быть сделано с IteratorUtils.asEnumeration(iterator) из Apache Commons Collection, окончательный код будет затем:

public static InputStream foo(List<byte[]> binary) { 
    return new SequenceInputStream(
     IteratorUtils.asEnumeration(
      binary.stream().map(ByteArrayInputStream::new).iterator() 
     ) 
    ); 
} 
+0

Есть ли способ использовать java8 parralel? – Coco

+1

@Coco: при использовании подхода 'SequenceInputStream' нет никакой необходимости в параллельной обработке, так как нет никакой реальной работы. – Holger

+1

@Nicolas Filotto: рабочая нагрузка на элемент списка настолько мала, что никакой выгоды не будет, независимо от того, насколько велик список. Дело в том, что 'Collectors.toList()' никогда не выигрывает от параллельной обработки, так как рабочая нагрузка этапа (ов) слияния в точности равна максимальной экономии предыдущих параллельных накоплений (ов). Единственное, что выгодно для параллельной обработки, это одновременное выполнение легкого 'ByteArrayInputStream :: new'. Это не будет компенсировать затраты на управление потоком. – Holger