Java读取主机文件生成XML遇字段空值,求多行关联记录处理方案
问题:Java读取主机文件转XML时字段为空,需实现多行关联基金记录生成逻辑
- 需求:读取主机文件并转换为指定结构的XML,保留前一行基金数据直到新基金记录触发,每条带
-M标识的余额行生成独立<ACTIVITY>节点。逻辑为:匹配基金起始条件时开始新记录,后续符合条件的行加入当前记录,再次匹配起始条件则开启新记录。 - 当前问题:FUND、FUNDID等字段均为空值,无法正确生成目标XML。
主机文件示例行
AMFDS 2045 TDRF R 1XA 100.0% 5,634.40 376.91 251.07 .00 .00 6,260.93-M .00 1.45-M
期望XML输出
<ACTIVITY> <FUND><![CDATA[AMFDS 2045 TDRF R]]></FUND> <FUND_ID><![CDATA[1X]]></FUND_ID> <SOURCE><![CDATA[A]]></SOURCE> <MIXPERCENTAGE><![CDATA[100.0%]]></MIXPERCENTAGE> <YTD><![CDATA[376.91]]></YTD> <BALANCE><![CDATA[6,260.93]]></BALANCE> </ACTIVITY> <ACTIVITY> <FUND><![CDATA[AMFDS 2045 TDRF R]]></FUND> <FUND_ID><![CDATA[1X]]></FUND_ID> <SOURCE><![CDATA[A]]></SOURCE> <MIXPERCENTAGE><![CDATA[100.0%]]></MIXPERCENTAGE> <YTD><![CDATA[376.91]]></YTD> <BALANCE><![CDATA[1.45]]></BALANCE> </ACTIVITY>
当前代码片段
核心处理方法
private void processFundRecords(Supplier<Stream<String>> streamSupplier, Map<String, List<String>> recordMap){ List<String> fundLines = new ArrayList<>(); streamSupplier.get().forEach(line -> { if (isFund(streamSupplier)) { // 如果是新基金的起始行,先处理之前的基金行 if (!fundLines.isEmpty()) { processFundData((Supplier<Stream<String>>) fundLines, recordMap); fundLines.clear(); // 重置当前基金行列表 } fundLines.add(line); // 开始新的基金记录 } else if (isPartOfSameFundRecord(streamSupplier) || line.startsWith("LOAN")) { fundLines.add(line); // 添加到当前基金记录 } }); // 处理剩余的基金行 if (!fundLines.isEmpty()) { processFundData((Supplier<Stream<String>>) fundLines, recordMap); } } private boolean ignoreLines(Supplier<Stream<String>> streamSupplier) { return streamSupplier.get().anyMatch(line -> line.startsWith("** TOTAL") || line.startsWith("***** INVESTMENTS") || line.startsWith("***** SOURCES") ); } private void processFundData(Supplier<Stream<String>> streamSupplier, Map<String, List<String>> recordMap) { if (streamSupplier.get().allMatch(s -> s.length() == 0)) { return; } if(!ignoreLines(streamSupplier)) { recordMap.put(FUND, streamSupplier.get() .map(s -> s.substring(0, 17).trim()) .collect(Collectors.toList())); recordMap.put(FUNDID, streamSupplier.get() .map(s -> s.substring(18, 20).trim()) .collect(Collectors.toList())); recordMap.put(SOURCE, streamSupplier.get() .map(s -> s.substring(20, 21).trim()) .collect(Collectors.toList())); recordMap.put(MIXPERCENTAGE, streamSupplier.get().map(s -> s.substring(22, 28).trim()) .collect(Collectors.toList())); recordMap.put(YTD, streamSupplier.get().map(s -> s.substring(43, 55).trim()) .collect(Collectors.toList())); recordMap.put(MBALANCE, streamSupplier.get() .filter(s -> s.length() > 115 && s.substring(115, 117).contains("-M")) .map(s -> s.substring(103, 115).trim()) .collect(Collectors.toList())); } }
调用逻辑片段
if (isPartOfSameFundRecord(streamSupplier)) { fundFound = true; activityKeyBucketMap = initializeNewMap(activityKeyBucketList); processFundData(streamSupplier, activityKeyBucketMap); //System.out.println("#### FUND ###"); }
问题分析与解决方案
1. 字段空值问题根源
Supplier<Stream<String>>误用:每次调用streamSupplier.get()都会生成新流,前一次消费后流已关闭,后续调用返回空数据,导致字段值为空。- 非法类型转换:将
List<String>强转为Supplier<Stream<String>>,会导致运行时异常或数据错误。 - 判断逻辑参数错误:
isFund和isPartOfSameFundRecord方法接收的是整个流的供应商,而非当前处理的单行,导致判断逻辑失效。
2. 修复步骤
第一步:修正判断方法参数
将判断方法改为基于单行内容判断:
// 判断是否为基金起始行(可根据实际格式调整规则) private boolean isFund(String line) { return line.trim().length() > 20 && !line.trim().startsWith(" "); } // 判断是否为当前基金的续行(比如余额行以大量空格开头) private boolean isPartOfSameFundRecord(String line) { return !line.trim().isEmpty() && line.startsWith(" "); }
第二步:重构核心处理逻辑
用实体类缓存基金元数据,避免重复解析:
// 存储基金元数据的实体类 static class FundMetadata { private String fund; private String fundId; private String source; private String mixPercentage; private String ytd; // getter、setter方法省略 } // 存储ACTIVITY数据的实体类 static class Activity { private String fund; private String fundId; private String source; private String mixPercentage; private String ytd; private String balance; // getter、setter方法省略 } private void processFundRecords(Supplier<Stream<String>> streamSupplier, List<Activity> activities) { List<String> fundLines = new ArrayList<>(); FundMetadata currentFund = null; streamSupplier.get().forEach(line -> { String trimmedLine = line.trim(); if (trimmedLine.isEmpty()) return; if (isFund(line)) { // 处理上一个基金的余额行 if (currentFund != null && !fundLines.isEmpty()) { processBalanceLines(currentFund, fundLines, activities); fundLines.clear(); } // 解析新基金的元数据 currentFund = parseFundMetadata(line); fundLines.add(line); } else if (isPartOfSameFundRecord(line) || line.startsWith("LOAN")) { fundLines.add(line); } }); // 处理最后一个基金的余额行 if (currentFund != null && !fundLines.isEmpty()) { processBalanceLines(currentFund, fundLines, activities); } } // 解析基金元数据 private FundMetadata parseFundMetadata(String line) { FundMetadata metadata = new FundMetadata(); metadata.setFund(line.substring(0, 17).trim()); metadata.setFundId(line.substring(18, 20).trim()); metadata.setSource(line.substring(20, 21).trim()); metadata.setMixPercentage(line.substring(22, 28).trim()); metadata.setYtd(line.substring(43, 55).trim()); return metadata; }
第三步:处理余额行生成ACTIVITY节点
private void processBalanceLines(FundMetadata fundMetadata, List<String> fundLines, List<Activity> activities) { for (String line : fundLines) { if (line.length() > 115 && line.substring(115, 117).contains("-M")) { String balance = line.substring(103, 115).trim(); Activity activity = new Activity(); activity.setFund(fundMetadata.getFund()); activity.setFundId(fundMetadata.getFundId()); activity.setSource(fundMetadata.getSource()); activity.setMixPercentage(fundMetadata.getMixPercentage()); activity.setYtd(fundMetadata.getYtd()); activity.setBalance(balance); activities.add(activity); } } }
第四步:生成XML输出
private String generateXml(List<Activity> activities) { StringBuilder xmlBuilder = new StringBuilder(); for (Activity activity : activities) { xmlBuilder.append("<ACTIVITY>\n"); xmlBuilder.append("<FUND><![CDATA[").append(activity.getFund()).append("]]></FUND>\n"); xmlBuilder.append("<FUND_ID><![CDATA[").append(activity.getFundId()).append("]]></FUND_ID>\n"); xmlBuilder.append("<SOURCE><![CDATA[").append(activity.getSource()).append("]]></SOURCE>\n"); xmlBuilder.append("<MIXPERCENTAGE><![CDATA[").append(activity.getMixPercentage()).append("]]></MIXPERCENTAGE>\n"); xmlBuilder.append("<YTD><![CDATA[").append(activity.getYtd()).append("]]></YTD>\n"); xmlBuilder.append("<BALANCE><![CDATA[").append(activity.getBalance()).append("]]></BALANCE>\n"); xmlBuilder.append("</ACTIVITY>\n"); } return xmlBuilder.toString(); }
3. 关键改进点
- 用
FundMetadata缓存基金信息,确保每条余额行复用同一基金的元数据。 - 避免重复消费Stream,直接操作
List<String>处理行数据。 - 拆分解析与生成逻辑,代码结构更清晰,便于维护。
内容的提问来源于stack exchange,提问作者Mani
相关产品推荐
相关产品推荐

