เป็นไปได้ไหมที่จะอ่านจาก InputStream ด้วยการหมดเวลา?


147

โดยเฉพาะปัญหาคือการเขียนวิธีเช่นนี้:

int maybeRead(InputStream in, long timeout)

โดยที่ค่าส่งคืนจะเหมือนกับ in.read () หากข้อมูลมีให้ใช้งานภายใน 'ระยะหมดเวลา' มิลลิวินาทีและเป็นอย่างอื่น ก่อนที่เมธอดจะส่งคืนเธรดที่เกิดใหม่ใด ๆ จะต้องออก

เพื่อหลีกเลี่ยงการขัดแย้งหัวเรื่องที่นี่ java.io.InputStream ตามที่ Sun (รับรอง Java เวอร์ชันใด ๆ ) โปรดทราบว่านี่ไม่ง่ายอย่างที่เห็น ด้านล่างนี้คือข้อเท็จจริงบางประการซึ่งได้รับการสนับสนุนโดยตรงจากเอกสารของซัน

  1. เมธอด in.read () อาจไม่สามารถอินเตอร์รัปต์ได้

  2. การตัด InputStream ใน Reader หรือ InterruptibleChannel ไม่ได้ช่วยเพราะคลาสเหล่านั้นทั้งหมดสามารถทำได้คือวิธีการโทรของ InputStream ถ้ามันเป็นไปได้ที่จะใช้คลาสเหล่านั้นมันจะเป็นไปได้ที่จะเขียนวิธีแก้ปัญหาที่รันตรรกะเดียวกันโดยตรงบน InputStream

  3. เป็นที่ยอมรับเสมอสำหรับ in.available () เพื่อส่งคืน 0

  4. เมธอด in.close () อาจบล็อกหรือไม่ทำอะไรเลย

  5. ไม่มีวิธีทั่วไปในการฆ่าเธรดอื่น

คำตอบ:


83

ใช้ inputStream.available ()

เป็นที่ยอมรับเสมอสำหรับ System.in.available () เพื่อส่งกลับ 0

ฉันได้พบสิ่งที่ตรงกันข้าม - มันคืนค่าที่ดีที่สุดสำหรับจำนวนไบต์ที่มีอยู่เสมอ Javadoc สำหรับInputStream.available():

Returns an estimate of the number of bytes that can be read (or skipped over) 
from this input stream without blocking by the next invocation of a method for 
this input stream.

ไม่สามารถหลีกเลี่ยงการประมาณการได้เนื่องจากกำหนดเวลา / ไม่สม่ำเสมอ ตัวเลขอาจดูเบาเพียงครั้งเดียวเนื่องจากข้อมูลใหม่เข้ามาอย่างต่อเนื่อง อย่างไรก็ตามมันจะ "รับสาย" เสมอในการโทรครั้งต่อไป - มันควรจะเป็นข้อมูลที่มาถึงทั้งหมดแถบที่มาถึงในเวลาที่มีการโทรใหม่ ส่งคืนอย่างถาวร 0 เมื่อมีข้อมูลล้มเหลวตามเงื่อนไขข้างต้น

First Caveat: คลาสย่อยคอนกรีตของ InputStream มีความรับผิดชอบสำหรับ ()

InputStreamเป็นคลาสนามธรรม ไม่มีแหล่งข้อมูล มันไม่มีความหมายเลยที่จะมีข้อมูลที่พร้อมใช้งาน ดังนั้น javadoc สำหรับavailable()รัฐยัง:

The available method for class InputStream always returns 0.

This method should be overridden by subclasses.

และแน่นอนคลาสอินพุตสตรีมแบบคอนกรีตจะแทนที่ค่าที่มีอยู่ () โดยให้ค่าที่มีความหมายไม่ใช่ 0 คงที่

Second Caveat: ตรวจสอบให้แน่ใจว่าคุณใช้ carriage-return เมื่อพิมพ์อินพุตใน Windows

