Канал между процессами Java в командной оболочке ненадежно работает

#java #pipe

#java #канал

Вопрос:

Я пытаюсь передать текст между двумя программами Java. Для простоты я представляю этот код:

 import java.io.DataInputStream;
import java.io.IOException;

public class Test {
    public static void main(String[] args) throws IOException {
        DataInputStream stdin = new DataInputStream(System.in);
        String completeText = ""; 

        while (stdin.available() > 0) {
            byte[] tempByte = { stdin.readByte() };
            completeText  = new String(tempByte);
        }

        System.out.println(completeText);
    }
}
  

При выполнении следующего в Linux или Windows текст, похоже, опускается, как если бы канал был заблокирован или потерян совершенно случайно. Иногда все проходит, иногда нет:

 echo "omg wtf" | java Test | java Test
  

Есть идеи по этому поводу? Кажется, что чем медленнее процессор, тем чаще проходит текст. «доступно» возвращает неправильный результат по какой-либо причине, когда входные данные передаются из java System.out.println()?

Приветствия!

Ответ №1:

Во-первых, available() метод не является надежным способом определить, исчерпан ли поток. Надежным признаком окончания потока является проверка возвращаемого значения read() метода (< 0 означает конец потока).

Короче говоря, available() может вернуться false (что завершит цикл), если поток на мгновение опустеет. Если канал все еще активен, эта ситуация изменится, как только процесс на другом конце канала запишет в него несколько байтов. Чтобы быть уверенным, что все данные были прочитаны, вам нужно проверить наличие end-of-stream.

Во-вторых, если вы хотите прочитать символы (и объединить их в строку), вы должны прочитать символы из устройства чтения (а не байты из потока). Это позволит вашему коду обрабатывать символы Юникода.

В-третьих, конкатенация больших фрагментов символов будет быстрее, если вы используете StringBuilder (а не обычную строку).

Наконец, если вам нужно только прочитать байты, вы можете напрямую использовать входной поток (нет необходимости оборачивать его потоком ввода данных).

Вот как я бы это написал:

 Reader r = new InputStreamReader(System.in);
StringBuilder sb = new StringBuilder();
while(true) {
  int ch = r.read();
  if(ch < 0)
    break;
  sb.append((char) ch);
}

System.out.println(sb.toString());
  

Ответ №2:

available() ненадежен для конвейерного ввода. Он проверяет, есть ли данные во входном буфере текущего процесса. У него нет способа проверить, собирается ли предыдущий (по каналу) процесс отправить какие-либо данные.

В вашем случае блокировка чтения является приемлемым решением:

 public class Test {
    public static void main(String[] args) throws IOException {
        DataInputStream stdin = new DataInputStream(System.in);
        StringBuilder completeText = new StringBuilder(); 
        byte[] tempByte = new byte[1024];
        int len = 0;  
        while ((len = stdin.read(tempByte)) != -1) {
            completeText.append(new String(tempByte, 0, len));
        }
        System.out.println(completeText.toString());
    }
}
  

Я также добавил StringBuilder, поскольку это «правильный» Java-способ объединения строк.