改行を含まないデータCSVファイルの読込み - Oboe吹きプログラマの黙示録
を書いたのなら、
TSV の場合も同様に書ける。
まず、Csvtolist はカンマ区切りなので、これをタブ区切りで処理する Tsvtolist を用意する。
Csvtolist から変更箇所は1箇所だけである
Tsvtolist
import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Function; public interface Tsvtolist extends Serializable{ public static Function<String, List<String>> of(){ return Tsvtolist::create; } public static List<String> create(String str){ char DELIMITER = '\t'; char CARRIAGE_RETURN = '\r'; char NEWLINE = '\n'; char DOUBLE_QUOTE = '"'; if (str==null || str.length()==0){ return Collections.emptyList(); } List<String> tokens = new ArrayList<String>(); StringBuilder tokenBuf = new StringBuilder(); boolean insideDoubleQuote = false; boolean isDoubleQuoteEscapeActive = false; StringBuilder wspBuf = new StringBuilder(); for(int ii=0; ii < str.length(); ii++){ final char ch = str.charAt(ii); if (ch==CARRIAGE_RETURN || ch==NEWLINE){ if (insideDoubleQuote){ tokenBuf.append(ch); }else{ throw new RuntimeException("unquoted " + (ch=='\n' ? "newline" : "carriage return") + " found at position #" + (ii+1)); } }else if(ch==DOUBLE_QUOTE){ if (insideDoubleQuote){ if (isDoubleQuoteEscapeActive){ tokenBuf.append(ch); isDoubleQuoteEscapeActive = false; }else if(((ii+1) < str.length()) && str.charAt(ii+1)==DOUBLE_QUOTE){ isDoubleQuoteEscapeActive = true; }else{ insideDoubleQuote = false; } }else{ insideDoubleQuote = true; if (wspBuf.length() != 0){ if (tokenBuf.length() != 0){ tokenBuf.append(wspBuf); } wspBuf.delete(0, wspBuf.length()); } } }else{ if (insideDoubleQuote){ tokenBuf.append(ch); }else{ if (ch==DELIMITER){ tokens.add(tokenBuf.toString()); tokenBuf.delete(0, tokenBuf.length()); wspBuf.delete(0, wspBuf.length()); }else if(Character.isWhitespace(ch)){ wspBuf.append(ch); }else{ if (wspBuf.length() != 0){ if (tokenBuf.length() != 0){ tokenBuf.append(wspBuf); } wspBuf.delete(0, wspBuf.length()); } tokenBuf.append(ch); } } } } if (insideDoubleQuote){ throw new RuntimeException("terminating double quote not found"); } tokens.add(tokenBuf.toString()); return tokens; } }
データに改行を含まない前提なので、ファイル読込みの処理を組み込んだ interface class は、
TsvReadProcess
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 TsvReadProcess{ public void inspectLine(int lineno, List<String> list) throws FingersplitException; public Charset getCharset(); public static TsvReadProcess of(Charset charset, BiFunction<Integer, List<String>, String> inspector) { return new TsvReadProcess() { @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 = Tsvtolist.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 = Tsvtolist.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; } } }
使用している BOMfunction や、FingersplitException は、
改行を含まないデータCSVファイルの読込み - Oboe吹きプログラマの黙示録
を参照
使用サンプル
try{ TsvReadProcess process = TsvReadProcess.of(Charset.forName("MS932"), (i,list)->{ // TODO 異常があれば、エラーメッセージを返す、正常なら、null またはブランク、空白を返す。 return null; }); process.execute(()->getMyInputStream(), (i, map)->{ System.out.println("i="+i+" "+ map); }); }catch(FingersplitException e){ System.err.println("catch! FingersplitException"); System.err.println("catch! "+e.getMessage()); e.printStackTrace(); }catch(Exception e){ e.printStackTrace(); }
UTF-8 の TSV ファイルだったら、
Charset.forName("MS932") を指定しないで、
StandardCharsets.UTF_8 を与える