想要通过inputStream从套接字中读取字节。服务器发送的字节数量可能不固定,客户端无法预先知道字节数组的长度。如何完成这个任务?
byte b[] = new byte[1024];
int bytesRead = sock.getInputStream().read(b); //检查是否读取到数据
这样做可以避免Net BzEAnSZ报告“可能未初始化”的错误。帮助解决问题。
想要通过inputStream从套接字中读取字节。服务器发送的字节数量可能不固定,客户端无法预先知道字节数组的长度。如何完成这个任务?
byte b[] = new byte[1024];
int bytesRead = sock.getInputStream().read(b); //检查是否读取到数据
这样做可以避免Net BzEAnSZ报告“可能未初始化”的错误。帮助解决问题。
你需要根据需要扩展缓冲区,通过每次读取1024字节的块来实现,就像我之前写的这个示例代码一样。
byte[] resultBuff = new byte[0];
byte[] buff = new byte[1024];
int k = -1;
while((k = sock.getInputStream().read(buff, 0, buff.length)) > -1) {
byte[] tbuff = new byte[resultBuff.length + k]; // temp buffer size = bytes already read + bytes last read
System.arraycopy(resultBuff, 0, tbuff, 0, resultBuff.length); // copy previous bytes
System.arraycopy(buff, 0, tbuff, resultBuff.length, k); // copy current lot
resultBuff = tbuff; // call the temp buffer as your result buff
}
System.out.println(resultBuff.length + " bytes read.");
return resultBuff;
int available = resIn.available();
byte[] buff = new byte[available];
ByteArrayOutputStream bao = new ByteArrayOutputStream(available);
int bytesRead = -1;
while ((bytesRead = resIn.read(buff, 0, buff.length)) > -1) {
bao.write(buff, 0, bytesRead);
}
- Marcus BeckerByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
while(true) {
int n = is.read(buf);
if( n < 0 ) break;
baos.write(buf,0,n);
}
byte data[] = baos.toByteArray();
读取一个整数,它表示接收到的下一段数据的大小。创建一个与该大小相同的缓冲区,或使用现有的空间充足的缓冲区。将数据读入缓冲区中,确保它的大小不超过之前读取的大小。反复执行该操作 :)
如果像你所说的那样真的不知道大小,请像其他答案中提到的那样,将数据读入扩展的 ByteArrayOutputStream 中。但是,使用 size 方法确实是最可靠的。
byte b[] = new byte[BIG_ENOUGH];
int nosRead = sock.getInputStream().read(b);
其中BIG_ENOUGH
足够大。
但是总的来说,这存在一个大问题。单个read
调用不能保证返回另一端写入的所有内容。
如果nosRead
值为BIG_ENOUGH
,则您的应用程序无法确定是否还有更多字节要到达;另一端可能恰好发送了BIG_ENOUGH
字节...或多于BIG_ENOUGH
字节。在前一种情况下,如果尝试读取,则您的应用程序将永远阻塞。在后一种情况下,您的应用程序必须进行(至少)另一个read
以获取其余数据。
如果nosRead
值小于BIG_ENOUGH
,则您的应用程序仍然不知道。它可能已经接收到所有内容,部分数据可能已经延迟(由于网络数据包分段,网络数据包丢失,网络分区等),或者另一端可能在发送数据时被阻塞或崩溃。
最佳答案是:要么您的应用程序需要事先知道要期望多少字节,要么应用程序协议需要以某种方式告诉应用程序要期望多少字节或何时发送所有字节。
可能的方法包括:
如果没有这些策略之一,您的应用程序将被迫猜测,并有可能偶尔出错。
然后您可以使用多个read调用和(可能)多个缓冲区。
IOUtils.toByteArray(inputStream);
public static byte[] readInputStreamToByteArray(InputStream inputStream) {
if (inputStream == null) {
// normally, the caller should check for null after getting the InputStream object from a resource
throw new FileProcessingException("Cannot read from InputStream that is NULL. The resource requested by the caller may not exist or was not looked up correctly.");
}
try {
return IOUtils.toByteArray(inputStream);
} catch (IOException e) {
throw new FileProcessingException("Error reading input stream.", e);
} finally {
closeStream(inputStream);
}
}
private static void closeStream(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (Exception e) {
throw new FileProcessingException("IO Error closing a stream.", e);
}
}
在这里,FileProcessingException
是您应用程序特定的有意义的运行时异常,它将不受干扰地传递到您正确的处理程序,而不会污染中间代码。
将所有输入数据流式传输到输出流中。以下是一个可行的示例:
InputStream inputStream = null;
byte[] tempStorage = new byte[1024];//try to read 1Kb at time
int bLength;
try{
ByteArrayOutputStream outputByteArrayStream = new ByteArrayOutputStream();
if (fileName.startsWith("http"))
inputStream = new URL(fileName).openStream();
else
inputStream = new FileInputStream(fileName);
while ((bLength = inputStream.read(tempStorage)) != -1) {
outputByteArrayStream.write(tempStorage, 0, bLength);
}
outputByteArrayStream.flush();
//Here is the byte array at the end
byte[] finalByteArray = outputByteArrayStream.toByteArray();
outputByteArrayStream.close();
inputStream.close();
}catch(Exception e){
e.printStackTrace();
if (inputStream != null) inputStream.close();
}
BufferedInputStream
,并使用available()
方法返回可供读取的字节数大小,然后构造一个相应大小的byte[]
。问题解决了。 :)BufferedInputStream buf = new BufferedInputStream(is);
int size = buf.available();
这个问题已经有7年了,但我遇到了一个类似的问题,当我做一个可与 NIO 和 OIO 兼容的系统时(客户端和服务器可能是任何他们想要的,OIO 或 NIO)。
由于阻塞 InputStreams 的存在,这是一个相当大的挑战。
我找到了一种方法,使之成为可能,并且我想张贴出来,以帮助那些有类似问题的人。
在这里,使用DataInputStream读取动态大小的字节数组,可以简单地将其包装在socketInputStream周围。此外,我不想引入特定的通信协议(例如首先发送要发送的字节数的大小),因为我想使这尽可能原始。首先,我有一个简单的实用程序缓冲类,它看起来像这样:
import java.util.ArrayList;
import java.util.List;
public class Buffer {
private byte[] core;
private int capacity;
public Buffer(int size){
this.capacity = size;
clear();
}
public List<Byte> list() {
final List<Byte> result = new ArrayList<>();
for(byte b : core) {
result.add(b);
}
return result;
}
public void reallocate(int capacity) {
this.capacity = capacity;
}
public void teardown() {
this.core = null;
}
public void clear() {
core = new byte[capacity];
}
public byte[] array() {
return core;
}
}
这个类只是因为 Java 中字节和 Byte 自动装箱的愚蠢方式与此列表一起使用而存在。在这个例子中,这实际上并不是必需的,但我不想在这个解释中遗漏任何东西。
接下来是两个简单的核心方法。在这些方法中,一个 StringBuilder 被用作“回调”。它将被填充已读取的结果,并返回已读取的字节数量。当然,这可以以不同的方式完成。
private int readNext(StringBuilder stringBuilder, Buffer buffer) throws IOException {
// Attempt to read up to the buffers size
int read = in.read(buffer.array());
// If EOF is reached (-1 read)
// we disconnect, because the
// other end disconnected.
if(read == -1) {
disconnect();
return -1;
}
// Add the read byte[] as
// a String to the stringBuilder.
stringBuilder.append(new String(buffer.array()).trim());
buffer.clear();
return read;
}
private Optional<String> readBlocking() throws IOException {
final Buffer buffer = new Buffer(256);
final StringBuilder stringBuilder = new StringBuilder();
// This call blocks. Therefor
// if we continue past this point
// we WILL have some sort of
// result. This might be -1, which
// means, EOF (disconnect.)
if(readNext(stringBuilder, buffer) == -1) {
return Optional.empty();
}
while(in.available() > 0) {
buffer.reallocate(in.available());
if(readNext(stringBuilder, buffer) == -1) {
return Optional.empty();
}
}
buffer.teardown();
return Optional.of(stringBuilder.toString());
}
第一个方法readNext
将使用来自DataInputStream的byte[]
填充缓冲区,并返回以此方式读取的字节数。
在第二个方法中,readBlocking
,我利用了阻塞的特性,不必担心消费者生产者问题。简单地说,readBlocking
将阻塞,直到收到一个新的字节数组。在调用此阻塞方法之前,我们分配了一个缓冲区大小。注意,我在第一次读取后(在while循环内部)调用了reallocate。这不是必需的。您可以安全地删除此行,代码仍将正常工作。我之所以这样做,是因为我的问题的独特性。
我没有更详细地解释的两件事是: 1. in(DataInputStream和此处唯一的short变量,对此表示抱歉) 2. 断开连接(您的断开连接例程)
总而言之,现在您可以这样使用它:
// The in has to be an attribute, or an parameter to the readBlocking method
DataInputStream in = new DataInputStream(socket.getInputStream());
final Optional<String> rawDataOptional = readBlocking();
rawDataOptional.ifPresent(string -> threadPool.execute(() -> handle(string)));
这将为您提供一种通过套接字(或任何InputStream)读取任何形状或形式的字节数组的方法。希望这有所帮助!
这里是一个更简单的示例,使用ByteArrayOutputStream...
socketInputStream = socket.getInputStream();
int expectedDataLength = 128; //todo - set accordingly/experiment. Does not have to be precise value.
ByteArrayOutputStream baos = new ByteArrayOutputStream(expectedDataLength);
byte[] chunk = new byte[expectedDataLength];
int numBytesJustRead;
while((numBytesJustRead = socketInputStream.read(chunk)) != -1) {
baos.write(chunk, 0, numBytesJustRead);
}
return baos.toString("UTF-8");
有两种解决方案:
让发送方在传输字节后关闭套接字。然后在接收方,一直读取直到结束标志。
按照Chris的建议,让发送方添加长度字段,然后读取相应数量的字节。
使用自描述协议,例如XML、序列化等。