【问题标题】:Is there an smart way to write a fixed length flat file?有没有一种聪明的方法来编写一个固定长度的平面文件?
【发布时间】:2011-08-18 14:37:09
【问题描述】:

是否有任何框架/库可以帮助在 java 中编写固定长度的平面文件?

我想将 bean/实体的集合写入一个平面文件,而不用担心转换、填充、对齐、填充等

例如,我想解析一个像这样的bean:

public class Entity{
    String name = "name"; // length = 10; align left; fill with spaces
    Integer id = 123; // length = 5; align left; fill with spaces
    Integer serial = 321 // length = 5; align to right; fill with '0'
    Date register = new Date();// length = 8; convert to yyyyMMdd
}

...进入...

name      123  0032120110505
mikhas    5000 0122120110504
superuser 1    0000120101231

...

【问题讨论】:

  • 你到底想做什么?现在听起来 Java 的内置函数应该足以满足您的需求。
  • 我想将 bean/实体的集合写入一个平面文件,而不用担心转换、填充、对齐、填充等
  • 您要查找的不是用于编写平面文件的框架/库。您正在寻找一个框架/库来将 bean/实体转换为可以写入文件的平面格式。
  • 对了,为什么它必须是平面文件,而不是 XML、JSON、YAML、Java-Serialization 等?
  • 必须是文本文件。它是与旧系统的接口。

标签: java flat-file fixed-length-record fixed-length-file


【解决方案1】:

Spring Batch 有一个 FlatFileItemWriter,但除非您使用整个 Spring Batch API,否则这对您没有帮助。


但除此之外,我想说您只需要一个可以轻松写入文件的库(除非您想自己编写整个 IO 代码)。

想到的两个是:

Guava

Files.write(stringData, file, Charsets.UTF_8);

Commons / IO

FileUtils.writeStringToFile(file, stringData, "UTF-8");

【讨论】:

  • 问题不是写文件,而是把每个字段格式化成一个固定长度的文件。
【解决方案2】:

不知道任何框架,但您可以使用 RandomAccessFile。您可以将文件指针定位到文件中的任何位置以进行读写操作。