หากใช้System.inโปรแกรมของคุณจะรับอินพุตเมื่อเชลล์คำสั่งของคุณส่งมอบเท่านั้น หากคุณใช้การเปลี่ยนเส้นทางไฟล์ / ไพพ์ (เช่น somefile> java myJavaApp หรือ somecommand | java myJavaApp) จากนั้นข้อมูลอินพุตจะถูกส่งทันที อย่างไรก็ตามหากคุณป้อนข้อมูลด้วยตนเองการส่งข้อมูลอาจล่าช้าได้ เช่นกับเชลล์ windows cmd.exe ข้อมูลจะถูกบัฟเฟอร์ภายในเชลล์ cmd.exe ข้อมูลถูกส่งผ่านไปยังโปรแกรม Java ที่ดำเนินการตามหลัง carriage-return (control-m หรือ<enter>) นั่นเป็นข้อ จำกัด ของสภาพแวดล้อมการดำเนินการ แน่นอน InputStream.available () จะคืนค่า 0 ตราบเท่าที่เชลล์บัฟเฟอร์ข้อมูล - นั่นเป็นพฤติกรรมที่ถูกต้อง ไม่มีข้อมูล ณ จุดนั้น ทันทีที่ข้อมูลพร้อมใช้งานจากเชลล์เมธอดจะส่งคืนค่า> 0 หมายเหตุ: Cygwin ใช้ cmd

ทางออกที่ง่ายที่สุด (ไม่มีการปิดกั้นดังนั้นไม่จำเป็นต้องหมดเวลา)

เพียงใช้สิ่งนี้:

    byte[] inputData = new byte[1024];
    int result = is.read(inputData, 0, is.available());  
    // result will indicate number of bytes read; -1 for EOF with no data read.

หรือเท่ากัน

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in, Charset.forName("ISO-8859-1")),1024);
    // ...
         // inside some iteration / processing logic:
         if (br.ready()) {
             int readCount = br.read(inputData, bufferOffset, inputData.length-bufferOffset);
         }

โซลูชันที่สมบูรณ์ยิ่งขึ้น (เติมบัฟเฟอร์สูงสุดภายในระยะเวลาหมดเวลา)

ประกาศสิ่งนี้:

public static int readInputStreamWithTimeout(InputStream is, byte[] b, int timeoutMillis)
     throws IOException  {
     int bufferOffset = 0;
     long maxTimeMillis = System.currentTimeMillis() + timeoutMillis;
     while (System.currentTimeMillis() < maxTimeMillis && bufferOffset < b.length) {
         int readLength = java.lang.Math.min(is.available(),b.length-bufferOffset);
         // can alternatively use bufferedReader, guarded by isReady():
         int readResult = is.read(b, bufferOffset, readLength);
         if (readResult == -1) break;
         bufferOffset += readResult;
     }
     return bufferOffset;
 }

จากนั้นใช้สิ่งนี้:

    byte[] inputData = new byte[1024];
    int readCount = readInputStreamWithTimeout(System.in, inputData, 6000);  // 6 second timeout
    // readCount will indicate number of bytes read; -1 for EOF with no data read.

1
หากis.available() > 1024ข้อเสนอแนะนี้จะล้มเหลว มีลำธารซึ่งจะคืนค่าเป็นศูนย์แน่นอน SSLSockets ตัวอย่างเช่นเมื่อเร็ว ๆ นี้ คุณไม่สามารถพึ่งพาสิ่งนี้ได้
มาร์ควิสแห่ง Lorne

กรณี 'is.available ()> 1024' มีการจัดการโดยเฉพาะผ่าน readLength
เกลนที่ดีที่สุด

ความคิดเห็นเกี่ยวกับ SSLSockets ไม่ถูกต้อง - มันจะคืนค่า 0 ถ้ามีถ้าไม่มีข้อมูลในบัฟเฟอร์ ตามคำตอบของฉัน Javadoc: "ถ้าไม่มีการบัฟเฟอร์ไบต์บนซ็อกเก็ตและซ็อกเก็ตไม่ได้ถูกปิดโดยใช้ปิดแล้วจะพร้อมใช้งานจะส่งคืน 0"
เกล็นที่ดีที่สุด

