Linux环境下XML输入流接收缓冲区部分丢失的技术问题求助
看起来你遇到的这个Linux远程环境下XML流截断的问题,根源其实出在两个容易被忽略的代码细节上——重复创建缓冲读取器和错误地依赖行分隔符处理XML。让我一步步给你拆解和解决:
1. 重复创建BufferedReader是致命问题
每次调用getXML()时,你都新建一个BufferedReader来包裹sock.getInputStream(),但你可能不知道,BufferedReader内部会预读一批数据到自己的缓冲区里。比如第一次处理完一个XML后,这个缓冲器可能已经把下一个XML的开头部分读进来了,但当方法执行完毕,这个BufferedReader被销毁,里面的缓冲数据就直接丢了。下一次调用getXML()时,新的BufferedReader会从socket流的当前位置(已经被之前的缓冲器读过的位置)开始读,自然就跳过了丢失的那部分数据,导致后面的XML直接从中间开始。
这个问题在本地环境(数据传输快,缓冲少)或者Windows系统(缓冲策略不同)可能不会立刻暴露,但在Linux远程连接时,网络延迟导致的缓冲累积就会触发这个问题——这也完美匹配了你给出的场景测试结果。
2. readLine()根本不适合处理XML流
XML的结构和行分隔符完全无关,</MEASUREMENT>标签可能出现在一行的中间,或者跨多行。用readLine()读取的话,不仅会以换行符为分割截断数据,还会自动丢弃换行符,这很容易破坏XML的结构。尤其是当发送端用的是Windows风格的\r\n换行,而Linux接收端处理成\n时,更容易出现读取错位的情况。
具体修复步骤
第一步:复用BufferedReader
在socket连接建立的时候,只创建一次BufferedReader,把它作为类的成员变量保存下来,后续所有的XML读取都复用这个实例,这样就不会丢失缓冲在里面的数据了。
第二步:替换readLine()为逐字符匹配结束标签
不要依赖行分隔符,而是逐段读取流中的数据,同时检查是否出现了完整的</MEASUREMENT>标签,这样能精准捕获每个XML报文的边界,不管标签在什么位置。
修改后的完整代码示例
首先,在你的类里添加一个成员变量:
// 全局复用的BufferedReader,只在socket连接时初始化一次 private BufferedReader xmlInputStreamReader;
然后在你建立socket连接的代码里初始化它:
// 假设这是你建立远程socket连接的代码 Socket sock = new Socket("远程地址", 端口号); xmlInputStreamReader = new BufferedReader(new InputStreamReader(sock.getInputStream()));
最后修改你的getXML()方法:
private Document getXML() throws JDOMException { SAXBuilder builder = new SAXBuilder(); try { StringBuilder xmlContent = new StringBuilder(); char[] readBuffer = new char[1024]; int charsRead; final String END_TAG = "</MEASUREMENT>"; int endTagLength = END_TAG.length(); // 保存最近读取的几个字符,用于匹配结束标签(避免标签被拆分成两个读取块) String recentChars = ""; while ((charsRead = xmlInputStreamReader.read(readBuffer)) != -1) { String chunk = new String(readBuffer, 0, charsRead); xmlContent.append(chunk); recentChars += chunk; // 只保留最近的endTagLength个字符,减少匹配的工作量 if (recentChars.length() > endTagLength) { recentChars = recentChars.substring(recentChars.length() - endTagLength); } // 检查是否找到了完整的结束标签 int endTagIndex = xmlContent.indexOf(END_TAG); if (endTagIndex != -1) { // 截取到结束标签的完整位置,加上根标签的闭合 String completeXml = xmlContent.substring(0, endTagIndex + endTagLength) + "</WEATHERDATA>"; System.out.println("XML DATA:" + completeXml); // 把未处理的剩余内容留在StringBuilder里,供下一次读取使用 xmlContent.delete(0, endTagIndex + endTagLength); // 解析XML并返回 return builder.build(new StringReader(completeXml)); } } return null; // 输入流已结束 } catch (IOException | NullPointerException e) { System.out.println("Client disconnected!"); return null; } }
额外优化建议
- 别再用
String拼接(xmlstream += line)了,改用StringBuilder,不仅性能更好,还能避免频繁创建字符串导致的内存碎片。 - 如果你的XML报文很大,可以考虑直接用SAX的流式解析API,不需要先拼接完整的XML字符串,这样更高效,还能节省内存。
内容的提问来源于stack exchange,提问作者Alterlai