【讨论】:

    【解决方案3】:

    将 bean/实体写入平面文件的一种简单方法是使用 ObjectOutputStream。

    public static void writeToFile(File file, Serializable object) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        oos.writeObject(object);
        oos.close();
    }
    

    您可以使用

    写入固定长度的平面文件
    FileUtils.writeByteArrayToFile(new File(filename), new byte[length]);
    

    您需要更具体地说明要对文件执行的操作。 ;)

    【讨论】:

    • 问题不是写文件,而是把每个字段格式化成一个固定长度的文件。
    • 上述解决方案解决了它可以将任何对象以二进制格式写入文件的问题。如果您想要文本格式,只需进行 base64 转换或类似的操作。对于固定大小,只需提供超出需要的部分即可。
    • @mikhas,为什么每个字段都有一个固定长度的文件。你能举个例子说明你在说什么吗?一个你想写的对象和你期望发生的事情。
    【解决方案4】:

    您不太可能遇到可以处理“传统”系统格式的框架。在大多数情况下,旧系统不使用标准格式,但框架需要它们。作为遗留 COBOL 系统和 Java/Groovy 转换的维护者,我经常遇到这种不匹配。 “担心转换、填充、对齐、填充等”主要是您在处理遗留系统时所做的事情。当然,您可以将其中的一些封装到方便的助手中。但最有可能的是,您需要真正熟悉 java.util.Formatter。

    例如,您可以使用装饰器模式创建装饰器来进行转换。下面是一些 groovy(很容易转换成 Java):

    class Entity{
        String name = "name"; // length = 10; align left; fill with spaces
        Integer id = 123; // length = 5; align left; fill with spaces
        Integer serial = 321 // length = 5; align to right; fill with '0'
        Date register = new Date();// length = 8; convert to yyyyMMdd
    }
    
    class EntityLegacyDecorator {
         Entity d
         EntityLegacyDecorator(Entity d) { this.d = d }
    
         String asRecord() {
             return String.format('%-10s%-5d%05d%tY%<tm%<td',
                                   d.name,d.id,d.serial,d.register)
       }
     }
    
    def e = new Entity(name: 'name', id: 123, serial: 321, register: new Date('2011/05/06'))
    
    assert new EntityLegacyDecorator(e).asRecord() == 'name      123  0032120110506'
    

    如果您没有太多这些并且对象不太复杂,这是可行的。但是很快格式字符串就变得无法忍受了。然后你可能想要 Date 的装饰器,比如:

    class DateYMD {
         Date d
         DateYMD(d) { this.d = d }
         String toString() { return d.format('yyyyMMdd') }
     }
    

    所以你可以用 %s 格式化:

        String asRecord() {
             return String.format('%-10s%-5d%05d%s',
                                   d.name,d.id,d.serial,new DateYMD(d.register))
       }
    

    但是对于大量的 bean 属性,字符串仍然太粗略,所以你需要一些能够理解列和长度的东西,看起来像你收到的 COBOL 规范,所以你会写这样的东西:

     class RecordBuilder {
    
        final StringBuilder record 
    
        RecordBuilder(recordSize) {
            record = new StringBuilder(recordSize)
            record.setLength(recordSize)
         }
    
        def setField(pos,length,String s) { 
           record.replace(pos - 1, pos + length, s.padRight(length))
        }
    
        def setField(pos,length,Date d) { 
          setField(pos,length, new DateYMD(d).toString())
        }
    
       def setField(pos,length, Integer i, boolean padded) { 
           if (padded) 
              setField(pos,length, String.format("%0" + length + "d",i))
           else 
              setField(pos,length, String.format("%-" + length + "d",i))
        }
    
        String toString() { record.toString() }
    }
    
    class EntityLegacyDecorator {
    
         Entity d
    
         EntityLegacyDecorator(Entity d) { this.d = d }
    
         String asRecord() {
             RecordBuilder record = new RecordBuilder(28)
             record.setField(1,10,d.name)
             record.setField(11,5,d.id,false)
             record.setField(16,5,d.serial,true)
             record.setField(21,8,d.register)
             return record.toString()
         }
    
    }
    

    在您编写了足够多的 setField() 方法来处理遗留系统之后,您将简要考虑将其作为“框架”发布在 GitHub 上,这样下一个可怜的 sap 就不必再次使用它了。但随后您将考虑您所见过的所有荒谬方式 COBOL 存储“日期”(MMDDYY、YYMMDD、YYDDD、YYYYDDD)和数字(假定为十进制、显式十进制、符号作为尾随分隔符或符号作为前导浮动字符)。然后你就会明白为什么没有人为此制作一个好的框架,并偶尔将你的生产代码的一部分发布到 SO 中作为示例...... ;)

    【讨论】:

    • 我很想在 GitHub 中找到这样一个“框架”,因为我是那些做同样事情的可怜虫之一。在我的自定义解决方案中,我使用映射类 + dyna beans。
    【解决方案5】:

    我刚刚找到了一个我正在使用的不错的库:
    http://sourceforge.net/apps/trac/ffpojo/wiki

    使用 XML 或注释配置非常简单!

    【讨论】:

      【解决方案6】:

      如果您仍在寻找框架,请查看 http://www.beanio.org 的 BeanIO

      【讨论】:

      • 仅供参考,在您提出建议后,我一直在各种项目中使用 BeanIO。谢谢
      • 2013年最后一次发布BeanIO,有人维护吗?
      【解决方案7】:

      Fixedformat4j 库是一个非常简洁的工具,可以做到这一点:http://fixedformat4j.ancientprogramming.com/

      【讨论】:

        【解决方案8】:

        uniVocity-parsers 在支持复杂的固定宽度格式方面走了很长一段路,包括具有不同字段、填充等的行。

        查看此示例以编写虚构的客户和帐户详细信息。这使用前瞻值来确定写入行时要使用的格式:

            FixedWidthFields accountFields = new FixedWidthFields();
            accountFields.addField("ID", 10); //account ID has length of 10
            accountFields.addField("Bank", 8); //bank name has length of 8
            accountFields.addField("AccountNumber", 15); //etc
            accountFields.addField("Swift", 12);
        
            //Format for clients' records
            FixedWidthFields clientFields = new FixedWidthFields();
            clientFields.addField("Lookahead", 5); //clients have their lookahead in a separate column
            clientFields.addField("ClientID", 15, FieldAlignment.RIGHT, '0'); //let's pad client ID's with leading zeroes.
            clientFields.addField("Name", 20);
        
            FixedWidthWriterSettings settings = new FixedWidthWriterSettings();
            settings.getFormat().setLineSeparator("\n");
            settings.getFormat().setPadding('_');
        
            //If a record starts with C#, it's a client record, so we associate "C#" with the client format.
            settings.addFormatForLookahead("C#", clientFields);
        
            //Rows starting with #A should be written using the account format
            settings.addFormatForLookahead("A#", accountFields);
        
            StringWriter out = new StringWriter();
        
            //Let's write
            FixedWidthWriter writer = new FixedWidthWriter(out, settings);
        
            writer.writeRow(new Object[]{"C#",23234, "Miss Foo"});
            writer.writeRow(new Object[]{"A#23234", "HSBC", "123433-000", "HSBCAUS"});
            writer.writeRow(new Object[]{"A#234", "HSBC", "222343-130", "HSBCCAD"});
            writer.writeRow(new Object[]{"C#",322, "Mr Bar"});
            writer.writeRow(new Object[]{"A#1234", "CITI", "213343-130", "CITICAD"});
        
            writer.close();
        
            System.out.println(out.toString());
        

        输出将是:

        C#___000000000023234Miss Foo____________
        A#23234___HSBC____123433-000_____HSBCAUS_____
        A#234_____HSBC____222343-130_____HSBCCAD_____
        C#___000000000000322Mr Bar______________
        A#1234____CITI____213343-130_____CITICAD_____
        

        这只是一个粗略的例子。还有很多其他的options available,包括对带注释的java bean的支持,你可以找到here

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

        【讨论】:

          【解决方案9】:

          试试FFPOJO API,因为它拥有创建固定长度的平面文件所需的一切,它还能将文件转换为对象,反之亦然。

          @PositionalRecord
          public class CFTimeStamp {
          
              String timeStamp;
          
              public CFTimeStamp(String timeStamp) {
                  this.timeStamp = timeStamp;
              }
          
              @PositionalField(initialPosition = 1, finalPosition = 26, paddingAlign = PaddingAlign.RIGHT, paddingCharacter = '0')
              public String getTimeStamp() {
                  return timeStamp;
              }
          
              @Override
              public String toString() {
                  try {
                      FFPojoHelper ffPojo = FFPojoHelper.getInstance();
                      return ffPojo.parseToText(this);
                  } catch (FFPojoException ex) {
                      trsLogger.error(ex.getMessage(), ex);
                  }
                  return null;
              }
          }
          

          【讨论】:

          • 你介意发布一个你所指的例子吗?
          猜你喜欢
          • 2013-11-28
          • 1970-01-01
          • 2020-08-28
          • 2015-04-07
          • 2021-03-02
          • 1970-01-01
          • 2023-03-21
          • 1970-01-01
          • 2022-01-23
          相关资源
          最近更新 更多