@GlenBest SSLSocket ความคิดเห็นของฉันไม่ถูกต้อง จนกระทั่งเมื่อไม่นานมานี้ [การเน้นของฉัน] มันใช้เพื่อกลับศูนย์ทุกครั้ง คุณกำลังพูดถึงของขวัญ ผมกำลังพูดถึงประวัติศาสตร์ทั้งหมดของ JSSE และผมเคยทำงานกับตั้งแต่ก่อนที่มันจะเป็นครั้งแรกที่รวมอยู่ในJava 1.4 ในปี 2002
มาร์ควิสแห่ง Lorne

โดยการเปลี่ยนเงื่อนไขวนลูปเป็น "ขณะที่ (is.available ()> 0 && System.currentTimeMillis () <maxTimeMillis && bufferOffset <b.length) {" ช่วยฉันโอเวอร์เฮดของ CPU จำนวนมาก
Logic1

65

สมมติว่ากระแสข้อมูลของคุณไม่ได้รับการสนับสนุนจากซ็อกเก็ต (ดังนั้นคุณจึงไม่สามารถใช้งานได้Socket.setSoTimeout()) ฉันคิดว่าวิธีมาตรฐานในการแก้ปัญหาประเภทนี้คือการใช้อนาคต

สมมติว่าฉันมีตัวจัดการและสตรีมต่อไปนี้:

    ExecutorService executor = Executors.newFixedThreadPool(2);
    final PipedOutputStream outputStream = new PipedOutputStream();
    final PipedInputStream inputStream = new PipedInputStream(outputStream);

ฉันมีนักเขียนที่เขียนข้อมูลบางส่วนแล้วรอประมาณ 5 วินาทีก่อนที่จะเขียนข้อมูลชิ้นสุดท้ายและปิดสตรีม:

    Runnable writeTask = new Runnable() {
        @Override
        public void run() {
            try {
                outputStream.write(1);
                outputStream.write(2);
                Thread.sleep(5000);
                outputStream.write(3);
                outputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    executor.submit(writeTask);

วิธีการอ่านแบบปกติมีดังต่อไปนี้ การอ่านจะบล็อกข้อมูลอย่างไม่ จำกัด ดังนั้นจึงเสร็จสมบูรณ์ใน 5 วินาที:

    long start = currentTimeMillis();
    int readByte = 1;
    // Read data without timeout
    while (readByte >= 0) {
        readByte = inputStream.read();
        if (readByte >= 0)
            System.out.println("Read: " + readByte);
    }
    System.out.println("Complete in " + (currentTimeMillis() - start) + "ms");

ผลลัพธ์ใด:

Read: 1
Read: 2
Read: 3
Complete in 5001ms

หากมีปัญหาพื้นฐานมากขึ้นเช่นผู้เขียนไม่ตอบสนองผู้อ่านจะบล็อกตลอดไป หากฉันสรุปการอ่านในอนาคตฉันสามารถควบคุมการหมดเวลาได้ดังนี้:

    int readByte = 1;
    // Read data with timeout
    Callable<Integer> readTask = new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            return inputStream.read();
        }
    };
    while (readByte >= 0) {
        Future<Integer> future = executor.submit(readTask);
        readByte = future.get(1000, TimeUnit.MILLISECONDS);
        if (readByte >= 0)
            System.out.println("Read: " + readByte);
    }

ผลลัพธ์ใด:

Read: 1
Read: 2
Exception in thread "main" java.util.concurrent.TimeoutException
    at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:228)
    at java.util.concurrent.FutureTask.get(FutureTask.java:91)
    at test.InputStreamWithTimeoutTest.main(InputStreamWithTimeoutTest.java:74)

ฉันสามารถตรวจจับการหมดเวลาและทำทุกอย่างที่ฉันต้องการ


