Во-первых, вы должны переопределить свою задачу. Вы читаете символы, поэтому не хотите преобразовывать InputStream
, а Reader
в Stream
.
Вы не можете повторно реализовать преобразование кодировки, которое происходит, например. в InputStreamReader
с Stream
операциями, так как может быть n: m сопоставлений между byte
s InputStream
и результирующими char
s.
Создать поток из Reader
немного сложно. Вам понадобится итератор, чтобы указать метод получения элемента и конечное условие:
PrimitiveIterator.OfInt it=new PrimitiveIterator.OfInt() {
int last=-2;
public int nextInt() {
if(last==-2 && !hasNext())
throw new NoSuchElementException();
try { return last; } finally { last=-2; }
}
public boolean hasNext() {
if(last==-2)
try { last=reader.read(); }
catch(IOException ex) { throw new UncheckedIOException(ex); }
return last>=0;
}
};
Когда у вас есть итератор, вы можете создать поток, используя обход разделителя, и выполнить желаемую операцию:
int[] tally = new int[26];
StreamSupport.intStream(Spliterators.spliteratorUnknownSize(
it, Spliterator.ORDERED | Spliterator.IMMUTABLE | Spliterator.NONNULL), false)
// now you have your stream and you can operate on it:
.map(Character::toLowerCase)
.filter(c -> c>='a'&&c<='z')
.map(c -> c-'a')
.forEach(i -> tally[i]++);
Обратите внимание, что хотя итераторы более привычны, реализация нового интерфейса Spliterator
напрямую упрощает операцию, поскольку не требует сохранения состояния между двумя методами, которые можно вызывать в произвольном порядке. Вместо этого у нас есть только один метод tryAdvance
, который можно напрямую сопоставить с вызовом read()
:
Spliterator.OfInt sp = new Spliterators.AbstractIntSpliterator(1000L,
Spliterator.ORDERED | Spliterator.IMMUTABLE | Spliterator.NONNULL) {
public boolean tryAdvance(IntConsumer action) {
int ch;
try { ch=reader.read(); }
catch(IOException ex) { throw new UncheckedIOException(ex); }
if(ch<0) return false;
action.accept(ch);
return true;
}
};
StreamSupport.intStream(sp, false)
// now you have your stream and you can operate on it:
…
Однако обратите внимание, что если вы передумаете и захотите использовать Files.lines
, ваша жизнь может быть намного проще:
int[] tally = new int[26];
Files.lines(Paths.get(file))
.flatMapToInt(CharSequence::chars)
.map(Character::toLowerCase)
.filter(c -> c>='a'&&c<='z')
.map(c -> c-'a')
.forEach(i -> tally[i]++);
person
Holger
schedule
23.05.2014
Character.isLetter
возвращаетtrue
больше, чем простоa-z
, например.ä
илиπ
. - person Holger   schedule 23.05.2014'Ä'
в'ä'
и'Π'
в'π'
. Но если вы хотите подсчитать только 26 значений между'a'
иz
, вам следует отфильтровать проверку для этого диапазона (как я сделал в своем ответе), а не использоватьisLetter
.'ä'
и'π'
являются строчными буквами… - person Holger   schedule 26.05.2014