创建具有有限吞吐量的OutputStream

时间:2012-11-05 21:48:13

标签: java io

我将数据从我的Java应用程序通过USB连接推送到另一端的Arduino。

Arduino只能在其末端缓冲64字节的数据,所以我必须限制每个字节中发送的字节数量#gulp'从我的Java应用程序(多余的字节将丢失)。当Arduino代码准备接收更多字节时,它将发送一个简单的ping线。

所以,我在类BufferedOutputStream中扩展了ArduinoBufferedOutputStream,这实际上是输出流的一部分。从Java应用程序的不同部分,将任意数量的字节写入流(使用write(byte b)),并且流是flush。{/ p>

我需要(我猜)是覆盖BufferedOutputStream的flush方法,以便在从Arduino接收ping之前它不会发送超过64个字节,此时流应该发送64个字节(或更少)。

     static class ArduinoBufferedOutputStream extends BufferedOutputStream {

        public static final int WIRE_CAPACITY = 25;
        private byte[] waiting = new byte[0];
        private int onWire = 0;

        public ArduinoBufferedOutputStream(final OutputStream wrapped) throws IOException {
            super(wrapped, 500);
        }

        public void ping() {
            this.onWire = 0;
            this.flush();
        }

        @Override
        public void flush() throws IOException {
            if (this.onWire >= WIRE_CAPACITY) {
                return; // we're exceeding capacity, don't to anything before the next ping
            }
            if (this.count > WIRE_CAPACITY) {
                this.waiting = new byte[this.count - WIRE_CAPACITY];
                System.arraycopy(this.buf, WIRE_CAPACITY, waiting, 0, this.count - WIRE_CAPACITY);
                this.buf = Arrays.copyOfRange(this.buf, 0, WIRE_CAPACITY);
                this.count = WIRE_CAPACITY;
            } else {
                this.waiting = new byte[0];
            }
            onWire += this.count;
            super.flush();
            if (this.waiting.length > 0) {
                System.arraycopy(this.waiting, 0, this.buf, 0, Math.min(this.waiting.length, WIRE_CAPACITY));
                this.count = Math.min(this.waiting.length, WIRE_CAPACITY);
            }
        }
    }

但是,这不能正常工作。如果缓冲区包含超过WIRE_CAPACITY个字节,则会丢失字节,如以下测试用例所示:

@Test
public void testStream() throws IOException {
    final ArduinoBufferedOutputStream stream = new ArduinoDisplayOutputStream.ArduinoBufferedOutputStream(System.out);
    stream.write("This is a very, very long string, which must be made even longer to demonstrate the problem".getBytes());
    stream.flush();
    stream.ping();
    stream.ping();
    stream.ping();
    stream.ping();
    stream.ping();
    stream.ping();
    stream.ping();
    stream.ping();
    stream.ping();
}

打印以下字符串:This is a very, very long string, which must be ma,而我显然希望打印整个字符串。

有谁能看到我在这里做错了什么? 或者甚至更好,是否有人知道现有的库可以满足我的需求?

3 个答案:

答案 0 :(得分:2)

我确实没有任何使用Arduino的经验,但我在处理Java Output / InputStreams方面有很多经验。拿一块盐,因为我无法在真正的Arduino设备上进行实际测试,但如果我在你的情况下,这就是我要做的。

我怀疑你对stream.write("This is a very, very long string, which must be made even longer to demonstrate the problem".getBytes());的调用实际上是在缓冲整个字符串。当您随后调用stream.flush()时,所有字符串数据都会立即“写入”Arduino设备。我怀疑这是因为你正在调用大小为500字节的BufferedOutputStream超级构造函数。首先,我尝试将其减少到64字节。但是,我怀疑这可能无法解决问题,因为BufferedOutputStream无法知道何时致电ping

我认为您要覆盖writeflush。根据Java规范,flush背后的想法是flush应该强制将任何未写入的缓冲字节写入底层流:

http://docs.oracle.com/javase/7/docs/api/java/io/OutputStream.html#flush()

我也可能只是扩展普通OutputStream并自己做任何缓冲。我会覆盖write方法,一次缓冲多达64字节的数据。如果64字节空间不足,请在填充的64字节缓冲区上调用super.write(),然后在缓冲/写入更多数据之前调用super.flush()然后调用ping()。在while循环中重复此过程,直到缓冲区有足够的空间来容纳输入数据的剩余部分。然后重写flush以写入任何未完全填充底层64字节缓冲区的数据(因此实际上并未通过之前调用super.write()写入)。

这样,如果您对过多数据调用write,您的类将写入基础流,直到最多保留64个字节,随后对flush的调用将确保最后几个字节实际上写入Arduino。在一天结束时,您还可以获得完全符合Java的OutputStream实现,这也很不错。

修改

我使用64字节只是因为你声明你的Arduino一次可以处理那么多。当然,如果有必要,可以减少这个,为了方便,我只是使用“64”。

答案 1 :(得分:1)

您的flush()在每次通话中写入WIRE_CAPACITY =最多25个字节。你调用它两次,总输出是50字节(假设cp1252编码)。我认为没有错。

也许您应该在flush()内拨打ping()?那么你的测试基本上就会成为:

@Test
public void testStream() throws IOException {
    final ArduinoDisplayOutputStream.ArduinoBufferedOutputStream stream = new ArduinoDisplayOutputStream.ArduinoBufferedOutputStream(System.out);
    stream.write("This is a very, very long string, which must be made even longer to demonstrate the problem".getBytes());
    stream.flush();
    stream.ping();
    stream.ping();
    ... // as many ping()s as written bytes / WIRE_CAPACITY
}

答案 2 :(得分:0)

好吧,我想我最终做对了。延长BufferedOutputStream是一个死胡同。相反,我使用一个单独的缓冲区,如@ ben-lawry建议的那样。通过使用ConcurrentLinkedQueue作为缓冲区,我可以轻松地在每个WIRE CAPACITY上轮询flush()个字节数。

人们可能会说,效率不是很高,但与Arduino处理相比,Java方面的工作仍然会很快。

这是我得到的:

   static class ArduinoBufferedOutputStream extends OutputStream {

        public static final int WIRE_CAPACITY = 25;
        private final ConcurrentLinkedQueue<Byte> buffer = new ConcurrentLinkedQueue<Byte>();
        private int onWire = 0;
        private OutputStream wrapped;

        public ArduinoBufferedOutputStream(final OutputStream wrapped) throws IOException {
            super();
            this.wrapped = wrapped;
        }

        @Override
        public void write(int i) throws IOException {
            this.buffer.add((byte) i);
        }

        @Override
        public void write(byte[] bytes) throws IOException {
            for (byte aByte : bytes) {
                this.buffer.add(aByte);
            }
        }

        @Override
        public void write(byte[] bytes, int off, int len) throws IOException {
            for (int i = off; i < len; i++) {
                this.buffer.add(bytes[i]);
            }
        }

        public void ping() throws IOException {
            synchronized (this.buffer) {
                this.onWire = 0;
            }
            this.flush();
        }

        @Override
        public void flush() throws IOException {
            synchronized (this.buffer) {
                for (; this.onWire < WIRE_CAPACITY && buffer.size() > 0; this.onWire++) {
                    wrapped.write(buffer.poll());
                }
            }
        }
    }

谢谢,@ gpeche和@ ben-lawry - 这肯定有助于更多地关注这个问题。