【问题标题】:Camel Split InputStream by length not by token骆驼按长度而不是令牌拆分 InputStream
【发布时间】:2016-08-06 07:29:28
【问题描述】:

我有一个这样的输入文件

1234AA11BB4321BS33XY...

我想把它拆分成这样的单个消息

Message 1: 1234AA11BB
Message 2: 4321BS33XY

将记录转换为 Java 对象,使用 jaxb 将它们编组为 xml,并在传出消息中聚合大约 1000 条记录。

转换和编组没问题,但我不能拆分上面的字符串。 没有分隔符,只有长度。每条记录的长度正好是 10 个字符。 我想知道是否有像

这样的开箱即用解决方案
split(body().tokenizeBySize(10)).streaming()

由于实际上每条记录由 300 个字符组成,并且文件中可能有 500.000 条记录,因此我想拆分一个 InputStream。

在其他示例中,我看到了用于拆分的自定义迭代器,但它们都基于令牌或 xml。

有什么想法吗?

顺便说一句,我们绑定到 Java 6 和 camel 2.13.4

谢谢 尼克

【问题讨论】:

    标签: java apache-camel


    【解决方案1】:

    最简单的方法是用空字符串分割 - .split().tokenize("", 10).streaming() - 这意味着标记器将获取每个字符 - 并将 10 个标记(字符)组合在一起,然后将它们聚合到一个组中,例如

    @Override
    public void configure() throws Exception {
      from("file:src/data?delay=3000&noop=true")
          .split().tokenize("", 10).streaming()
          .aggregate().constant(true) // all messages have the same correlator
            .aggregationStrategy(new GroupedMessageAggregationStrategy())
            .completionSize(1000)
            .completionTimeout(5000) // use a timeout or a predicate 
                                     // to know when to stop
          .process(new Processor() { // process the aggregate
            @Override
            public void process(final Exchange e) throws Exception {
              final List<Message> aggregatedMessages = 
                (List<Message>) e.getIn().getBody();
              StringBuilder builder = new StringBuilder();
              for (Message message : aggregatedMessages) {
                builder.append(message.getBody()).append("-");
              }
              e.getIn().setBody(builder.toString());
            }
          })
          .log("Got ${body}")
          .delay(2000);
    }
    

    编辑

    这是我在流模式下的内存消耗,100MB 文件延迟 2 秒:

    【讨论】:

    • 嘿,谢谢,这听起来很有希望。我会试一试。 split().tokenize("", 10) 似乎正是我想要的。看起来很简单,为什么我不自己想出这个?
    • 试过了。现在,如果我读取一个大文件,我会在内存中获得数百兆字节的 char[] 对象。我想如果我设置例如split().tokenize("", 10) 和 completionSize(10) 将从我的文件中读取 10 个长度为 10 的字符串,并通过我的所有路由进行路由。接下来的 10 个字符串将从文件中读取,直到前一个已发送到目标系统(好吧,离开最后一条路线)。关于内存,如果我将整个文件内容放入字符串或使用上面的路线,这没有什么区别。似乎 tokenize 将整个文件内容放入 char[] 并且没有流式传输。
    • 嗯,你确定你在拆分之前没有做任何额外的处理吗?因为对我来说,这段代码确实可以正确流式传输 - 它基于 java.util.Scanner 创建了一个 GroupTokenIterator,它一次被缓冲并流式传输 8KB。但是,您将(并且应该)在内存中看到很多 char[]s,因为 Camel 将为每个组实例化一个新数组并等到收集到 1000 个组,并且在内存已满之前 GC 不会触发。跨度>
    • 要查看它是否正确流式传输,您可以在 .log() 步骤之后添加一个 .delay(5000) 步骤并打开 jconsole 并跟踪内存消耗 - 您会看到内存正在缓慢填充而不是立即。
    • 谢谢你,我在 jconsole 中得到了一步高的窥视。我想它一定是别的东西。我会分析这个...
    【解决方案2】:

    为什么不让一个普通的java类做拆分和引用呢?看这里: http://camel.apache.org/splitter.html

    取自文档的代码示例。

    下面的java dsl使用“方法”来调用单独类中定义的split方法。

    from("direct:body")
            // here we use a POJO bean mySplitterBean to do the split of the payload
            .split().method("mySplitterBean", "splitBody")
    

    在下面定义拆分器并返回每个拆分消息。

    public class MySplitterBean {
    
        /**
         * The split body method returns something that is iteratable such as a java.util.List.
         *
         * @param body the payload of the incoming message
         * @return a list containing each part splitted
         */
        public List<String> splitBody(String body) {
            // since this is based on an unit test you can of cause
            // use different logic for splitting as Camel have out
            // of the box support for splitting a String based on comma
            // but this is for show and tell, since this is java code
            // you have the full power how you like to split your messages
            List<String> answer = new ArrayList<String>();
            String[] parts = body.split(",");
            for (String part : parts) {
                answer.add(part);
            }
            return answer;
        }
    

    【讨论】:

    • 嗨,谢谢我知道这个例子,但在 splitBody(String body) 中,参数正文将包含整个文件内容。如果我有一个包含 500.000 条记录的文件,那么这些记录将在 body 参数中,并且数组和列表将分别包含 500.000 个项目。而且我不能用“,”标记分割,因为 10 个字符的记录不是用逗号分隔的。我需要的是这样的:读取一个 InputStream 的 10 个字节,将它们放在一个列表中。这样做直到列表中有 1000 个项目。然后将 List 放到路由中的下一个端点,并开始用 1000 个项目填充下一个列表,依此类推。
    • 所以我从来没有将整个文件放在内存中,只有一小部分。
    • convertBodyTo(String.class) 或您想要的任何格式,即 InputStream
    • 一旦你把它作为流,然后用你喜欢的任何方式,按长度或某个字符来分割它。
    • 嗯,这样的话,splitter Method只会被调用一次。所以我必须在这个方法中读取整个文件并将拆分的字符串存储到消息列表中。因此要记忆。我想要做的是在遍历 InputStream 时调用 split 方法。所以我需要一个像这里camel.465427.n5.nabble.com/… 这样的自定义迭代器。也许我应该使用 2 个分离器。一个用于拆分大文件,一个用于剪切 10 个字符的字符串。后者可以像您在第一个答案中写的那样工作。
    猜你喜欢
    • 2020-05-16
    • 2016-09-08
    • 2015-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多