【问题标题】:Parsing a fixed-width formatted file in Java在 Java 中解析固定宽度格式的文件
【发布时间】:2009-10-22 20:38:57
【问题描述】:

我从供应商处获得了一个文件,该文件每行有 115 个固定宽度的字段。如何将该文件解析为 115 个字段,以便在我的代码中使用它们?

我的第一个想法就是为每个字段创建常量,例如 NAME_START_POSITIONNAME_LENGTH 并使用 substring。这看起来很丑,所以我很好奇更好的方法。谷歌搜索的几个图书馆似乎也没有一个更好。

【问题讨论】:

标签: java parsing fixed-width


【解决方案1】:

我会使用像flatworm 这样的平面文件解析器,而不是重新发明轮子:它有一个干净的 API,易于使用,有不错的错误处理和一个简单的文件格式描述符。另一种选择是jFFP,但我更喜欢第一种。

【讨论】:

  • 我只是想跟进,感谢您提供指向 Flatworm 的指针。它就像一个冠军,我的整个工作团队现在都在使用它。
  • @MattGrommes 很高兴知道你喜欢它。非常感谢您的跟进,非常感谢!
  • 我几天前试过图书馆,它坏得无法修复。我会尝试以前的版本,但我没有看到任何文档
  • 这是一个很棒的工具!有没有办法将它集成到某种编辑器中 - eclipse?
  • 游戏迟到了,但 github.com/ffpojo/ffpojo 在映射到 POJO 和从 POJO 映射时看起来不错
【解决方案2】:

我和fixedformat4j 玩过arround,非常棒。易于配置转换器等。

【讨论】:

  • 请注意,ff4j 使用运行时注解,这使得批量解析非常缓慢。
【解决方案3】:

uniVocity-parsers 带有 FixedWidthParserFixedWidthWriter 可以支持棘手的固定宽度格式,包括具有不同字段、填充等的行。

// creates the sequence of field lengths in the file to be parsed
FixedWidthFields fields = new FixedWidthFields(4, 5, 40, 40, 8);

// creates the default settings for a fixed width parser
FixedWidthParserSettings settings = new FixedWidthParserSettings(fields); // many settings here, check the tutorial.

//sets the character used for padding unwritten spaces in the file
settings.getFormat().setPadding('_');

// creates a fixed-width parser with the given settings
FixedWidthParser parser = new FixedWidthParser(settings);

// parses all rows in one go.
List<String[]> allRows = parser.parseAll(new File("path/to/fixed.txt")));

这里有几个examples for parsing 各种固定宽度的输入。

这里还有一些其他examples for writing in general 和其他fixed-width examples 特定于固定宽度格式。

披露:我是这个库的作者,它是开源免费的(Apache 2.0 许可)