14
แต่สิ่งที่เกี่ยวกับเธรดการบล็อก มันจะยังคงอยู่ในหน่วยความจำจนกว่าแอปพลิเคชั่นจะสิ้นสุดลงหรือไม่ หากฉันถูกต้องสิ่งนี้อาจสร้างเธรดที่ไม่มีที่สิ้นสุดที่แอปพลิเคชันมีการโหลดจำนวนมากและมากขึ้นให้บล็อกเธรดเพิ่มเติมจากการใช้พูลของคุณซึ่งมีเธรดที่ถูกครอบครองและถูกบล็อก โปรดแก้ไขฉันหากฉันผิด ขอบคุณ.
มูฮัมหมัด Gelbana

4
Muhammad Gelbana คุณพูดถูก: การบล็อกการอ่าน () ยังคงทำงานอยู่และไม่เป็นไร ฉันได้พบวิธีในการป้องกันสิ่งนี้แม้ว่า: เมื่อหมดเวลาการเข้าชมปิดจากหัวข้อการโทรกระแสข้อมูล (ในกรณีของฉันฉันปิดซ็อกเก็ตบลูทู ธ Android ที่กระแสเข้ามา) เมื่อคุณทำสิ่งนั้นการเรียก read () จะกลับมาทันที .. ในกรณีของฉันฉันใช้ int read (byte []) โอเวอร์โหลดและอันนั้นส่งคืนทันที บางทีการอ่านที่มากเกินไปของ int จะทำให้ IOException เกิดขึ้นเพราะฉันไม่รู้ว่ามันจะกลับมาเป็นอย่างไร ... ในใจของฉันนั่นคือทางออกที่เหมาะสม
Emmanuel Touzery

5
-1 เนื่องจากเธรดที่อ่านอยู่บล็อกจนกว่าแอปพลิเคชันจะสิ้นสุดลง
Ortwin Angermeier

11
@ortang นั่นคือสิ่งที่ฉันหมายถึงโดย "catch the TimeoutException และทำความสะอาดทุกอย่าง ... " ตัวอย่างเช่นฉันอาจต้องการฆ่าเธรดการอ่าน: ... catch (TimeoutException e) {executor.shutdownNow (); }
Ian Jones

12
executer.shutdownNowจะไม่ฆ่าเธรด มันจะพยายามขัดจังหวะโดยไม่มีผลกระทบ ไม่สามารถล้างข้อมูลได้และนี่เป็นปัญหาร้ายแรง
Marko Topolnik

22

หาก InputStream ของคุณมีการสนับสนุนจากซ็อกเก็ตคุณสามารถตั้งค่าหมดเวลา Socket (มิลลิวินาที) โดยใช้setSoTimeout หากการเรียก read () ไม่ได้ปลดบล็อกภายในระยะเวลาที่กำหนดมันจะส่ง SocketTimeoutException

เพียงตรวจสอบให้แน่ใจว่าคุณเรียก setSoTimeout บน Socket ก่อนทำการโทร read ()


18

ฉันจะตั้งคำถามกับคำแถลงปัญหามากกว่าแค่ยอมรับมันแบบสุ่ม ๆ คุณต้องการหมดเวลาจากคอนโซลหรือผ่านเครือข่าย หากหลังคุณมีSocket.setSoTimeout()และHttpURLConnection.setReadTimeout()สิ่งที่ทั้งสองทำสิ่งที่จำเป็นต้องใช้ตราบใดที่คุณตั้งค่าอย่างถูกต้องเมื่อคุณสร้าง / รับพวกเขา ปล่อยไว้ที่จุดใดจุดหนึ่งในแอปพลิเคชันเมื่อสิ่งที่คุณมีคือ InputStream คือการออกแบบที่ไม่ดีซึ่งนำไปสู่การใช้งานที่น่าอึดอัดใจมาก


10
มีสถานการณ์อื่น ๆ ที่การอ่านอาจบล็อกได้ในช่วงเวลาที่สำคัญ เช่นเมื่ออ่านจากเทปไดรฟ์จากไดรฟ์เครือข่ายที่ติดตั้งแบบรีโมตหรือจาก HFS ที่มีหุ่นยนต์เทปที่ด้านหลัง (แต่แรงผลักดันหลักของคำตอบของคุณถูกต้อง)
สตีเฟนซี

