Je réduis un flux de InputStreams
comme ceci:
InputStream total = input.collect(
Collectors.reducing(
empty,
Item::getInputStream,
(is1, is2) -> new SequenceInputStream(is1, is2)));
Pour l'identité InputStream
, j'utilise:
InputStream empty = new ByteArrayInputStream(new byte[0]);
Cela fonctionne, mais existe-t-il une meilleure façon de représenter un InputStream
vide?
J'irais dans une voie différente.
La réduction d'un plus grand nombre d'instances InputStream
via (is1, is2) -> new SequenceInputStream(is1, is2)
Peut créer un arbre profond et déséquilibré d'instances SequenceInputStream
, qui peut devenir très inefficace.
Une structure de données linéaire est plus appropriée:
InputStream total = new SequenceInputStream(
Collections.enumeration(input.map(Item::getInputStream).collect(Collectors.toList())));
Cela crée un seul SequenceInputStream
traitant tous les flux d'entrée collectés. Étant donné que cela gère également intrinsèquement le cas de liste vide, il n'est plus nécessaire d'avoir une implémentation spéciale InputStream
vide.
Mais quand vous regardez le code source de SequenceInputStream
, vous verrez que cette classe en aucune magie, en fait, nous pourrions même faire mieux en n'utilisant pas de classes archaïques comme Vector
et Enumeration
:
public class StreamInputStream extends InputStream {
final Spliterator<? extends InputStream> source;
final Consumer<InputStream> c = is -> in = Objects.requireNonNull(is);
InputStream in;
public StreamInputStream(Stream<? extends InputStream> sourceStream) {
(source = sourceStream.spliterator()).tryAdvance(c);
}
public StreamInputStream(InputStream first, InputStream second) {
this(Stream.of(first, second));
}
public int available() throws IOException {
return in == null? 0: in.available();
}
public int read() throws IOException {
if(in == null) return -1;
int b; do b = in.read(); while(b<0 && next());
return b;
}
public int read(byte b[], int off, int len) throws IOException {
if((off|len) < 0 || len > b.length - off) throw new IndexOutOfBoundsException();
if(in == null) return -1; else if(len == 0) return 0;
int n; do n = in.read(b, off, len); while(n<0 && next());
return n;
}
public void close() throws IOException {
closeCurrent();
}
private boolean next() throws IOException {
closeCurrent();
return source.tryAdvance(c);
}
private void closeCurrent() throws IOException {
if(in != null) try { in.close(); } finally { in = null; }
}
}
En plus d'être plus simple et plus propre (il n'a pas besoin d'instructions comme catch (IOException ex) { throw new Error("panic"); }
), il considère la nature paresseuse des flux: lorsqu'il est fermé avant que tous les éléments aient été traversés, il ne traverse pas le flux restant pour fermer le Les éléments InputStream
, car ils ne sont normalement pas encore créés à ce stade, n'ont donc pas besoin d'être fermés.
La création du flux est maintenant aussi simple que
InputStream total = new StreamInputStream(input.map(Item::getInputStream));
Puisque InputStream
n'a qu'une seule méthode abstraite, read()
,
public abstract int read() throws IOException
Renvoie:
l'octet suivant de données, ou-1
si la fin du flux est atteinte.
il est facile de créer un flux vide par une sous-classe anonyme . Comme ça:
InputStream empty = new InputStream() {
@Override
public int read() {
return -1; // end of stream
}
};
Mais il est vrai que c'est plus de code que votre ByteArrayInputStream
vide.
Depuis Java 11, vous pouvez utiliser une méthode statique InputStream.nullInputStream()
:
Renvoie un nouveau InputStream qui ne lit aucun octet. Le flux renvoyé est initialement ouvert. Le flux est fermé en appelant la méthode close (). Les appels ultérieurs à close () n'ont aucun effet.