在Java中解析固定宽度格式的文件

20

我从供应商那里得到了一个每行有115个定宽字段的文件。如何将该文件解析成115个字段,以便在我的代码中使用?

我的第一个想法是为每个字段创建常量,例如NAME_START_POSITIONNAME_LENGTH,再使用substring方法。但这看起来很丑陋,因此我想知道是否有更好的方法。我在谷歌搜索中找到的几个库也没有更好的方法。


你可能想查看相关问题https://dev59.com/hm025IYBdhLWcg3wpXod - mthomas
查看com.ancientprogramming.fixedformat4j库。 - firstpostcommenter
10个回答

22

我会使用类似于flatworm这样的平面文件解析器,而不是重复造轮子:它具有简洁的API,易于使用,具有良好的错误处理和简单的文件格式描述符。另一个选择是jFFP,但我更喜欢第一个。


1
我只是想跟进一下,感谢你指向Flatworm。它非常好用,我们工作团队现在都在使用它。 - MattGrommes
1
@MattGrommes 很高兴知道你喜欢它。非常感谢你的跟进,我们非常感激! - Pascal Thivent
我几天前尝试了这个库,但它已经无法修复了。我想尝试之前的版本,但是我没有看到任何文档。 - Monachus
这是一个很棒的工具!有没有办法将它集成到某种编辑器中,比如Eclipse? - Rekin
你们还在使用这个 flatworm 工具吗?文件格式的 XML 定义中 DTD 引用已经损坏了。我该如何解决这个问题? - Iofacture
1
虽然有些晚了,但是https://github.com/ffpojo/ffpojo看起来很不错,因为它可以将POJO映射到另一个POJO。 - Usman Ismail

8
我玩过 fixedformat4j ,感觉很不错。可以很容易地配置转换器等。

1
请注意,ff4j使用运行时注释,这使得大规模解析变得非常缓慢。 - ron

7

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")));

这里有一些解析各种固定宽度输入的示例

这里还有一些其他通用编写示例和其他特定于固定宽度格式的示例

声明:本库的作者,它是开源免费的(Apache 2.0许可证)。


1
这里是我使用的基本实现:

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);
      }
    }
  }
}

两年后,但我希望你看到这个。如果只有可能返回输入流中的字符或-1表示文件结束,为什么需要检查读入的字符data是否等于13或10呢? - Efie
你是正确的... 这个实现用于以换行符结尾的固定宽度记录。 - Constantin

1

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

我对于没有适当的固定长度格式库感到非常厌烦,所以我创建了自己的库。您可以在此处查看:https://github.com/atais/Fixed-Length

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

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)

1
如果你的字符串被称为inStr,请将其转换为字符数组并使用String(char[], start, length)构造函数。
char[] intStrChar = inStr.toCharArray();
String charfirst10 = new String(intStrChar,0,9);
String char10to20 = new String(intStrChar,10,19);

0
/*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
    }

}

0

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

看起来固定宽度功能在从沙盒中推广过程中没有得到保留。


那似乎是“在沙盒里”。我不熟悉commons,但我的印象是它意味着它还没有完成? - Ape-inago
这意味着没有官方发布。这与“不起作用”显著不同。基于它在沙盒中的时间,似乎没有人在推动它发布,但它仍然被广泛使用。 - Jherico
你能详细说明一下吗?我刚刚查看了API,没有找到任何迹象/证据表明它实际上支持固定宽度列而不是分隔符。顺便说一句,当前的URL是http://commons.apache.org/proper/commons-csv/。 - Gandalf
您可以为这样的功能投票 https://issues.apache.org/jira/browse/CSV-272 - Holger Brandl

0

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

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

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


1
如果您要链接到您编写的库,如项目的贡献者页面所示,您必须在回答中直接披露它是您自己的。链接到关联内容但未披露其关联性的帖子将被标记为垃圾邮件并删除。请阅读此指南了解如何格式化您的帖子。 - Das_Geek

0

这里是读取固定宽度文件的纯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);
            }
        }

    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接