1
@StephenC +1 สำหรับความคิดเห็นและตัวอย่างของคุณ ในการเพิ่มตัวอย่างของคุณกรณีที่ง่ายอาจเป็นที่การเชื่อมต่อซ็อกเก็ตได้อย่างถูกต้อง แต่ความพยายามในการอ่านถูกบล็อกเนื่องจากข้อมูลจะถูกดึงมาจากฐานข้อมูล แต่มันก็ไม่ได้เกิดขึ้นอย่างใด (สมมติว่า DB ไม่ตอบสนอง อยู่ในสถานะล็อค) ในสถานการณ์นี้คุณต้องมีวิธีในการหมดเวลาการดำเนินการอ่านบนซ็อกเก็ตอย่างชัดเจน
sactiw

1
จุดรวมของสิ่งที่เป็นนามธรรมของ InputStream คือการไม่คิดถึงการใช้งานพื้นฐาน มันยุติธรรมที่จะโต้แย้งเกี่ยวกับข้อดีข้อเสียของคำตอบที่โพสต์ แต่การตั้งคำถามกับคำแถลงปัญหาจะไม่ช่วยให้เกิดความสับสน
pellucide

2
InputStream ทำงานบนสตรีมและบล็อก แต่ก็ไม่มีกลไกการหมดเวลา ดังนั้น InputStream abstraction จึงไม่ใช่ abstraction ที่ออกแบบมาอย่างเหมาะสม ดังนั้นการขอวิธีการหยุดพักชั่วคราวในสตรีมจึงไม่ได้ขอมาก ดังนั้นคำถามคือการขอวิธีการแก้ปัญหาในทางปฏิบัติมาก การใช้งานพื้นฐานส่วนใหญ่จะปิดกั้น นั่นคือแก่นแท้ของกระแส Sockets, Files, Pipes จะปิดกั้นหากอีกฝั่งของสตรีมไม่พร้อมกับข้อมูลใหม่
pellucide

2
@EJP ฉันไม่รู้ว่าคุณได้รับสิ่งนั้นอย่างไร ฉันไม่เห็นด้วยกับคุณ คำแถลงปัญหา "วิธีการหมดเวลาใน InputStream" นั้นถูกต้อง เนื่องจากกรอบงานไม่มีวิธีการหยุดพักชั่วคราวจึงเหมาะสมที่จะถามคำถามดังกล่าว
pellucide

7

ฉันไม่ได้ใช้คลาสจากแพ็คเกจ Java NIO แต่ดูเหมือนว่าพวกเขาอาจมีความช่วยเหลือบางอย่างที่นี่ โดยเฉพาะjava.nio.channels.Channelsและjava.nio.channels.InterruptibleChannel


2
+1: ฉันไม่เชื่อว่ามีวิธีที่เชื่อถือได้ในการทำสิ่งที่ OP ขอด้วย InputStream เพียงอย่างเดียว อย่างไรก็ตาม nio ถูกสร้างขึ้นเพื่อจุดประสงค์นี้และอื่น ๆ
Eddie

2
OP ได้ตัดออกไปแล้ว InputStreams กำลังบล็อกโดยเนื้อแท้และอาจไม่สามารถอินเตอร์รัปต์ได้
มาร์ควิสแห่ง Lorne

5

นี่คือวิธีรับ NIO FileChannel จาก System.in และตรวจสอบความพร้อมใช้งานของข้อมูลโดยใช้การหมดเวลาซึ่งเป็นกรณีพิเศษของปัญหาที่อธิบายไว้ในคำถาม เรียกใช้ที่คอนโซลอย่าพิมพ์อินพุตใด ๆ และรอผลลัพธ์ ผ่านการทดสอบแล้วภายใต้ Java 6 บน Windows และ Linux

import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedByInterruptException;

public class Main {

    static final ByteBuffer buf = ByteBuffer.allocate(4096);

