2009-05-16 28 views
5

Có bài viết/thuật toán nào về cách tôi có thể đọc một tệp dài với một tốc độ nhất định không?Đọc tập tin với tốc độ nhất định trong Java

Giả sử tôi không muốn vượt qua 10 KB/giây khi phát hành lần đọc.

+1

Câu hỏi đặt ra là tại sao bạn sẽ muốn đọc một tệp với tốc độ nhất định? Có vẻ như bạn muốn đọc dữ liệu theo yêu cầu, do đó, hiểu "nhu cầu" của bạn có thể cho phép chúng tôi chỉ cho bạn một giải pháp tốt hơn. – EFraim

+0

Tôi sẽ tải xuống một tệp lớn ngoài internet nhưng tôi không muốn ứng dụng của mình vượt qua giới hạn bộ người dùng. –

+0

https://svn.apache.org/repos/asf/hadoop/common/trunk/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/ThrottledInputStream.java – pvllnspk

Trả lời

4

Giải pháp thô chỉ là đọc một đoạn tại một thời điểm và sau đó ngủ ví dụ 10k sau đó ngủ một giây. Nhưng câu hỏi đầu tiên tôi phải hỏi là: tại sao? Có một vài câu trả lời có khả năng:

  1. Bạn không muốn tạo công việc nhanh hơn có thể thực hiện; hoặc
  2. Bạn không muốn tạo tải quá lớn trên hệ thống.

Đề xuất của tôi không kiểm soát ở cấp độ đọc. Đó là loại lộn xộn và không chính xác. Thay vào đó hãy kiểm soát nó ở cuối công việc. Java có rất nhiều công cụ đồng thời tuyệt vời để giải quyết vấn đề này. Có một vài cách khác để thực hiện việc này.

Tôi có xu hướng thích sử dụng mẫu producer consumer để giải quyết loại sự cố này. Nó cung cấp cho bạn các tùy chọn tuyệt vời về việc có thể theo dõi tiến trình bằng cách có một chuỗi báo cáo và như vậy và nó có thể là một giải pháp thực sự sạch sẽ.

Có thể sử dụng một thứ như ArrayBlockingQueue cho loại điều chỉnh cần thiết cho cả (1) và (2). Với dung lượng giới hạn, đầu đọc cuối cùng sẽ chặn khi hàng đợi đầy, do đó sẽ không lấp đầy quá nhanh. Các công nhân (người tiêu dùng) có thể được kiểm soát để chỉ làm việc quá nhanh để cũng tăng tốc độ bao gồm (2).

1

Nó phụ thuộc một chút vào việc bạn có nghĩa là "không vượt quá một tỷ lệ nhất định" hay "ở gần một tỷ lệ nhất định".

Nếu bạn có nghĩa là "không vượt quá", bạn có thể đảm bảo rằng với một vòng lặp đơn giản:

while not EOF do 
    read a buffer 
    Thread.wait(time) 
    write the buffer 
od 

Lượng thời gian để chờ đợi là một chức năng đơn giản về kích thước của bộ đệm; nếu kích thước bộ đệm là 10K byte, bạn muốn đợi một giây giữa các lần đọc.

Nếu bạn muốn tiến gần hơn, bạn có thể cần sử dụng bộ hẹn giờ.

  • tạo ra một Runnable để làm việc đọc
  • tạo Timer với một TimerTask để làm đọc lịch
  • các TimerTask n lần một giây.

Nếu bạn lo ngại về tốc độ truyền dữ liệu đến một thứ khác, thay vì kiểm soát đọc, hãy đưa dữ liệu vào cấu trúc dữ liệu như hàng đợi hoặc bộ đệm tròn và điều khiển đầu kia; gửi dữ liệu định kỳ. Bạn cần phải cẩn thận với điều đó, tuy nhiên, tùy thuộc vào kích thước tập dữ liệu và như vậy, bởi vì bạn có thể chạy vào giới hạn bộ nhớ nếu người đọc nhanh hơn rất nhiều so với người viết.

1

Nếu bạn đã sử dụng Java I/O thì bạn nên làm quen với các luồng trang trí. Tôi đề xuất một lớp con InputStream cần có một số khác InputStream và điều chỉnh lưu lượng. (Bạn có thể phân lớp FileInputStream nhưng cách tiếp cận đó rất dễ bị lỗi và không linh hoạt.)

Việc triển khai chính xác của bạn sẽ tùy thuộc vào yêu cầu chính xác của bạn. Nói chung, bạn sẽ muốn ghi lại thời gian đọc cuối cùng của bạn trở lại (System.nanoTime). Trên đọc hiện tại, sau khi đọc cơ bản, wait cho đến khi đủ thời gian trôi qua cho lượng dữ liệu được truyền. Một thực hiện phức tạp hơn có thể đệm và trả về (gần như) ngay lập tức với chỉ nhiều dữ liệu theo tỷ lệ quy định (hãy cẩn thận rằng bạn chỉ nên trả về độ dài đọc là 0 nếu bộ đệm có độ dài bằng không).

