Edited at

Javaでバイナリファイルを読む1

More than 1 year has passed since last update.


はじめに

少し前に思いついた方法です。ベストプラクティスではないです。オブジェクト指向が好きなんですのん( ˘ω˘)


基本


ファイルをByteBufferにする

まずInputStreamからbyte[]に取り出します。取り出し方はこちらが参考になります。

それをwrapしてByteBuffer(以下buf)を作ります。

byte[] bin = BinUtil.readAll(stream);

ByteBuffer buf = ByteBuffer.wrap(bin);


bufから4byteを符号ありとして読むには

(long)buf.getInt()


bufから4byteを符号なしとして読むには

Javaには符号の概念はないのです( ˘ω˘)。

(long)buf.getInt () & 0xffffffffL


定義する


  • データ型のinterfaceを定義します。


データ型インターフェース

bufからデータを取ったり入れたりできたりしないといけません。compileとdecompileを用意。

全体のデータサイズ長とオフセット(後述)も必要。


public interface DataTypeInterface<T> {
Integer getOffset();

void setOffset(Integer offset);

Integer getSize();

void setSize(Integer size);

void decompile(ByteBuffer buf);

void compile(ByteBuffer buf);

/**
* 子要素を取得する
*
* @return 子要素
*/

T get();

/**
* 子要素をセットする
*
* @param item 子要素
*/

void set(T item);
}


単純なデータ構造の場合

内部値がjavaのプリミティブ(StringとかLongとか)で表現できるタイプのもの。

public abstract class SingleDataType<T> implements DataTypeInterface<T>{

/**
* 内部値
*/

private T value;

/**
* バイトストリーム内のオブジェクト開始位置
*/

private Integer offset;

/**
* オブジェクトバイトサイズ
*/

private Integer size;

@Override
final public Integer getOffset(){
return this.offset;
}

@Override
final public void setOffset(Integer offset){
this.offset= offset;
}

@Override
final public void setSize(Integer size) {
this.size = size;
}

@Override
public Integer getSize() {
return this.size;
}

public T get() {
return this.value;
}

public void set(T value) {
this.value = value;
}
}


数値用の抽象クラスを作る

数値だけです。

abstract public class NumericDataType<T extends Number> extends SingleDataType<T> {

@Override
final public T get() {
return super.get();
}

@Override
public void set(T val) {
super.set(val);
}

abstract public void setNumber(Number val);

}


LONG

基本でやったlong (゚∀゚)

public class LONG extends NumericDataType<Long> {

@Override
public void decompile(ByteBuffer buf) {
this.setOffset(buf.position());
this.set((long)buf.getInt());
}

@Override
public void compile(ByteBuffer buf) {
this.setOffset(buf.position());
buf.putInt(this.get().intValue());
}

public LONG(ByteBuffer buf) {
this.decompile(buf);
this.setSize(4);
}

public LONG(Long value) {
this.set(value);
this.setSize(4);
}

/**
* 値をバリデートしてセットする
*
* @param value 値
* @throws IllegalArgumentException 値の範囲が-2147483648L未満か2147483647Lを超えていた場合
*/

@Override
public void set(Long value) throws IllegalArgumentException {
/*1*/
if ( value < -2147483648L || 2147483647L < value) {
throw new IllegalArgumentException("値の範囲が最大長、最小長を超えています");
}
/*2*/
super.set(value);
}

@Override
public void setNumber(Number val) {
this.set(val.longValue());
}
}


複雑なデータ構造の場合

Javaにはstructの概念はないのです( ;ω;)。

アノテーションとリフレクションでなんとかします

@Retention(RetentionPolicy.RUNTIME)

public @interface PROPERTY {
int order();
}

フィールドをリフレクションで列挙して上記のアノテーションがついたもののみを抽出し、order順に並び替えて、その順番でdecompileします。


public abstract class CompositeDataType implements DataTypeInterface<DataTypeInterface>{

protected Integer offset;

@Override
final public Integer getOffset(){
return this.offset;
}

@Override
final public void setOffset(Integer offset){
this.offset= offset;
}

@Override
public Integer getSize(){
return this.OrderedProperties()
.entrySet()
.stream()
.mapToInt(item -> item.getValue().getSize())
.sum();
}

@Override
@Deprecated
public void setSize(Integer size) {
throw new RuntimeException("複合オブジェクトのサイズを設定することはできません。");
}

@Override
public void decompile(ByteBuffer buf){
this.setOffset(buf.position());
for (Map.Entry<String, DataTypeInterface> item: this.OrderedProperties().entrySet()) {
try {
item.getValue().decompile(buf);
}catch (IllegalArgumentException e){
System.out.println("キー("+ item.getKey() +")の子要素はスキップされます");
e.printStackTrace();
}
}
}

@Override
public void compile(ByteBuffer buf) {
this.setOffset(buf.position());
for (Map.Entry<String, DataTypeInterface> item: this.OrderedProperties().entrySet()) {
try {
item.getValue().compile(buf);
}catch (IllegalArgumentException e){
System.out.println("キー("+ item.getKey() +")の子要素はスキップされます");
e.printStackTrace();
}
}
}

protected Map<String,DataTypeInterface> OrderedProperties() {

List<Field> fields=new ArrayList<>();
Class c= this.getClass() ;
while(c != null){
fields.addAll(java.util.Arrays.asList(c.getDeclaredFields()));
c = c.getSuperclass();
}

List<Field> paramfields = fields
.stream()
.filter(f -> f.getAnnotation(PROPERTY.class) != null)
.collect(Collectors.toList());

Collections.sort(paramfields, (Field o1, Field o2) -> {
PROPERTY or1 = o1.getAnnotation(PROPERTY.class);
PROPERTY or2 = o2.getAnnotation(PROPERTY.class);

if (or1 != null && or2 != null) {
return or1.order() - or2.order();
}
return o1.getName().compareTo(o2.getName());
}
);

Map<String, DataTypeInterface> m = new LinkedHashMap<>();

try {
for (Field f : paramfields) {
f.setAccessible(true);
m.put(f.getName(), (DataTypeInterface) f.get(this));
}
} catch (IllegalAccessException e) {
throw new RuntimeException("フィールド("+ fields.toString() +"にアクセスできません");
}

return m;

}

@Override
public DataTypeInterface get() {
return null;
}

@Override
public void set(DataTypeInterface item) {
}
}

なにかのデータ構造があった場合、下記のように使います。


public class Version extends CompositeDataType {

@PROPERTY(order = 1)
public LONG version = new LONG();

@PROPERTY(order = 2)
public LONG subVersion = new LONG();
}

作ったものは再利用できます。

public class Data1 extends CompositeDataType {

@PROPERTY(order = 1)
public Version version = new Version();

@PROPERTY(order = 2)
public LONG data1 = new LONG();
}

ここまでが基礎的なもので、2は配列データ、オフセットデータをやります。