    public static void main(String[] args) {

        long timeout = 1000 * 5;

        try {
            InputStream in = extract(System.in);
            if (! (in instanceof FileInputStream))
                throw new RuntimeException(
                        "Could not extract a FileInputStream from STDIN.");

            try {
                int ret = maybeAvailable((FileInputStream)in, timeout);
                System.out.println(
                        Integer.toString(ret) + " bytes were read.");

            } finally {
                in.close();
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    /* unravels all layers of FilterInputStream wrappers to get to the
     * core InputStream
     */
    public static InputStream extract(InputStream in)
            throws NoSuchFieldException, IllegalAccessException {

        Field f = FilterInputStream.class.getDeclaredField("in");
        f.setAccessible(true);

        while( in instanceof FilterInputStream )
            in = (InputStream)f.get((FilterInputStream)in);

        return in;
    }

    /* Returns the number of bytes which could be read from the stream,
     * timing out after the specified number of milliseconds.
     * Returns 0 on timeout (because no bytes could be read)
     * and -1 for end of stream.
     */
    public static int maybeAvailable(final FileInputStream in, long timeout)
            throws IOException, InterruptedException {

        final int[] dataReady = {0};
        final IOException[] maybeException = {null};
        final Thread reader = new Thread() {
            public void run() {                
                try {
                    dataReady[0] = in.getChannel().read(buf);
                } catch (ClosedByInterruptException e) {
                    System.err.println("Reader interrupted.");
                } catch (IOException e) {
                    maybeException[0] = e;
                }
            }
        };

        Thread interruptor = new Thread() {
            public void run() {
                reader.interrupt();
            }
        };

        reader.start();
        for(;;) {

            reader.join(timeout);
            if (!reader.isAlive())
                break;

            interruptor.start();
            interruptor.join(1000);
            reader.join(1000);
            if (!reader.isAlive())
                break;

            System.err.println("We're hung");
            System.exit(1);
        }

        if ( maybeException[0] != null )
            throw maybeException[0];

        return dataReady[0];
    }
}

ที่น่าสนใจเมื่อรันโปรแกรมภายใน NetBeans 6.5 มากกว่าที่คอนโซลการหมดเวลาใช้งานไม่ได้เลยและการเรียกใช้ System.exit () เป็นสิ่งจำเป็นในการฆ่าเธรดซอมบี้ สิ่งที่เกิดขึ้นคือบล็อคเธรดอินเตอร์รัปเตอร์ (!) บนการเรียกไปยัง reader.interrupt () โปรแกรมทดสอบอื่น (ไม่แสดงที่นี่) พยายามปิดช่องสัญญาณเพิ่มเติม แต่ก็ไม่ทำงานเช่นกัน


ไม่ทำงานบน mac os ไม่ได้ใช้กับ JDK 1.6 หรือกับ JDK 1.7 อินเตอร์รัปต์จะถูกจดจำหลังจากกดย้อนกลับระหว่างการอ่านเท่านั้น
Mostowski ล่ม

4

ดังที่ jt พูดว่า NIO เป็นทางออกที่ดีที่สุด (และถูกต้อง) หากคุณติดอยู่กับ InputStream จริงๆคุณก็สามารถทำได้

  1. วางเธรดที่มีงานพิเศษคืออ่านจาก InputStream และใส่ผลลัพธ์ลงในบัฟเฟอร์ที่สามารถอ่านได้จากเธรดดั้งเดิมของคุณโดยไม่ปิดกั้น สิ่งนี้จะทำงานได้ดีถ้าคุณมีเพียงหนึ่งอินสแตนซ์ของสตรีม มิฉะนั้นคุณอาจสามารถฆ่าเธรดโดยใช้เมธอดที่เลิกใช้แล้วในคลาสเธรดแม้ว่าจะทำให้ทรัพยากรรั่วไหล

  2. พึ่งพา isAvailable เพื่อระบุข้อมูลที่สามารถอ่านได้โดยไม่ปิดกั้น อย่างไรก็ตามในบางกรณี (เช่นกับ Sockets) อาจใช้การอ่านเพื่อบล็อก isAvailable เพื่อรายงานสิ่งอื่นที่ไม่ใช่ 0


5
Socket.setSoTimeout()เป็นทางออกที่ถูกต้องและเรียบง่ายกว่ากัน HttpURLConnection.setReadTimeout()หรือ
มาร์ควิสแห่ง Lorne

3
@EJP - เหล่านี้เป็น "ถูกต้องเท่ากัน" ภายใต้สถานการณ์บางอย่าง; เช่นถ้ากระแสอินพุตเป็นซ็อกเก็ตสตรีม / กระแสการเชื่อมต่อ HTTP
Stephen C

1
@Stephen C NIO เป็นแบบไม่บล็อกและสามารถเลือกได้ภายใต้สถานการณ์เดียวกัน ไม่มีไฟล์ I / O ที่ไม่มีการบล็อกเช่น
มาร์ควิสแห่ง Lorne

2
@EJP แต่ไม่มีการปิดกั้นไปป์ IO (System.in), การปิดกั้น I / O สำหรับไฟล์ (บนดิสก์ภายในเครื่อง) นั้นเป็นเรื่องไร้สาระ
woky

1
@EJP ส่วนใหญ่ (ทั้งหมดหรือไม่) Unices System.in เป็นไพพ์จริง ๆ (หากคุณไม่ได้บอกให้เชลล์แทนที่ด้วยไฟล์) และในฐานะไพพ์คุณสามารถไม่บล็อก
woky

0

ได้แรงบันดาลใจจากคำตอบนี้ฉันจึงได้วิธีแก้ปัญหาเชิงวัตถุเพิ่มขึ้นอีกเล็กน้อย

สิ่งนี้จะใช้ได้ก็ต่อเมื่อคุณต้องการอ่านตัวอักษร

คุณสามารถแทนที่ BufferedReader และนำสิ่งนี้มาใช้:

public class SafeBufferedReader extends BufferedReader{

    private long millisTimeout;

    ( . . . )

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        try {
            waitReady();
        } catch(IllegalThreadStateException e) {
            return 0;
        }
        return super.read(cbuf, off, len);
    }

    protected void waitReady() throws IllegalThreadStateException, IOException {
        if(ready()) return;
        long timeout = System.currentTimeMillis() + millisTimeout;
        while(System.currentTimeMillis() < timeout) {
            if(ready()) return;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                break; // Should restore flag
            }
        }
        if(ready()) return; // Just in case.
        throw new IllegalThreadStateException("Read timed out");
    }
}

นี่เป็นตัวอย่างที่เกือบสมบูรณ์

ฉันกลับ 0 ในวิธีการบางอย่างคุณควรเปลี่ยนเป็น -2 เพื่อตอบสนองความต้องการของคุณ แต่ฉันคิดว่า 0 เหมาะสมกับสัญญา BufferedReader ไม่มีอะไรผิดพลาดเกิดขึ้นเพียงแค่อ่าน 0 ตัวอักษร วิธี readLine เป็นนักฆ่าประสิทธิภาพที่น่ากลัว คุณควรสร้าง BufferedReader ใหม่ทั้งหมดหากคุณต้องการใช้ readLin e ตอนนี้มันไม่ปลอดภัยเธรด หากมีคนเรียกใช้การดำเนินการในขณะที่ readLines กำลังรอสายมันจะสร้างผลลัพธ์ที่ไม่คาดคิด

ฉันไม่ชอบกลับ -2 ฉันอยู่ที่ไหน ฉันขอยกเว้นเพราะบางคนอาจตรวจสอบว่า int <0 เพื่อพิจารณา EOS อย่างไรก็ตามวิธีการเหล่านั้นอ้างว่า "ไม่สามารถปิดกั้น" คุณควรตรวจสอบว่าคำสั่งนั้นเป็นจริงหรือไม่

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.nio.CharBuffer;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

/**
 * 
 * readLine
 * 
 * @author Dario
 *
 */
public class SafeBufferedReader extends BufferedReader{

