実践で良く処理するCSVは、改行をデータに含まないケースがほとんどである。
改行をデータに含むケースを考慮したものとして、
GitHub - yipuran/yipuran-csv: Java CSV read and write
を作ったが、改行をデータに含まないケースはこれを使わずにもっと少ないコードで
機能を果たせる。
先日投稿した、CSV形式の1行分の文字列からList<String>への変換をinterface method にする。 - Oboe吹きプログラマの黙示録
この Csvtolist を使えば以下のように普遍的に書くこともできる。
CSVファイル読込をBiConsumer で処理する
@param bifunc<String, String>
public static void executeCsvline(BiConsumer<Integer, Map<String, String>> bifunc) throws Exception{
try(InputStream inst = getMyInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inst))){
String line = BOMfunction.chop(reader.readLine());
AtomicInteger ix = new AtomicInteger(0);
List<String> hlist = Csvtolist.create(line);
int rownum = hlist.size();
Map<Integer, String> hmap = hlist.stream()
.collect(()->new HashMap<Integer, String>(),(r, t)->{
r.put(ix.incrementAndGet(), t);
},(r, t)->{});
TODO
int lc = 1;
while((line = reader.readLine()) != null){
Map<String, String> map = new HashMap<>();
List<String> list = Csvtolist.create(line);
if (rownum != list.size()) {
throw new RuntimeException("header rows No match!");
}
hmap.entrySet().stream().forEach(e->{
map.put(e.getValue(), list.get(e.getKey()-1));
});
TODO
bifunc.accept(lc, map);
lc++;
}
}catch(IOException e){
throw e;
}
}
BOM付きなら除去する BOMfunction.chop の BOMfunction は、以下である。
(yipuran-csv の中にあるが、同じものをここに書いておく)
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
BOM操作ユーティリティクラス.
public final class BOMfunction{
private constructor.
private BOMfunction(){}
BOMを出力する.
@param out
@throws IOException
public static void push(OutputStream out) throws IOException{
out.write(new byte[]{ (byte)0xef,(byte)0xbb, (byte)0xbf });
}
BOMが先頭に付いた文字列のBOMを除去する.
@param str
@return
public static String chop(String str){
byte[] b = str.getBytes();
if (b.length < 3) return str;
if (b[0] == -17 && b[1] == -69 && b[2] == -65) {
byte[] n = new byte[b.length-3];
for(int i=0,k=3; i < n.length;i++, k++){
n[i] = b[k];
}
return new String(n);
}else if(b[0] == -2 && b[1] == -1){
byte[] n = new byte[b.length-2];
for(int i=0,k=2; i < n.length;i++, k++){
n[i] = b[k];
}
return new String(n);
}else if(b[0] == -1 && b[1] == -2){
byte[] n = new byte[b.length-2];
for(int i=0,k=2; i < n.length;i++, k++){
n[i] = b[k];
}
return new String(n);
}
return str;
}
文字列がBOM付き文字であるか返す
@param str
@return
public static boolean match(String str){
if (str==null) return false;
byte[] b = str.getBytes();
if (b.length < 3) return false;
if (b[0] == -17 && b[1] == -69 && b[2] == -65) return true;
if (b[0] == -2 && b[1] == -1) return true;
if (b[0] == -1 && b[1] == -2) return true;
return false;
}
BOM付き 状況から Charset を返す。
@param str
@return
public static Charset getCharset(String str){
if (str==null) return StandardCharsets.UTF_8;
byte[] b = str.getBytes();
if (b.length < 3) return StandardCharsets.UTF_8;
if (b[0] == -17 && b[1] == -69 && b[2] == -65) return StandardCharsets.UTF_8;
if (b[0] == -2 && b[1] == -1) return StandardCharsets.UTF_16BE;
if (b[0] == -1 && b[1] == -2) return StandardCharsets.UTF_16LE;
return StandardCharsets.UTF_8;
}
}
使用サンプルは、、
executeCsvline(n, m)->{
});
これは、ファイル入力ストリーム等、実装の状況に合わせるサンプルでしかない。
汎用的なものが欲しい。
ファイル入力を組み込んだ interface class として以下を用意する
CsvReadProcess
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
public interface CsvReadProcess{
public void inspectLine(int lineno, List<String> list) throws FingersplitException;
public Charset getCharset();
public static CsvReadProcess of(Charset charset,
BiFunction<Integer, List<String>, String> inspector) {
return new CsvReadProcess() {
@Override
public void inspectLine(int lineno, List<String> list) throws FingersplitException {
String res = inspector.apply(lineno, list);
if (res != null && !res.isBlank()){
throw new FingersplitException(lineno, res);
}
}
@Override
public Charset getCharset(){
return charset;
}
};
}
public default void execute(ThrowableSupplier<InputStream> getStreamer,
BiConsumer<Integer, Map<String, String>> bifunc) throws Exception {
try(InputStream inst = getStreamer.get();
BufferedReader reader = new BufferedReader(new InputStreamReader(inst, getCharset()))){
String line = BOMfunction.chop(reader.readLine());
AtomicInteger ix = new AtomicInteger(0);
List<String> hlist = Csvtolist.create(line);
inspectLine(1, hlist);
int rownum = hlist.size();
Map<Integer, String> hmap = hlist.stream()
.collect(()->new HashMap<Integer, String>(),(r, t)->{
r.put(ix.incrementAndGet(), t);
},(r, t)->{});
ix.set(1);
int lc = 1;
while((line = reader.readLine()) != null){
Map<String, String> map = new HashMap<>();
List<String> list = Csvtolist.create(line);
if (rownum != list.size()) {
throw new FingersplitException(lc, "header rows No match!");
}
inspectLine(ix.incrementAndGet(), list);
hmap.entrySet().stream().forEach(e->{
map.put(e.getValue(), list.get(e.getKey()-1));
});
bifunc.accept(lc, map);
lc++;
}
}catch(Exception e){
throw e;
}
}
}
この中で使用しているものは以下、
ThrowableSupplier は、入力ストリームを生成して提供することを
約束する Throwableな、Function の Supplier
(yipuran-core に、これはあるが、同じものをここに書いておく)
java.util.function.Supplier で例外捕捉をすることで呼び出しの外に例外捕捉を書けるようになる。
import java.io.Serializable;
import java.util.function.Function;
import java.util.function.Supplier;
Exception 捕捉 Supplier.
@FunctionalInterface
public interface ThrowableSupplier<R> extends Serializable{
R get() throws Exception;
ThrowableSupplier 生成.
@param supplier<>
@param onCatch
@return<>
public static <R> Supplier<R> to(ThrowableSupplier<? extends R> supplier,
Function<Exception, R> onCatch){
return ()->{
try{
return supplier.get();
}catch(Exception e){
return onCatch.apply(e);
}
};
}
ThrowableSupplier 生成(外に例外スロー).
@param supplier
@return<>
public static <R> Supplier<R> to(ThrowableSupplier<? extends R> supplier){
return ()->{
try{
return supplier.get();
}catch(Throwable ex){
throw new RuntimeException(ex.getMessage(), ex);
}
};
}
}
例外を定義:FingersplitException
public class FingersplitException extends RuntimeException{
private final int lineno;
private final String message;
public FingersplitException(int lineno, String message) {
this.lineno = lineno;
this.message = message;
}
public int getLine() {
return lineno;
}
public String getMessage() {
return message;
}
@Override
public String toString() {
return "line " + lineno + ": " + message;
}
}
BOMfunction.chop は、先に書いたとおり。
CsvReadProcess 使用例は、
以下、getCustomInputStream() というCSVファイル
の入力ストリームを取得するメソッドが存在するものとして、
try{
CsvReadProcess process = CsvReadProcess.of(Charset.forName("MS932"), (i, list)->{
TODO
return null;
});
process.execute(()->getCustomInputStream(), (i, m)->{
});
}catch(FingersplitException e){
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}
CSVファイルがUTF-8 なら、Charset.forName("MS932") の代わりに、
StandardCharsets.UTF_8 を使う。