【讨论】:

    【解决方案4】:

    这是我使用的一个基本实现:

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.io.Reader;
    import java.io.Writer;
    
    public class FlatFileParser {
    
      public static void main(String[] args) {
        File inputFile = new File("data.in");
        File outputFile = new File("data.out");
        int columnLengths[] = {7, 4, 10, 1};
        String charset = "ISO-8859-1";
        String delimiter = "~";
    
        System.out.println(
            convertFixedWidthFile(inputFile, outputFile, columnLengths, delimiter, charset)
            + " lines written to " + outputFile.getAbsolutePath());
      }
    
      /**
       * Converts a fixed width file to a delimited file.
       * <p>
       * This method ignores (consumes) newline and carriage return
       * characters. Lines returned is based strictly on the aggregated
       * lengths of the columns.
       *
       * A RuntimeException is thrown if run-off characters are detected
       * at eof.
       *
       * @param inputFile the fixed width file
       * @param outputFile the generated delimited file
       * @param columnLengths the array of column lengths
       * @param delimiter the delimiter used to split the columns
       * @param charsetName the charset name of the supplied files
       * @return the number of completed lines
       */
      public static final long convertFixedWidthFile(
          File inputFile,
          File outputFile,
          int columnLengths[],
          String delimiter,
          String charsetName) {
    
        InputStream inputStream = null;
        Reader inputStreamReader = null;
        OutputStream outputStream = null;
        Writer outputStreamWriter = null;
        String newline = System.getProperty("line.separator");
        String separator;
        int data;
        int currentIndex = 0;
        int currentLength = columnLengths[currentIndex];
        int currentPosition = 0;
        long lines = 0;
    
        try {
          inputStream = new FileInputStream(inputFile);
          inputStreamReader = new InputStreamReader(inputStream, charsetName);
          outputStream = new FileOutputStream(outputFile);
          outputStreamWriter = new OutputStreamWriter(outputStream, charsetName);
    
          while((data = inputStreamReader.read()) != -1) {
            if(data != 13 && data != 10) {
              outputStreamWriter.write(data);
              if(++currentPosition > (currentLength - 1)) {
                currentIndex++;
                separator = delimiter;
                if(currentIndex > columnLengths.length - 1) {
                  currentIndex = 0;
                  separator = newline;
                  lines++;
                }
                outputStreamWriter.write(separator);
                currentLength = columnLengths[currentIndex];
                currentPosition = 0;
              }
            }
          }
          if(currentIndex > 0 || currentPosition > 0) {
            String line = "Line " + ((int)lines + 1);
            String column = ", Column " + ((int)currentIndex + 1);
            String position = ", Position " + ((int)currentPosition);
            throw new RuntimeException("Incomplete record detected. " + line + column + position);
          }
          return lines;
        }
        catch (Throwable e) {
          throw new RuntimeException(e);
        }
        finally {
          try {
            inputStreamReader.close();
            outputStreamWriter.close();
          }
          catch (Throwable e) {
            throw new RuntimeException(e);
          }
        }
      }
    }
    

    【讨论】:

    • 2 年后,但我希望你能看到这一点。如果唯一可能的返回是来自输入流的字符或表示文件结尾的 -1,为什么需要检查读取的字符 data 是否等于 13 或 10?
    • 你是对的......这个实现用于以换行符结尾的固定宽度记录。
    【解决方案5】:

    最适合 Scala,但可能你可以在 Java 中使用它

    我已经厌倦了没有合适的固定长度格式库的事实,我已经创建了自己的。 您可以在这里查看:https://github.com/atais/Fixed-Length

    基本用法是创建一个案例类,并将其描述为HList(无形):

    case class Employee(name: String, number: Option[Int], manager: Boolean)
    
    object Employee {
    
        import com.github.atais.util.Read._
        import cats.implicits._
        import com.github.atais.util.Write._
        import Codec._
    
        implicit val employeeCodec: Codec[Employee] = {
          fixed[String](0, 10) <<:
            fixed[Option[Int]](10, 13, Alignment.Right) <<:
            fixed[Boolean](13, 18)
        }.as[Employee]
    }
    

    您现在可以轻松解码您的行或编码您的对象:

    import Employee._
    Parser.decode[Employee](exampleString)
    Parser.encode(exampleObject)
    

    【讨论】:

      【解决方案6】:

      如果您的字符串名为 inStr,请将其转换为 char 数组并使用 String(char[], start, length)构造函数

      char[] intStrChar = inStr.toCharArray();
      String charfirst10 = new String(intStrChar,0,9);
      String char10to20 = new String(intStrChar,10,19);
      

      【讨论】:

        【解决方案7】:

        Apache Commons CSV 项目可以处理固定文件。

        看起来固定宽度功能无法在沙盒中进行推广。

        【讨论】:

        • 这似乎是“在沙箱中”。我不熟悉公地,但我觉得这意味着它还没有“完成”?
        • 表示没有正式发布。这与“不起作用”有很大不同。根据它在沙盒中的使用时间,似乎没有人将其推向发布,但它最终仍然被广泛使用。
        • 你能详细说明一下吗?我只是看了一下 API,找不到任何提示/证明它实际上支持固定宽度的列而不是分隔符。顺便说一句,当前 URL 是 commons.apache.org/proper/commons-csv
        • 你可以为这样的功能投票issues.apache.org/jira/browse/CSV-272
        【解决方案8】:

        这是读取固定宽度文件的纯 java 代码:

        import java.io.File;
        import java.io.FileNotFoundException;
        import java.io.BufferedReader;
        import java.io.FileReader;
        import java.io.IOException;
        import java.util.Arrays;
        import java.util.List;
        
        public class FixedWidth {
        
            public static void main(String[] args) throws FileNotFoundException, IOException {
                // String S1="NHJAMES TURNER M123-45-67890004224345";
                String FixedLengths = "2,15,15,1,11,10";
        
                List<String> items = Arrays.asList(FixedLengths.split("\\s*,\\s*"));
                File file = new File("src/sample.txt");
        
                try (BufferedReader br = new BufferedReader(new FileReader(file))) {
                    String line1;
                    while ((line1 = br.readLine()) != null) {
                        // process the line.
        
                        int n = 0;
                        String line = "";
                        for (String i : items) {
                            // System.out.println("Before"+n);
                            if (i == items.get(items.size() - 1)) {
                                line = line + line1.substring(n, n + Integer.parseInt(i)).trim();
                            } else {
                                line = line + line1.substring(n, n + Integer.parseInt(i)).trim() + ",";
                            }
                            // System.out.println(
                            // S1.substring(n,n+Integer.parseInt(i)));
                            n = n + Integer.parseInt(i);
                            // System.out.println("After"+n);
                        }
                        System.out.println(line);
                    }
                }
        
            }
        
        }
        

        【讨论】:

          【解决方案9】:
          /*The method takes three parameters, fixed length record , length of record which will come from schema , say 10 columns and third parameter is delimiter*/
          public class Testing {
          
              public static void main(String as[]) throws InterruptedException {
          
                  fixedLengthRecordProcessor("1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10", 10, ",");
          
              }
          
              public static void fixedLengthRecordProcessor(String input, int reclength, String dilimiter) {
                  String[] values = input.split(dilimiter);
                  String record = "";
                  int recCounter = 0;
                  for (Object O : values) {
          
                      if (recCounter == reclength) {
                          System.out.println(record.substring(0, record.length() - 1));// process
                                                                                          // your
                                                                                          // record
                          record = "";
                          record = record + O.toString() + ",";
                          recCounter = 1;
                      } else {
          
                          record = record + O.toString() + ",";
          
                          recCounter++;
          
                      }
          
                  }
                  System.out.println(record.substring(0, record.length() - 1)); // process
                                                                                  // your
                                                                                  // record
              }
          
          }
          

          【讨论】:

            【解决方案10】:

            另一个可用于解析固定宽度文本源的库: https://github.com/org-tigris-jsapar/jsapar

            允许您在 xml 或代码中定义模式并将固定宽度的文本解析为 Java bean 或从内部格式获取值。

            披露:我是 jsapar 库的作者。如果它不能满足您的需求,请在 this page 上找到其他解析库的完整列表。它们中的大多数仅用于分隔文件,但有些也可以解析固定宽度。

            【讨论】:

            • 如果您要链接到您编写的库,如项目的 contributor's page 所示,您必须直接在 你的答案。链接到附属内容且未透露该附属关系的帖子将被标记为垃圾邮件并被删除。请阅读this guide 了解如何格式化您的帖子。
            猜你喜欢
            • 2011-06-22
            • 2011-01-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多