    private long millisTimeout;

    private long millisInterval = 100;

    private int lookAheadLine;

    public SafeBufferedReader(Reader in, int sz, long millisTimeout) {
        super(in, sz);
        this.millisTimeout = millisTimeout;
    }

    public SafeBufferedReader(Reader in, long millisTimeout) {
        super(in);
        this.millisTimeout = millisTimeout;
    }



    /**
     * This is probably going to kill readLine performance. You should study BufferedReader and completly override the method.
     * 
     * It should mark the position, then perform its normal operation in a nonblocking way, and if it reaches the timeout then reset position and throw IllegalThreadStateException
     * 
     */
    @Override
    public String readLine() throws IOException {
        try {
            waitReadyLine();
        } catch(IllegalThreadStateException e) {
            //return null; //Null usually means EOS here, so we can't.
            throw e;
        }
        return super.readLine();
    }

    @Override
    public int read() throws IOException {
        try {
            waitReady();
        } catch(IllegalThreadStateException e) {
            return -2; // I'd throw a runtime here, as some people may just be checking if int < 0 to consider EOS
        }
        return super.read();
    }

    @Override
    public int read(char[] cbuf) throws IOException {
        try {
            waitReady();
        } catch(IllegalThreadStateException e) {
            return -2;  // I'd throw a runtime here, as some people may just be checking if int < 0 to consider EOS
        }
        return super.read(cbuf);
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        try {
            waitReady();
        } catch(IllegalThreadStateException e) {
            return 0;
        }
        return super.read(cbuf, off, len);
    }

