在Java中,如何在不强制使用File作为媒介的情况下创建Apache Avro容器文件的等效文件?

omn*_*ist 18 java serialization avro

如果有人熟悉Apache Avro的Java实现,那么这在黑暗中就是一个镜头.

我的高级目标是通过网络传输一些avro数据系列(例如,让我们说HTTP,但是特定协议对于此目的并不重要).在我的上下文中我有一个HttpServletResponse我需要以某种方式编写这些数据.

我最初尝试将数据写为avro容器文件的虚拟版本(假设"response"的类型为HttpServletResponse):

response.setContentType("application/octet-stream");
response.setHeader("Content-transfer-encoding", "binary");
ServletOutputStream outStream = response.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(outStream);

Schema someSchema = Schema.parse(".....some valid avro schema....");
GenericRecord someRecord = new GenericData.Record(someSchema);
someRecord.put("somefield", someData);
...

GenericDatumWriter<GenericRecord> datumWriter = new GenericDatumWriter<GenericRecord>(someSchema);
DataFileWriter<GenericRecord> fileWriter = new DataFileWriter<GenericRecord>(datumWriter);
fileWriter.create(someSchema, bos);
fileWriter.append(someRecord);
fileWriter.close();
bos.flush();
Run Code Online (Sandbox Code Playgroud)

这一切都很好,但是事实证明Avro并没有提供一种方法来读取除实际文件之外的容器文件:DataFileReader只有两个构造函数:

public DataFileReader(File file, DatumReader<D> reader);
Run Code Online (Sandbox Code Playgroud)

public DataFileReader(SeekableInput sin, DatumReader<D> reader);
Run Code Online (Sandbox Code Playgroud)

其中SeekableInput是一些特定于avro的自定义表单,其创建也最终从文件中读取.现在给出,除非有某种方法以某种方式将InputStream强制转换为文件(http://stackoverflow.com/questions/578305/create-a-java-file-object-or-equivalent-using-a-byte- array-in-memory-without-a表明没有,我也试过查看Java文档),如果OutputStream另一端的阅读器收到该avro容器文件,这种方法将无效(我不确定为什么他们允许一个人将avro二进制容器文件输出到任意OutputStream,而没有提供从另一端的相应InputStream读取它们的方法,但这不是重点.似乎容器文件阅读器的实现需要具体文件提供的"可搜索"功能.

好的,所以看起来这种方法看起来不像我想做的那样.如何创建模仿avro容器文件的JSON响应?

public static Schema WRAPPER_SCHEMA = Schema.parse(
  "{\"type\": \"record\", " +
   "\"name\": \"AvroContainer\", " +
   "\"doc\": \"a JSON avro container file\", " +
   "\"namespace\": \"org.bar.foo\", " +
   "\"fields\": [" +
     "{\"name\": \"schema\", \"type\": \"string\", \"doc\": \"schema representing the included data\"}, " +
     "{\"name\": \"data\", \"type\": \"bytes\", \"doc\": \"packet of data represented by the schema\"}]}"
  );
Run Code Online (Sandbox Code Playgroud)

鉴于上述限制,我不确定这是否是解决此问题的最佳方法,但看起来这可能会成功.我将把模式(例如,来自上面的"Schema someSchema")作为一个字符串放在"schema"字段中,然后放入avro-binary-serialized形式的记录拟合该模式(即"GenericRecord" someRecord")在"data"字段中.

我实际上想知道下面描述的具体细节,但我认为给出一个更大的背景是值得的,所以如果有一个更好的高级方法我可以采取(这种方法有效)但是感觉不是最佳)请告诉我.

我的问题是,假设我采用这种基于JSON的方法,如何将我的Record的avro二进制表示写入AvroContainer模式的"数据"字段?例如,我到了这里:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
GenericDatumWriter<GenericRecord> datumWriter = new GenericDatumWriter<GenericRecord>(someSchema);
Encoder e = new BinaryEncoder(baos);
datumWriter.write(resultsRecord, e);
e.flush();

GenericRecord someRecord = new GenericData.Record(someSchema);
someRecord.put("schema", someSchema.toString());
someRecord.put("data", ByteBuffer.wrap(baos.toByteArray()));
datumWriter = new GenericDatumWriter<GenericRecord>(WRAPPER_SCHEMA);
JsonGenerator jsonGenerator = new JsonFactory().createJsonGenerator(baos, JsonEncoding.UTF8);
e = new JsonEncoder(WRAPPER_SCHEMA, jsonGenerator);
datumWriter.write(someRecord, e);
e.flush();

PrintWriter printWriter = response.getWriter(); // recall that response is the HttpServletResponse
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
printWriter.print(baos.toString("UTF-8"));
Run Code Online (Sandbox Code Playgroud)

我最初尝试省略ByteBuffer.wrap子句,但接着是行

datumWriter.write(someRecord, e);
Run Code Online (Sandbox Code Playgroud)

抛出一个异常,我无法将字节数组转换为ByteBuffer.很公平,看起来当调用Encoder类(其中JsonEncoder是子类)来编写avro Bytes对象时,它需要将ByteBuffer作为参数给出.因此,我尝试使用java.nio.ByteBuffer.wrap封装byte [],但是当打印出数据时,它被打印为一系列直接的字节,而不通过avro十六进制表示:

"data": {"bytes": ".....some gibberish other than the expected format...}
Run Code Online (Sandbox Code Playgroud)

这似乎不对.根据avro文档,他们给出的示例字节对象说我需要放入一个json对象,其中一个例子看起来像"\ u00FF",而我放在那里的内容显然不是那种格式.我现在想知道的是以下内容:

  • 什么是avro字节格式的示例?它看起来像"\ uDEADBEEFDEADBEEF ......"吗?
  • 如何将我的二进制avro数据(由BinaryEncoder输出到byte []数组中)强制转换为可以插入GenericRecord对象并在JSON中正确打印的格式?例如,我想要一个对象数据,我可以调用一些GenericRecord"someRecord.put("data",DATA);" 我的avro序列化数据在里面?
  • 当给出文本JSON表示并希望重新创建由AvroContainer格式JSON表示的GenericRecord时,我如何将该数据读回另一个(消费者)端的字节数组?
  • (重申之前的问题)有没有更好的方法可以做到这一切?

Ben*_*Ben 2

正如 Knut 所说,如果您想使用文件以外的其他内容,您可以:

  • 正如 Knut 所说,使用 SeekableByteArrayInput 来处理任何可以插入字节数组的内容
  • 以您自己的方式实现 SeekablInput - 例如,如果您从某些奇怪的数据库结构中获取它。
  • 或者只使用一个文件。为什么不?

这些就是你的答案。

  • 另外,使用文件会增加磁盘 I/O 的开销,因此如果您通过网络接收字节数组,您不希望先将其放入文件中,然后再读取它(磁盘 I/O 往返!! !)。 (4认同)