4
  • khi! EOF
    • cửa hàng System.currentTimeMillis() + 1000 (1 giây) trong một thời gian dài biến
    • đọc một 10K đệm
    • kiểm tra nếu thời gian lưu trữ đã trôi qua
      • nếu không, Thread.sleep() cho thời gian được lưu trữ - thời gian hiện tại

Tạo ThrottledInputStream cần một InputStream khác như đề xuất sẽ là một giải pháp tốt đẹp.

11

Một giải pháp đơn giản, bằng cách tạo ThrottledInputStream.

này nên được sử dụng như thế này:

 final InputStream slowIS = new ThrottledInputStream(new BufferedInputStream(new FileInputStream("c:\\file.txt"),8000),300); 

300 là số kilobyte trên giây. 8000 là kích thước khối cho BufferedInputStream.

Điều này tất nhiên nên được khái quát hóa bằng cách thực hiện đọc (byte b [], int off, int len), mà sẽ phụ tùng cho bạn một tấn các cuộc gọi System.currentTimeMillis(). System.currentTimeMillis() được gọi một lần cho mỗi byte đọc, mà có thể gây ra một chút chi phí. Cũng có thể lưu trữ số lượng byte có thể đọc được mà không cần gọi System.currentTimeMillis().

Đảm bảo đặt BufferedInputStream ở giữa, nếu không thì FileInputStream sẽ được thăm dò trong một byte chứ không phải là khối. Điều này sẽ làm giảm mẫu tải CPU 10% xuống gần 0. Bạn sẽ có nguy cơ vượt quá tốc độ dữ liệu theo số byte trong kích thước khối.

import java.io.InputStream; 
import java.io.IOException; 

public class ThrottledInputStream extends InputStream { 
    private final InputStream rawStream; 
    private long totalBytesRead; 
    private long startTimeMillis; 

    private static final int BYTES_PER_KILOBYTE = 1024; 
    private static final int MILLIS_PER_SECOND = 1000; 
    private final int ratePerMillis; 

    public ThrottledInputStream(InputStream rawStream, int kBytesPersecond) { 
     this.rawStream = rawStream; 
     ratePerMillis = kBytesPersecond * BYTES_PER_KILOBYTE/MILLIS_PER_SECOND; 
    } 

    @Override 
    public int read() throws IOException { 
     if (startTimeMillis == 0) { 
      startTimeMillis = System.currentTimeMillis(); 
     } 
     long now = System.currentTimeMillis(); 
     long interval = now - startTimeMillis; 
     //see if we are too fast.. 
     if (interval * ratePerMillis < totalBytesRead + 1) { //+1 because we are reading 1 byte 
      try { 
       final long sleepTime = ratePerMillis/(totalBytesRead + 1) - interval; // will most likely only be relevant on the first few passes 
       Thread.sleep(Math.max(1, sleepTime)); 
      } catch (InterruptedException e) {//never realized what that is good for :) 
      } 
     } 
     totalBytesRead += 1; 
     return rawStream.read(); 
    } 
} 
+1

FYI : Ngoại lệ bị gián đoạn là để đảm bảo rằng Thread có thể phản ứng ngay lập tức với một yêu cầu ngắt, ngay cả khi nó đang ngủ. – Simiil

0

Bạn có thể sử dụng Trình đánh giá. Và thực hiện việc đọc của riêng bạn trong InputStream. Một ví dụ về điều này có thể được nhìn thấy dưới đây

public class InputStreamFlow extends InputStream { 
    private final InputStream inputStream; 
    private final RateLimiter maxBytesPerSecond; 

    public InputStreamFlow(InputStream inputStream, RateLimiter limiter) { 
     this.inputStream = inputStream; 
     this.maxBytesPerSecond = limiter; 
    } 

    @Override 
    public int read() throws IOException { 
     maxBytesPerSecond.acquire(1); 
     return (inputStream.read()); 
    } 

    @Override 
    public int read(byte[] b) throws IOException { 
     maxBytesPerSecond.acquire(b.length); 
     return (inputStream.read(b)); 
    } 

    @Override 
    public int read(byte[] b, int off, int len) throws IOException { 
     maxBytesPerSecond.acquire(len); 
     return (inputStream.read(b,off, len)); 
    } 
} 

nếu bạn muốn hạn chế dòng chảy của 1 MB/s bạn có thể lấy dòng đầu vào như thế này:

final RateLimiter limiter = RateLimiter.create(RateLimiter.ONE_MB); 
final InputStreamFlow inputStreamFlow = new InputStreamFlow(originalInputStream, limiter); 
Các vấn đề liên quan