    @Override
    public int read(CharBuffer target) throws IOException {
        try {
            waitReady();
        } catch(IllegalThreadStateException e) {
            return 0;
        }
        return super.read(target);
    }

    @Override
    public void mark(int readAheadLimit) throws IOException {
        super.mark(readAheadLimit);
    }

    @Override
    public Stream<String> lines() {
        return super.lines();
    }

    @Override
    public void reset() throws IOException {
        super.reset();
    }

    @Override
    public long skip(long n) throws IOException {
        return super.skip(n);
    }

    public long getMillisTimeout() {
        return millisTimeout;
    }

    public void setMillisTimeout(long millisTimeout) {
        this.millisTimeout = millisTimeout;
    }

    public void setTimeout(long timeout, TimeUnit unit) {
        this.millisTimeout = TimeUnit.MILLISECONDS.convert(timeout, unit);
    }

    public long getMillisInterval() {
        return millisInterval;
    }

    public void setMillisInterval(long millisInterval) {
        this.millisInterval = millisInterval;
    }

    public void setInterval(long time, TimeUnit unit) {
        this.millisInterval = TimeUnit.MILLISECONDS.convert(time, unit);
    }

    /**
     * This is actually forcing us to read the buffer twice in order to determine a line is actually ready.
     * 
     * @throws IllegalThreadStateException
     * @throws IOException
     */
    protected void waitReadyLine() throws IllegalThreadStateException, IOException {
        long timeout = System.currentTimeMillis() + millisTimeout;
        waitReady();

        super.mark(lookAheadLine);
        try {
            while(System.currentTimeMillis() < timeout) {
                while(ready()) {
                    int charInt = super.read();
                    if(charInt==-1) return; // EOS reached
                    char character = (char) charInt;
                    if(character == '\n' || character == '\r' ) return;
                }
                try {
                    Thread.sleep(millisInterval);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // Restore flag
                    break;
                }
            }
        } finally {
            super.reset();
        }
        throw new IllegalThreadStateException("readLine timed out");

    }

    protected void waitReady() throws IllegalThreadStateException, IOException {
        if(ready()) return;
        long timeout = System.currentTimeMillis() + millisTimeout;
        while(System.currentTimeMillis() < timeout) {
            if(ready()) return;
            try {
                Thread.sleep(millisInterval);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // Restore flag
                break;
            }
        }
        if(ready()) return; // Just in case.
        throw new IllegalThreadStateException("read timed out");
    }

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.