style: 缩进

This commit is contained in:
octopus_yan 2024-12-01 22:14:32 +08:00
parent 1294fb5414
commit 223abc7ac5
9 changed files with 1375 additions and 1361 deletions

View File

@ -25,283 +25,283 @@ import java.util.Objects;
* @since 5.0.4 * @since 5.0.4
*/ */
public class CsvBaseReader implements Serializable { public class CsvBaseReader implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**
* 默认编码 * 默认编码
*/ */
protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
private final CsvReadConfig config; private final CsvReadConfig config;
//--------------------------------------------------------------------------------------------- Constructor start //--------------------------------------------------------------------------------------------- Constructor start
/** /**
* 构造使用默认配置项 * 构造使用默认配置项
*/ */
public CsvBaseReader() { public CsvBaseReader() {
this(null); this(null);
} }
/** /**
* 构造 * 构造
* *
* @param config 配置项 * @param config 配置项
*/ */
public CsvBaseReader(CsvReadConfig config) { public CsvBaseReader(CsvReadConfig config) {
this.config = ObjectUtil.defaultIfNull(config, CsvReadConfig::defaultConfig); this.config = ObjectUtil.defaultIfNull(config, CsvReadConfig::defaultConfig);
} }
//--------------------------------------------------------------------------------------------- Constructor end //--------------------------------------------------------------------------------------------- Constructor end
/** /**
* 设置字段分隔符默认逗号',' * 设置字段分隔符默认逗号','
* *
* @param fieldSeparator 字段分隔符默认逗号',' * @param fieldSeparator 字段分隔符默认逗号','
*/ */
public void setFieldSeparator(char fieldSeparator) { public void setFieldSeparator(char fieldSeparator) {
this.config.setFieldSeparator(fieldSeparator); this.config.setFieldSeparator(fieldSeparator);
} }
/** /**
* 设置 文本分隔符文本包装符默认双引号'"' * 设置 文本分隔符文本包装符默认双引号'"'
* *
* @param textDelimiter 文本分隔符文本包装符默认双引号'"' * @param textDelimiter 文本分隔符文本包装符默认双引号'"'
*/ */
public void setTextDelimiter(char textDelimiter) { public void setTextDelimiter(char textDelimiter) {
this.config.setTextDelimiter(textDelimiter); this.config.setTextDelimiter(textDelimiter);
} }
/** /**
* 设置是否首行做为标题行默认false * 设置是否首行做为标题行默认false
* *
* @param containsHeader 是否首行做为标题行默认false * @param containsHeader 是否首行做为标题行默认false
*/ */
public void setContainsHeader(boolean containsHeader) { public void setContainsHeader(boolean containsHeader) {
this.config.setContainsHeader(containsHeader); this.config.setContainsHeader(containsHeader);
} }
/** /**
* 设置是否跳过空白行默认true * 设置是否跳过空白行默认true
* *
* @param skipEmptyRows 是否跳过空白行默认true * @param skipEmptyRows 是否跳过空白行默认true
*/ */
public void setSkipEmptyRows(boolean skipEmptyRows) { public void setSkipEmptyRows(boolean skipEmptyRows) {
this.config.setSkipEmptyRows(skipEmptyRows); this.config.setSkipEmptyRows(skipEmptyRows);
} }
/** /**
* 设置每行字段个数不同时是否抛出异常默认false * 设置每行字段个数不同时是否抛出异常默认false
* *
* @param errorOnDifferentFieldCount 每行字段个数不同时是否抛出异常默认false * @param errorOnDifferentFieldCount 每行字段个数不同时是否抛出异常默认false
*/ */
public void setErrorOnDifferentFieldCount(boolean errorOnDifferentFieldCount) { public void setErrorOnDifferentFieldCount(boolean errorOnDifferentFieldCount) {
this.config.setErrorOnDifferentFieldCount(errorOnDifferentFieldCount); this.config.setErrorOnDifferentFieldCount(errorOnDifferentFieldCount);
} }
/** /**
* 读取CSV文件默认UTF-8编码 * 读取CSV文件默认UTF-8编码
* *
* @param file CSV文件 * @param file CSV文件
* @return {@link CsvData}包含数据列表和行信息 * @return {@link CsvData}包含数据列表和行信息
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
public CsvData read(File file) throws IORuntimeException { public CsvData read(File file) throws IORuntimeException {
return read(file, DEFAULT_CHARSET); return read(file, DEFAULT_CHARSET);
} }
/** /**
* 从字符串中读取CSV数据 * 从字符串中读取CSV数据
* *
* @param csvStr CSV字符串 * @param csvStr CSV字符串
* @return {@link CsvData}包含数据列表和行信息 * @return {@link CsvData}包含数据列表和行信息
*/ */
public CsvData readFromStr(String csvStr) { public CsvData readFromStr(String csvStr) {
return read(new StringReader(csvStr)); return read(new StringReader(csvStr));
} }
/** /**
* 从字符串中读取CSV数据 * 从字符串中读取CSV数据
* *
* @param csvStr CSV字符串 * @param csvStr CSV字符串
* @param rowHandler 行处理器用于一行一行的处理数据 * @param rowHandler 行处理器用于一行一行的处理数据
*/ */
public void readFromStr(String csvStr, CsvRowHandler rowHandler) { public void readFromStr(String csvStr, CsvRowHandler rowHandler) {
read(parse(new StringReader(csvStr)), true, rowHandler); read(parse(new StringReader(csvStr)), true, rowHandler);
} }
/** /**
* 读取CSV文件 * 读取CSV文件
* *
* @param file CSV文件 * @param file CSV文件
* @param charset 文件编码默认系统编码 * @param charset 文件编码默认系统编码
* @return {@link CsvData}包含数据列表和行信息 * @return {@link CsvData}包含数据列表和行信息
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
public CsvData read(File file, Charset charset) throws IORuntimeException { public CsvData read(File file, Charset charset) throws IORuntimeException {
return read(Objects.requireNonNull(file.toPath(), "file must not be null"), charset); return read(Objects.requireNonNull(file.toPath(), "file must not be null"), charset);
} }
/** /**
* 读取CSV文件默认UTF-8编码 * 读取CSV文件默认UTF-8编码
* *
* @param path CSV文件 * @param path CSV文件
* @return {@link CsvData}包含数据列表和行信息 * @return {@link CsvData}包含数据列表和行信息
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
public CsvData read(Path path) throws IORuntimeException { public CsvData read(Path path) throws IORuntimeException {
return read(path, DEFAULT_CHARSET); return read(path, DEFAULT_CHARSET);
} }
/** /**
* 读取CSV文件 * 读取CSV文件
* *
* @param path CSV文件 * @param path CSV文件
* @param charset 文件编码默认系统编码 * @param charset 文件编码默认系统编码
* @return {@link CsvData}包含数据列表和行信息 * @return {@link CsvData}包含数据列表和行信息
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
public CsvData read(Path path, Charset charset) throws IORuntimeException { public CsvData read(Path path, Charset charset) throws IORuntimeException {
Assert.notNull(path, "path must not be null"); Assert.notNull(path, "path must not be null");
return read(FileUtil.getReader(path, charset)); return read(FileUtil.getReader(path, charset));
} }
/** /**
* 从Reader中读取CSV数据读取后关闭Reader * 从Reader中读取CSV数据读取后关闭Reader
* *
* @param reader Reader * @param reader Reader
* @return {@link CsvData}包含数据列表和行信息 * @return {@link CsvData}包含数据列表和行信息
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
public CsvData read(Reader reader) throws IORuntimeException { public CsvData read(Reader reader) throws IORuntimeException {
return read(reader, true); return read(reader, true);
} }
/** /**
* 从Reader中读取CSV数据 * 从Reader中读取CSV数据
* *
* @param reader Reader * @param reader Reader
* @param close 读取结束是否关闭Reader * @param close 读取结束是否关闭Reader
* @return {@link CsvData}包含数据列表和行信息 * @return {@link CsvData}包含数据列表和行信息
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
public CsvData read(Reader reader, boolean close) throws IORuntimeException { public CsvData read(Reader reader, boolean close) throws IORuntimeException {
final CsvParser csvParser = parse(reader); final CsvParser csvParser = parse(reader);
final List<CsvRow> rows = new ArrayList<>(); final List<CsvRow> rows = new ArrayList<>();
read(csvParser, close, rows::add); read(csvParser, close, rows::add);
final List<String> header = config.headerLineNo > -1 ? csvParser.getHeader() : null; final List<String> header = config.headerLineNo > -1 ? csvParser.getHeader() : null;
return new CsvData(header, rows); return new CsvData(header, rows);
} }
/** /**
* 从Reader中读取CSV数据结果为Map读取后关闭Reader<br> * 从Reader中读取CSV数据结果为Map读取后关闭Reader<br>
* 此方法默认识别首行为标题行 * 此方法默认识别首行为标题行
* *
* @param reader Reader * @param reader Reader
* @return {@link CsvData}包含数据列表和行信息 * @return {@link CsvData}包含数据列表和行信息
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
public List<Map<String, String>> readMapList(Reader reader) throws IORuntimeException { public List<Map<String, String>> readMapList(Reader reader) throws IORuntimeException {
// 此方法必须包含标题 // 此方法必须包含标题
this.config.setContainsHeader(true); this.config.setContainsHeader(true);
final List<Map<String, String>> result = new ArrayList<>(); final List<Map<String, String>> result = new ArrayList<>();
read(reader, (row) -> result.add(row.getFieldMap())); read(reader, (row) -> result.add(row.getFieldMap()));
return result; return result;
} }
/** /**
* 从Reader中读取CSV数据并转换为Bean列表读取后关闭Reader<br> * 从Reader中读取CSV数据并转换为Bean列表读取后关闭Reader<br>
* 此方法默认识别首行为标题行 * 此方法默认识别首行为标题行
* *
* @param <T> Bean类型 * @param <T> Bean类型
* @param reader Reader * @param reader Reader
* @param clazz Bean类型 * @param clazz Bean类型
* @return Bean列表 * @return Bean列表
*/ */
public <T> List<T> read(Reader reader, Class<T> clazz) { public <T> List<T> read(Reader reader, Class<T> clazz) {
// 此方法必须包含标题 // 此方法必须包含标题
this.config.setContainsHeader(true); this.config.setContainsHeader(true);
final List<T> result = new ArrayList<>(); final List<T> result = new ArrayList<>();
read(reader, (row) -> result.add(row.toBean(clazz))); read(reader, (row) -> result.add(row.toBean(clazz)));
return result; return result;
} }
/** /**
* 从字符串中读取CSV数据并转换为Bean列表读取后关闭Reader<br> * 从字符串中读取CSV数据并转换为Bean列表读取后关闭Reader<br>
* 此方法默认识别首行为标题行 * 此方法默认识别首行为标题行
* *
* @param <T> Bean类型 * @param <T> Bean类型
* @param csvStr csv字符串 * @param csvStr csv字符串
* @param clazz Bean类型 * @param clazz Bean类型
* @return Bean列表 * @return Bean列表
*/ */
public <T> List<T> read(String csvStr, Class<T> clazz) { public <T> List<T> read(String csvStr, Class<T> clazz) {
// 此方法必须包含标题 // 此方法必须包含标题
this.config.setContainsHeader(true); this.config.setContainsHeader(true);
final List<T> result = new ArrayList<>(); final List<T> result = new ArrayList<>();
read(new StringReader(csvStr), (row) -> result.add(row.toBean(clazz))); read(new StringReader(csvStr), (row) -> result.add(row.toBean(clazz)));
return result; return result;
} }
/** /**
* 从Reader中读取CSV数据读取后关闭Reader * 从Reader中读取CSV数据读取后关闭Reader
* *
* @param reader Reader * @param reader Reader
* @param rowHandler 行处理器用于一行一行的处理数据 * @param rowHandler 行处理器用于一行一行的处理数据
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
public void read(Reader reader, CsvRowHandler rowHandler) throws IORuntimeException { public void read(Reader reader, CsvRowHandler rowHandler) throws IORuntimeException {
read(reader, true, rowHandler); read(reader, true, rowHandler);
} }
/** /**
* 从Reader中读取CSV数据读取后关闭Reader * 从Reader中读取CSV数据读取后关闭Reader
* *
* @param reader Reader * @param reader Reader
* @param close 读取结束是否关闭Reader * @param close 读取结束是否关闭Reader
* @param rowHandler 行处理器用于一行一行的处理数据 * @param rowHandler 行处理器用于一行一行的处理数据
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
public void read(Reader reader, boolean close, CsvRowHandler rowHandler) throws IORuntimeException { public void read(Reader reader, boolean close, CsvRowHandler rowHandler) throws IORuntimeException {
read(parse(reader), close, rowHandler); read(parse(reader), close, rowHandler);
} }
//--------------------------------------------------------------------------------------------- Private method start //--------------------------------------------------------------------------------------------- Private method start
/** /**
* 读取CSV数据读取后关闭Parser * 读取CSV数据读取后关闭Parser
* *
* @param csvParser CSV解析器 * @param csvParser CSV解析器
* @param close 读取结束是否关闭{@link CsvParser} * @param close 读取结束是否关闭{@link CsvParser}
* @param rowHandler 行处理器用于一行一行的处理数据 * @param rowHandler 行处理器用于一行一行的处理数据
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
* @since 5.0.4 * @since 5.0.4
*/ */
private void read(CsvParser csvParser, boolean close, CsvRowHandler rowHandler) throws IORuntimeException { private void read(CsvParser csvParser, boolean close, CsvRowHandler rowHandler) throws IORuntimeException {
try { try {
while (csvParser.hasNext()) { while (csvParser.hasNext()) {
rowHandler.handle(csvParser.next()); rowHandler.handle(csvParser.next());
} }
} finally { } finally {
if(close){ if (close) {
IoUtil.close(csvParser); IoUtil.close(csvParser);
} }
} }
} }
/** /**
* 构建 {@link CsvParser} * 构建 {@link CsvParser}
* *
* @param reader Reader * @param reader Reader
* @return CsvParser * @return CsvParser
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
protected CsvParser parse(Reader reader) throws IORuntimeException { protected CsvParser parse(Reader reader) throws IORuntimeException {
return new CsvParser(reader, this.config); return new CsvParser(reader, this.config);
} }
//--------------------------------------------------------------------------------------------- Private method start //--------------------------------------------------------------------------------------------- Private method start
} }

View File

@ -16,105 +16,105 @@ import java.util.Map;
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class CsvConfig<T extends CsvConfig<T>> implements Serializable { public class CsvConfig<T extends CsvConfig<T>> implements Serializable {
private static final long serialVersionUID = -8069578249066158459L; private static final long serialVersionUID = -8069578249066158459L;
/** /**
* 字段分隔符默认逗号',' * 字段分隔符默认逗号','
*/ */
protected char fieldSeparator = CharUtil.COMMA; protected char fieldSeparator = CharUtil.COMMA;
/** /**
* 文本包装符默认双引号'"' * 文本包装符默认双引号'"'
*/ */
protected char textDelimiter = CharUtil.DOUBLE_QUOTES; protected char textDelimiter = CharUtil.DOUBLE_QUOTES;
/** /**
* 注释符号用于区分注释行默认'#' * 注释符号用于区分注释行默认'#'
*/ */
protected Character commentCharacter = '#'; protected Character commentCharacter = '#';
/** /**
* 标题别名 * 标题别名
*/ */
protected Map<String, String> headerAlias = new LinkedHashMap<>(); protected Map<String, String> headerAlias = new LinkedHashMap<>();
/** /**
* 设置字段分隔符默认逗号',' * 设置字段分隔符默认逗号','
* *
* @param fieldSeparator 字段分隔符默认逗号',' * @param fieldSeparator 字段分隔符默认逗号','
* @return this * @return this
*/ */
public T setFieldSeparator(final char fieldSeparator) { public T setFieldSeparator(final char fieldSeparator) {
this.fieldSeparator = fieldSeparator; this.fieldSeparator = fieldSeparator;
return (T) this; return (T) this;
} }
/** /**
* 设置 文本分隔符文本包装符默认双引号'"' * 设置 文本分隔符文本包装符默认双引号'"'
* *
* @param textDelimiter 文本分隔符文本包装符默认双引号'"' * @param textDelimiter 文本分隔符文本包装符默认双引号'"'
* @return this * @return this
*/ */
public T setTextDelimiter(char textDelimiter) { public T setTextDelimiter(char textDelimiter) {
this.textDelimiter = textDelimiter; this.textDelimiter = textDelimiter;
return (T) this; return (T) this;
} }
/** /**
* 设置注释无效<br> * 设置注释无效<br>
* 当写出CSV时{@link CsvWriter#writeComment(String)}将抛出异常<br> * 当写出CSV时{@link CsvWriter#writeComment(String)}将抛出异常<br>
* 当读取CSV时注释行按照正常行读取 * 当读取CSV时注释行按照正常行读取
* *
* @return this * @return this
* @since 5.7.14 * @since 5.7.14
*/ */
public T disableComment() { public T disableComment() {
return setCommentCharacter(null); return setCommentCharacter(null);
} }
/** /**
* 设置 注释符号用于区分注释行{@code null}表示忽略注释 * 设置 注释符号用于区分注释行{@code null}表示忽略注释
* *
* @param commentCharacter 注释符号用于区分注释行 * @param commentCharacter 注释符号用于区分注释行
* @return this * @return this
* @since 5.5.7 * @since 5.5.7
*/ */
public T setCommentCharacter(Character commentCharacter) { public T setCommentCharacter(Character commentCharacter) {
this.commentCharacter = commentCharacter; this.commentCharacter = commentCharacter;
return (T) this; return (T) this;
} }
/** /**
* 设置标题行的别名Map * 设置标题行的别名Map
* *
* @param headerAlias 别名Map * @param headerAlias 别名Map
* @return this * @return this
* @since 5.7.10 * @since 5.7.10
*/ */
public T setHeaderAlias(Map<String, String> headerAlias) { public T setHeaderAlias(Map<String, String> headerAlias) {
this.headerAlias = headerAlias; this.headerAlias = headerAlias;
return (T) this; return (T) this;
} }
/** /**
* 增加标题别名 * 增加标题别名
* *
* @param header 标题 * @param header 标题
* @param alias 别名 * @param alias 别名
* @return this * @return this
* @since 5.7.10 * @since 5.7.10
*/ */
public T addHeaderAlias(String header, String alias) { public T addHeaderAlias(String header, String alias) {
this.headerAlias.put(header, alias); this.headerAlias.put(header, alias);
return (T) this; return (T) this;
} }
/** /**
* 去除标题别名 * 去除标题别名
* *
* @param header 标题 * @param header 标题
* @return this * @return this
* @since 5.7.10 * @since 5.7.10
*/ */
public T removeHeaderAlias(String header) { public T removeHeaderAlias(String header) {
this.headerAlias.remove(header); this.headerAlias.remove(header);
return (T) this; return (T) this;
} }
} }

View File

@ -11,73 +11,73 @@ import java.util.List;
* @author Looly * @author Looly
*/ */
public class CsvData implements Iterable<CsvRow>, Serializable { public class CsvData implements Iterable<CsvRow>, Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final List<String> header; private final List<String> header;
private final List<CsvRow> rows; private final List<CsvRow> rows;
/** /**
* 构造 * 构造
* *
* @param header 头信息, 可以为null * @param header 头信息, 可以为null
* @param rows * @param rows
*/ */
public CsvData(final List<String> header, final List<CsvRow> rows) { public CsvData(final List<String> header, final List<CsvRow> rows) {
this.header = header; this.header = header;
this.rows = rows; this.rows = rows;
} }
/** /**
* 总行数 * 总行数
* *
* @return 总行数 * @return 总行数
*/ */
public int getRowCount() { public int getRowCount() {
return this.rows.size(); return this.rows.size();
} }
/** /**
* 获取头信息列表如果无头信息为{@code Null}返回列表为只读列表 * 获取头信息列表如果无头信息为{@code Null}返回列表为只读列表
* *
* @return the header row - might be {@code null} if no header exists * @return the header row - might be {@code null} if no header exists
*/ */
public List<String> getHeader() { public List<String> getHeader() {
if(null == this.header){ if (null == this.header) {
return null; return null;
} }
return Collections.unmodifiableList(this.header); return Collections.unmodifiableList(this.header);
} }
/** /**
* 获取指定行从0开始 * 获取指定行从0开始
* *
* @param index 行号 * @param index 行号
* @return 行数据 * @return 行数据
* @throws IndexOutOfBoundsException if index is out of range * @throws IndexOutOfBoundsException if index is out of range
*/ */
public CsvRow getRow(final int index) { public CsvRow getRow(final int index) {
return this.rows.get(index); return this.rows.get(index);
} }
/** /**
* 获取所有行 * 获取所有行
* *
* @return 所有行 * @return 所有行
*/ */
public List<CsvRow> getRows() { public List<CsvRow> getRows() {
return this.rows; return this.rows;
} }
@Override @Override
public Iterator<CsvRow> iterator() { public Iterator<CsvRow> iterator() {
return this.rows.iterator(); return this.rows.iterator();
} }
@Override @Override
public String toString() { public String toString() {
return "CsvData{" + return "CsvData{" +
"header=" + header + "header=" + header +
", rows=" + rows + ", rows=" + rows +
'}'; '}';
} }
} }

View File

@ -22,468 +22,468 @@ import java.util.*;
* @author Looly * @author Looly
*/ */
public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, Serializable { public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final int DEFAULT_ROW_CAPACITY = 10; private static final int DEFAULT_ROW_CAPACITY = 10;
private final Reader reader; private final Reader reader;
private final CsvReadConfig config; private final CsvReadConfig config;
private final Buffer buf = new Buffer(IoUtil.DEFAULT_LARGE_BUFFER_SIZE); private final Buffer buf = new Buffer(IoUtil.DEFAULT_LARGE_BUFFER_SIZE);
/** /**
* 前一个特殊分界字符 * 前一个特殊分界字符
*/ */
private int preChar = -1; private int preChar = -1;
/** /**
* 是否在引号包装内 * 是否在引号包装内
*/ */
private boolean inQuotes; private boolean inQuotes;
/** /**
* 连续双引号计数 * 连续双引号计数
*/ */
private int continuousCount = 0; private int continuousCount = 0;
/** /**
* 当前读取字段 * 当前读取字段
*/ */
private final StrBuilder currentField = new StrBuilder(512); private final StrBuilder currentField = new StrBuilder(512);
/** /**
* 标题行 * 标题行
*/ */
private CsvRow header; private CsvRow header;
/** /**
* 当前行号 * 当前行号
*/ */
private long lineNo = -1; private long lineNo = -1;
/** /**
* 引号内的行数 * 引号内的行数
*/ */
private long inQuotesLineCount; private long inQuotesLineCount;
/** /**
* 第一行字段数用于检查每行字段数是否一致 * 第一行字段数用于检查每行字段数是否一致
*/ */
private int firstLineFieldCount = -1; private int firstLineFieldCount = -1;
/** /**
* 最大字段数量用于初始化行减少扩容 * 最大字段数量用于初始化行减少扩容
*/ */
private int maxFieldCount; private int maxFieldCount;
/** /**
* 是否读取结束 * 是否读取结束
*/ */
private boolean finished; private boolean finished;
/** /**
* CSV解析器 * CSV解析器
* *
* @param reader Reader * @param reader Reader
* @param config 配置null则为默认配置 * @param config 配置null则为默认配置
*/ */
public CsvParser(final Reader reader, CsvReadConfig config) { public CsvParser(final Reader reader, CsvReadConfig config) {
this.reader = Objects.requireNonNull(reader, "reader must not be null"); this.reader = Objects.requireNonNull(reader, "reader must not be null");
this.config = ObjectUtil.defaultIfNull(config, CsvReadConfig::defaultConfig); this.config = ObjectUtil.defaultIfNull(config, CsvReadConfig::defaultConfig);
} }
/** /**
* 获取头部字段列表如果headerLineNo &lt; 0抛出异常 * 获取头部字段列表如果headerLineNo &lt; 0抛出异常
* *
* @return 头部列表 * @return 头部列表
* @throws IllegalStateException 如果不解析头部或者没有调用nextRow()方法 * @throws IllegalStateException 如果不解析头部或者没有调用nextRow()方法
*/ */
public List<String> getHeader() { public List<String> getHeader() {
if (config.headerLineNo < 0) { if (config.headerLineNo < 0) {
throw new IllegalStateException("No header available - header parsing is disabled"); throw new IllegalStateException("No header available - header parsing is disabled");
} }
if (lineNo < config.beginLineNo) { if (lineNo < config.beginLineNo) {
throw new IllegalStateException("No header available - call nextRow() first"); throw new IllegalStateException("No header available - call nextRow() first");
} }
return header.getRawList(); return header.getRawList();
} }
@Override @Override
protected CsvRow computeNext() { protected CsvRow computeNext() {
return nextRow(); return nextRow();
} }
/** /**
* 读取下一行数据 * 读取下一行数据
* *
* @return CsvRow * @return CsvRow
* @throws IORuntimeException IO读取异常 * @throws IORuntimeException IO读取异常
*/ */
public CsvRow nextRow() throws IORuntimeException { public CsvRow nextRow() throws IORuntimeException {
List<String> currentFields; List<String> currentFields;
int fieldCount; int fieldCount;
while (false == finished) { while (false == finished) {
currentFields = readLine(); currentFields = readLine();
fieldCount = currentFields.size(); fieldCount = currentFields.size();
if (fieldCount < 1) { if (fieldCount < 1) {
// 空List表示读取结束 // 空List表示读取结束
break; break;
} }
// 读取范围校验 // 读取范围校验
if(lineNo < config.beginLineNo){ if (lineNo < config.beginLineNo) {
// 未达到读取起始行继续 // 未达到读取起始行继续
continue; continue;
} }
if(lineNo > config.endLineNo){ if (lineNo > config.endLineNo) {
// 超出结束行读取结束 // 超出结束行读取结束
break; break;
} }
// 跳过空行 // 跳过空行
if (config.skipEmptyRows && fieldCount == 1 && currentFields.get(0).isEmpty()) { if (config.skipEmptyRows && fieldCount == 1 && currentFields.get(0).isEmpty()) {
// [""]表示空行 // [""]表示空行
continue; continue;
} }
// 检查每行的字段数是否一致 // 检查每行的字段数是否一致
if (config.errorOnDifferentFieldCount) { if (config.errorOnDifferentFieldCount) {
if (firstLineFieldCount < 0) { if (firstLineFieldCount < 0) {
firstLineFieldCount = fieldCount; firstLineFieldCount = fieldCount;
} else if (fieldCount != firstLineFieldCount) { } else if (fieldCount != firstLineFieldCount) {
throw new IORuntimeException(String.format("Line %d has %d fields, but first line has %d fields", lineNo, fieldCount, firstLineFieldCount)); throw new IORuntimeException(String.format("Line %d has %d fields, but first line has %d fields", lineNo, fieldCount, firstLineFieldCount));
} }
} }
// 记录最大字段数 // 记录最大字段数
if (fieldCount > maxFieldCount) { if (fieldCount > maxFieldCount) {
maxFieldCount = fieldCount; maxFieldCount = fieldCount;
} }
//初始化标题 //初始化标题
if (lineNo == config.headerLineNo && null == header) { if (lineNo == config.headerLineNo && null == header) {
initHeader(currentFields); initHeader(currentFields);
// 作为标题行后此行跳过下一行做为第一行 // 作为标题行后此行跳过下一行做为第一行
continue; continue;
} }
return new CsvRow(lineNo, null == header ? null : header.headerMap, currentFields); return new CsvRow(lineNo, null == header ? null : header.headerMap, currentFields);
} }
return null; return null;
} }
/** /**
* 当前行做为标题行 * 当前行做为标题行
* *
* @param currentFields 当前行字段列表 * @param currentFields 当前行字段列表
*/ */
private void initHeader(final List<String> currentFields) { private void initHeader(final List<String> currentFields) {
final Map<String, Integer> localHeaderMap = new LinkedHashMap<>(currentFields.size()); final Map<String, Integer> localHeaderMap = new LinkedHashMap<>(currentFields.size());
for (int i = 0; i < currentFields.size(); i++) { for (int i = 0; i < currentFields.size(); i++) {
String field = currentFields.get(i); String field = currentFields.get(i);
if (MapUtil.isNotEmpty(this.config.headerAlias)) { if (MapUtil.isNotEmpty(this.config.headerAlias)) {
// 自定义别名 // 自定义别名
field = ObjectUtil.defaultIfNull(this.config.headerAlias.get(field), field); field = ObjectUtil.defaultIfNull(this.config.headerAlias.get(field), field);
} }
if (StrUtil.isNotEmpty(field) && false == localHeaderMap.containsKey(field)) { if (StrUtil.isNotEmpty(field) && false == localHeaderMap.containsKey(field)) {
localHeaderMap.put(field, i); localHeaderMap.put(field, i);
} }
} }
header = new CsvRow(this.lineNo, Collections.unmodifiableMap(localHeaderMap), Collections.unmodifiableList(currentFields)); header = new CsvRow(this.lineNo, Collections.unmodifiableMap(localHeaderMap), Collections.unmodifiableList(currentFields));
} }
/** /**
* 读取一行数据如果读取结束返回size为0的List<br> * 读取一行数据如果读取结束返回size为0的List<br>
* 空行是size为1的List唯一元素是"" * 空行是size为1的List唯一元素是""
* *
* <p> * <p>
* 行号要考虑注释行和引号包装的内容中的换行 * 行号要考虑注释行和引号包装的内容中的换行
* </p> * </p>
* *
* @return 一行数据 * @return 一行数据
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
private List<String> readLine() throws IORuntimeException { private List<String> readLine() throws IORuntimeException {
// 矫正行号 // 矫正行号
// 当一行内容包含多行数据时记录首行行号但是读取下一行时需要把多行内容的行数加上 // 当一行内容包含多行数据时记录首行行号但是读取下一行时需要把多行内容的行数加上
if(inQuotesLineCount > 0){ if (inQuotesLineCount > 0) {
this.lineNo += this.inQuotesLineCount; this.lineNo += this.inQuotesLineCount;
this.inQuotesLineCount = 0; this.inQuotesLineCount = 0;
} }
final List<String> currentFields = new ArrayList<>(maxFieldCount > 0 ? maxFieldCount : DEFAULT_ROW_CAPACITY); final List<String> currentFields = new ArrayList<>(maxFieldCount > 0 ? maxFieldCount : DEFAULT_ROW_CAPACITY);
final StrBuilder currentField = this.currentField; final StrBuilder currentField = this.currentField;
final Buffer buf = this.buf; final Buffer buf = this.buf;
int preChar = this.preChar;//前一个特殊分界字符 int preChar = this.preChar;//前一个特殊分界字符
int copyLen = 0; //拷贝长度 int copyLen = 0; //拷贝长度
boolean inComment = false; boolean inComment = false;
while (true) { while (true) {
if (false == buf.hasRemaining()) { if (false == buf.hasRemaining()) {
// 此Buffer读取结束开始读取下一段 // 此Buffer读取结束开始读取下一段
if (copyLen > 0) { if (copyLen > 0) {
buf.appendTo(currentField, copyLen); buf.appendTo(currentField, copyLen);
// 此处无需markread方法会重置mark // 此处无需markread方法会重置mark
} }
if (buf.read(this.reader) < 0) { if (buf.read(this.reader) < 0) {
// CSV读取结束 // CSV读取结束
finished = true; finished = true;
if (currentField.hasContent() || preChar == config.fieldSeparator) { if (currentField.hasContent() || preChar == config.fieldSeparator) {
//剩余部分作为一个字段 //剩余部分作为一个字段
addField(currentFields, currentField.toStringAndReset()); addField(currentFields, currentField.toStringAndReset());
} }
break; break;
} }
//重置 //重置
copyLen = 0; copyLen = 0;
} }
final char c = buf.get(); final char c = buf.get();
// 注释行标记 // 注释行标记
if(preChar < 0 || preChar == CharUtil.CR || preChar == CharUtil.LF){ if (preChar < 0 || preChar == CharUtil.CR || preChar == CharUtil.LF) {
// 判断行首字符为指定注释字符的注释开始直到遇到换行符 // 判断行首字符为指定注释字符的注释开始直到遇到换行符
// 行首分两种1是preChar < 0表示文本开始2是换行符后紧跟就是下一行的开始 // 行首分两种1是preChar < 0表示文本开始2是换行符后紧跟就是下一行的开始
// issue#IA8WE0 如果注释符出现在包装符内被认为是普通字符 // issue#IA8WE0 如果注释符出现在包装符内被认为是普通字符
if((false == inQuotes) && null != this.config.commentCharacter && c == this.config.commentCharacter){ if ((false == inQuotes) && null != this.config.commentCharacter && c == this.config.commentCharacter) {
inComment = true; inComment = true;
} }
} }
// 注释行处理 // 注释行处理
if(inComment){ if (inComment) {
if (c == CharUtil.CR || c == CharUtil.LF) { if (c == CharUtil.CR || c == CharUtil.LF) {
// 注释行以换行符为结尾 // 注释行以换行符为结尾
lineNo++; lineNo++;
inComment = false; inComment = false;
} }
// 跳过注释行中的任何字符 // 跳过注释行中的任何字符
buf.mark(); buf.mark();
preChar = c; preChar = c;
continue; continue;
} }
if (inQuotes) { if (inQuotes) {
//引号内作为内容直到引号结束 //引号内作为内容直到引号结束
if (c == config.textDelimiter) { if (c == config.textDelimiter) {
if(buf.canRead(1) && buf.read(1) == CharUtil.DOUBLE_QUOTES) { if (buf.canRead(1) && buf.read(1) == CharUtil.DOUBLE_QUOTES) {
continuousCount++; continuousCount++;
} else if(continuousCount != 0 && (continuousCount + 1) % 2 == 0) { } else if (continuousCount != 0 && (continuousCount + 1) % 2 == 0) {
continuousCount = 0; continuousCount = 0;
} else { } else {
inQuotes = false; inQuotes = false;
} }
} else { } else {
if(continuousCount != 0) continuousCount = 0; if (continuousCount != 0) continuousCount = 0;
// 字段内容中新行 // 字段内容中新行
if (isLineEnd(c, preChar)) { if (isLineEnd(c, preChar)) {
inQuotesLineCount++; inQuotesLineCount++;
} }
} }
// 普通字段字符 // 普通字段字符
copyLen++; copyLen++;
} else { } else {
// 非引号内 // 非引号内
if (c == config.fieldSeparator) { if (c == config.fieldSeparator) {
//一个字段结束 //一个字段结束
if (copyLen > 0) { if (copyLen > 0) {
buf.appendTo(currentField, copyLen); buf.appendTo(currentField, copyLen);
copyLen = 0; copyLen = 0;
} }
buf.mark(); buf.mark();
addField(currentFields, currentField.toStringAndReset()); addField(currentFields, currentField.toStringAndReset());
} else if (c == config.textDelimiter && isFieldBegin(preChar)) { } else if (c == config.textDelimiter && isFieldBegin(preChar)) {
// 引号开始且出现在字段开头 // 引号开始且出现在字段开头
inQuotes = true; inQuotes = true;
copyLen++; copyLen++;
} else if (c == CharUtil.CR) { } else if (c == CharUtil.CR) {
// \r直接结束 // \r直接结束
if (copyLen > 0) { if (copyLen > 0) {
buf.appendTo(currentField, copyLen); buf.appendTo(currentField, copyLen);
} }
buf.mark(); buf.mark();
addField(currentFields, currentField.toStringAndReset()); addField(currentFields, currentField.toStringAndReset());
preChar = c; preChar = c;
break; break;
} else if (c == CharUtil.LF) { } else if (c == CharUtil.LF) {
// \n // \n
if (preChar != CharUtil.CR) { if (preChar != CharUtil.CR) {
if (copyLen > 0) { if (copyLen > 0) {
buf.appendTo(currentField, copyLen); buf.appendTo(currentField, copyLen);
} }
buf.mark(); buf.mark();
addField(currentFields, currentField.toStringAndReset()); addField(currentFields, currentField.toStringAndReset());
preChar = c; preChar = c;
break; break;
} }
// 前一个字符是\r已经处理过这个字段了此处直接跳过 // 前一个字符是\r已经处理过这个字段了此处直接跳过
buf.mark(); buf.mark();
} else { } else {
// 普通字符 // 普通字符
copyLen++; copyLen++;
} }
} }
preChar = c; preChar = c;
} }
// restore fields // restore fields
this.preChar = preChar; this.preChar = preChar;
lineNo++; lineNo++;
return currentFields; return currentFields;
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
reader.close(); reader.close();
} }
/** /**
* 将字段加入字段列表并自动去包装和去转义 * 将字段加入字段列表并自动去包装和去转义
* *
* @param currentFields 当前的字段列表即为行 * @param currentFields 当前的字段列表即为行
* @param field 字段 * @param field 字段
*/ */
private void addField(List<String> currentFields, String field) { private void addField(List<String> currentFields, String field) {
final char textDelimiter = this.config.textDelimiter; final char textDelimiter = this.config.textDelimiter;
// 忽略多余引号后的换行符 // 忽略多余引号后的换行符
field = StrUtil.trim(field, 1, (c-> c == CharUtil.LF || c == CharUtil.CR)); field = StrUtil.trim(field, 1, (c -> c == CharUtil.LF || c == CharUtil.CR));
// 去除手写csv列值前后的缩进符 // 去除手写csv列值前后的缩进符
field = field.replaceAll("\t+\"|\"\t+", "\""); field = field.replaceAll("\t+\"|\"\t+", "\"");
if(StrUtil.isWrap(field, textDelimiter)){ if (StrUtil.isWrap(field, textDelimiter)) {
field = StrUtil.sub(field, 1, field.length() - 1); field = StrUtil.sub(field, 1, field.length() - 1);
// https://datatracker.ietf.org/doc/html/rfc4180#section-2 // https://datatracker.ietf.org/doc/html/rfc4180#section-2
// 第七条规则只有包装内的包装符需要转义 // 第七条规则只有包装内的包装符需要转义
field = StrUtil.replace(field, String.valueOf(textDelimiter) + textDelimiter, String.valueOf(textDelimiter)); field = StrUtil.replace(field, String.valueOf(textDelimiter) + textDelimiter, String.valueOf(textDelimiter));
} }
if(this.config.trimField){ if (this.config.trimField) {
// issue#I49M0C@Gitee // issue#I49M0C@Gitee
field = StrUtil.trim(field); field = StrUtil.trim(field);
} }
currentFields.add(field); currentFields.add(field);
} }
/** /**
* 是否行结束符 * 是否行结束符
* *
* @param c 符号 * @param c 符号
* @param preChar 前一个字符 * @param preChar 前一个字符
* @return 是否结束 * @return 是否结束
* @since 5.7.4 * @since 5.7.4
*/ */
private boolean isLineEnd(char c, int preChar) { private boolean isLineEnd(char c, int preChar) {
return (c == CharUtil.CR || c == CharUtil.LF) && preChar != CharUtil.CR; return (c == CharUtil.CR || c == CharUtil.LF) && preChar != CharUtil.CR;
} }
/** /**
* 通过前一个字符判断是否字段开始几种情况 * 通过前一个字符判断是否字段开始几种情况
* <ul> * <ul>
* <li>正文开头无前字符</li> * <li>正文开头无前字符</li>
* <li>缩进</li> * <li>缩进</li>
* <li>字段分隔符即上个字段结束</li> * <li>字段分隔符即上个字段结束</li>
* <li>换行符即新行开始</li> * <li>换行符即新行开始</li>
* </ul> * </ul>
* *
* @param preChar 前字符 * @param preChar 前字符
* @return 是否字段开始 * @return 是否字段开始
*/ */
private boolean isFieldBegin(final int preChar) { private boolean isFieldBegin(final int preChar) {
return preChar == -1 return preChar == -1
|| preChar == CoreConstants.TAB || preChar == CoreConstants.TAB
|| preChar == config.fieldSeparator || preChar == config.fieldSeparator
|| preChar == CharUtil.LF || preChar == CharUtil.LF
|| preChar == CharUtil.CR; || preChar == CharUtil.CR;
} }
/** /**
* 内部Buffer * 内部Buffer
* *
* @author looly * @author looly
*/ */
private static class Buffer implements Serializable{ private static class Buffer implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
final char[] buf; final char[] buf;
/** /**
* 标记位置用于读数据 * 标记位置用于读数据
*/ */
private int mark; private int mark;
/** /**
* 当前位置 * 当前位置
*/ */
private int position; private int position;
/** /**
* 读取的数据长度一般小于buf.length-1表示无数据 * 读取的数据长度一般小于buf.length-1表示无数据
*/ */
private int limit; private int limit;
Buffer(int capacity) { Buffer(int capacity) {
buf = new char[capacity]; buf = new char[capacity];
} }
/** /**
* 是否还有未读数据 * 是否还有未读数据
* *
* @return 是否还有未读数据 * @return 是否还有未读数据
*/ */
public final boolean hasRemaining() { public final boolean hasRemaining() {
return position < limit; return position < limit;
} }
/** /**
* 读取到缓存<br> * 读取到缓存<br>
* 全量读取会重置Buffer中所有数据 * 全量读取会重置Buffer中所有数据
* *
* @param reader {@link Reader} * @param reader {@link Reader}
*/ */
int read(Reader reader) { int read(Reader reader) {
int length; int length;
try { try {
length = reader.read(this.buf); length = reader.read(this.buf);
} catch (IOException e) { } catch (IOException e) {
throw new IORuntimeException(e); throw new IORuntimeException(e);
} }
this.mark = 0; this.mark = 0;
this.position = 0; this.position = 0;
this.limit = length; this.limit = length;
return length; return length;
} }
/** /**
* 先获取当前字符再将当前位置后移一位<br> * 先获取当前字符再将当前位置后移一位<br>
* 此方法不检查是否到了数组末尾请自行使用{@link #hasRemaining()}判断 * 此方法不检查是否到了数组末尾请自行使用{@link #hasRemaining()}判断
* *
* @return 当前位置字符 * @return 当前位置字符
* @see #hasRemaining() * @see #hasRemaining()
*/ */
char get() { char get() {
return this.buf[this.position++]; return this.buf[this.position++];
} }
boolean canRead(int position) { boolean canRead(int position) {
return (this.position + position - 1) < limit; return (this.position + position - 1) < limit;
} }
char read(int position) { char read(int position) {
return this.buf[this.position + position - 1]; return this.buf[this.position + position - 1];
} }
/** /**
* 标记位置记为下次读取位置 * 标记位置记为下次读取位置
*/ */
void mark() { void mark() {
this.mark = this.position; this.mark = this.position;
} }
/** /**
* 将数据追加到{@link StrBuilder}追加结束后需手动调用{@link #mark()} 重置读取位置 * 将数据追加到{@link StrBuilder}追加结束后需手动调用{@link #mark()} 重置读取位置
* *
* @param builder {@link StrBuilder} * @param builder {@link StrBuilder}
* @param length 追加的长度 * @param length 追加的长度
* @see #mark() * @see #mark()
*/ */
void appendTo(StrBuilder builder, int length) { void appendTo(StrBuilder builder, int length) {
builder.append(this.buf, this.mark, length); builder.append(this.buf, this.mark, length);
} }
} }
} }

View File

@ -6,113 +6,124 @@ import java.io.Serializable;
* CSV读取配置项 * CSV读取配置项
* *
* @author looly * @author looly
*
*/ */
public class CsvReadConfig extends CsvConfig<CsvReadConfig> implements Serializable { public class CsvReadConfig extends CsvConfig<CsvReadConfig> implements Serializable {
private static final long serialVersionUID = 5396453565371560052L; private static final long serialVersionUID = 5396453565371560052L;
/** 指定标题行号,-1表示无标题行 */ /**
protected long headerLineNo = -1; * 指定标题行号-1表示无标题行
/** 是否跳过空白行默认true */ */
protected boolean skipEmptyRows = true; protected long headerLineNo = -1;
/** 每行字段个数不同时是否抛出异常默认false */ /**
protected boolean errorOnDifferentFieldCount; * 是否跳过空白行默认true
/** 定义开始的行(包括),此处为原始文件行号 */ */
protected long beginLineNo; protected boolean skipEmptyRows = true;
/** 结束的行(包括),此处为原始文件行号 */ /**
protected long endLineNo = Long.MAX_VALUE-1; * 每行字段个数不同时是否抛出异常默认false
/** 每个字段是否去除两边空白符 */ */
protected boolean trimField; protected boolean errorOnDifferentFieldCount;
/**
* 定义开始的行包括此处为原始文件行号
*/
protected long beginLineNo;
/**
* 结束的行包括此处为原始文件行号
*/
protected long endLineNo = Long.MAX_VALUE - 1;
/**
* 每个字段是否去除两边空白符
*/
protected boolean trimField;
/** /**
* 默认配置 * 默认配置
* *
* @return 默认配置 * @return 默认配置
*/ */
public static CsvReadConfig defaultConfig() { public static CsvReadConfig defaultConfig() {
return new CsvReadConfig(); return new CsvReadConfig();
} }
/** /**
* 设置是否首行做为标题行默认false<br> * 设置是否首行做为标题行默认false<br>
* 当设置为{@code true}默认标题行号是{@link #beginLineNo}{@code false}-1表示无行号 * 当设置为{@code true}默认标题行号是{@link #beginLineNo}{@code false}-1表示无行号
* *
* @param containsHeader 是否首行做为标题行默认false * @param containsHeader 是否首行做为标题行默认false
* @return this * @return this
* @see #setHeaderLineNo(long) * @see #setHeaderLineNo(long)
*/ */
public CsvReadConfig setContainsHeader(boolean containsHeader) { public CsvReadConfig setContainsHeader(boolean containsHeader) {
return setHeaderLineNo(containsHeader ? beginLineNo : -1); return setHeaderLineNo(containsHeader ? beginLineNo : -1);
} }
/** /**
* 设置标题行行号默认-1表示无标题行<br> * 设置标题行行号默认-1表示无标题行<br>
* *
* @param headerLineNo 标题行行号-1表示无标题行 * @param headerLineNo 标题行行号-1表示无标题行
* @return this * @return this
* @since 5.7.23 * @since 5.7.23
*/ */
public CsvReadConfig setHeaderLineNo(long headerLineNo) { public CsvReadConfig setHeaderLineNo(long headerLineNo) {
this.headerLineNo = headerLineNo; this.headerLineNo = headerLineNo;
return this; return this;
} }
/** /**
* 设置是否跳过空白行默认true * 设置是否跳过空白行默认true
* *
* @param skipEmptyRows 是否跳过空白行默认true * @param skipEmptyRows 是否跳过空白行默认true
* @return this * @return this
*/ */
public CsvReadConfig setSkipEmptyRows(boolean skipEmptyRows) { public CsvReadConfig setSkipEmptyRows(boolean skipEmptyRows) {
this.skipEmptyRows = skipEmptyRows; this.skipEmptyRows = skipEmptyRows;
return this; return this;
} }
/** /**
* 设置每行字段个数不同时是否抛出异常默认false * 设置每行字段个数不同时是否抛出异常默认false
* *
* @param errorOnDifferentFieldCount 每行字段个数不同时是否抛出异常默认false * @param errorOnDifferentFieldCount 每行字段个数不同时是否抛出异常默认false
* @return this * @return this
*/ */
public CsvReadConfig setErrorOnDifferentFieldCount(boolean errorOnDifferentFieldCount) { public CsvReadConfig setErrorOnDifferentFieldCount(boolean errorOnDifferentFieldCount) {
this.errorOnDifferentFieldCount = errorOnDifferentFieldCount; this.errorOnDifferentFieldCount = errorOnDifferentFieldCount;
return this; return this;
} }
/** /**
* 设置开始的行包括默认0此处为原始文件行号 * 设置开始的行包括默认0此处为原始文件行号
* *
* @param beginLineNo 开始的行号包括 * @param beginLineNo 开始的行号包括
* @return this * @return this
* @since 5.7.4 * @since 5.7.4
*/ */
public CsvReadConfig setBeginLineNo(long beginLineNo) { public CsvReadConfig setBeginLineNo(long beginLineNo) {
this.beginLineNo = beginLineNo; this.beginLineNo = beginLineNo;
return this; return this;
} }
/** /**
* 设置结束的行包括默认不限制此处为原始文件行号 * 设置结束的行包括默认不限制此处为原始文件行号
* *
* @param endLineNo 结束的行号包括 * @param endLineNo 结束的行号包括
* @return this * @return this
* @since 5.7.4 * @since 5.7.4
*/ */
public CsvReadConfig setEndLineNo(long endLineNo) { public CsvReadConfig setEndLineNo(long endLineNo) {
this.endLineNo = endLineNo; this.endLineNo = endLineNo;
return this; return this;
} }
/** /**
* 设置每个字段是否去除两边空白符<br> * 设置每个字段是否去除两边空白符<br>
* 如果字段以{@link #textDelimiter}包围则保留两边空格 * 如果字段以{@link #textDelimiter}包围则保留两边空格
* *
* @param trimField 去除两边空白符 * @param trimField 去除两边空白符
* @return this * @return this
* @since 5.7.13 * @since 5.7.13
*/ */
public CsvReadConfig setTrimField(boolean trimField) { public CsvReadConfig setTrimField(boolean trimField) {
this.trimField = trimField; this.trimField = trimField;
return this; return this;
} }
} }

View File

@ -21,133 +21,134 @@ import java.util.stream.StreamSupport;
* @since 4.0.1 * @since 4.0.1
*/ */
public class CsvReader extends CsvBaseReader implements Iterable<CsvRow>, Closeable { public class CsvReader extends CsvBaseReader implements Iterable<CsvRow>, Closeable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final Reader reader; private final Reader reader;
//--------------------------------------------------------------------------------------------- Constructor start //--------------------------------------------------------------------------------------------- Constructor start
/** /**
* 构造使用默认配置项 * 构造使用默认配置项
*/ */
public CsvReader() { public CsvReader() {
this(null); this(null);
} }
/** /**
* 构造 * 构造
* *
* @param config 配置项 * @param config 配置项
*/ */
public CsvReader(CsvReadConfig config) { public CsvReader(CsvReadConfig config) {
this((Reader) null, config); this((Reader) null, config);
} }
/** /**
* 构造默认{@link #DEFAULT_CHARSET}编码 * 构造默认{@link #DEFAULT_CHARSET}编码
* *
* @param file CSV文件路径null表示不设置路径 * @param file CSV文件路径null表示不设置路径
* @param config 配置项null表示默认配置 * @param config 配置项null表示默认配置
* @since 5.0.4 * @since 5.0.4
*/ */
public CsvReader(File file, CsvReadConfig config) { public CsvReader(File file, CsvReadConfig config) {
this(file, DEFAULT_CHARSET, config); this(file, DEFAULT_CHARSET, config);
} }
/** /**
* 构造默认{@link #DEFAULT_CHARSET}编码 * 构造默认{@link #DEFAULT_CHARSET}编码
* *
* @param path CSV文件路径null表示不设置路径 * @param path CSV文件路径null表示不设置路径
* @param config 配置项null表示默认配置 * @param config 配置项null表示默认配置
* @since 5.0.4 * @since 5.0.4
*/ */
public CsvReader(Path path, CsvReadConfig config) { public CsvReader(Path path, CsvReadConfig config) {
this(path, DEFAULT_CHARSET, config); this(path, DEFAULT_CHARSET, config);
} }
/** /**
* 构造 * 构造
* *
* @param file CSV文件路径null表示不设置路径 * @param file CSV文件路径null表示不设置路径
* @param charset 编码 * @param charset 编码
* @param config 配置项null表示默认配置 * @param config 配置项null表示默认配置
* @since 5.0.4 * @since 5.0.4
*/ */
public CsvReader(File file, Charset charset, CsvReadConfig config) { public CsvReader(File file, Charset charset, CsvReadConfig config) {
this(FileUtil.getReader(file, charset), config); this(FileUtil.getReader(file, charset), config);
} }
/** /**
* 构造 * 构造
* *
* @param path CSV文件路径null表示不设置路径 * @param path CSV文件路径null表示不设置路径
* @param charset 编码 * @param charset 编码
* @param config 配置项null表示默认配置 * @param config 配置项null表示默认配置
* @since 5.0.4 * @since 5.0.4
*/ */
public CsvReader(Path path, Charset charset, CsvReadConfig config) { public CsvReader(Path path, Charset charset, CsvReadConfig config) {
this(FileUtil.getReader(path, charset), config); this(FileUtil.getReader(path, charset), config);
} }
/** /**
* 构造 * 构造
* *
* @param reader {@link Reader}null表示不设置默认reader * @param reader {@link Reader}null表示不设置默认reader
* @param config 配置项null表示默认配置 * @param config 配置项null表示默认配置
* @since 5.0.4 * @since 5.0.4
*/ */
public CsvReader(Reader reader, CsvReadConfig config) { public CsvReader(Reader reader, CsvReadConfig config) {
super(config); super(config);
this.reader = reader; this.reader = reader;
} }
//--------------------------------------------------------------------------------------------- Constructor end //--------------------------------------------------------------------------------------------- Constructor end
/**
* 读取CSV文件此方法只能调用一次<br>
* 调用此方法的前提是构造中传入文件路径或Reader
*
* @return {@link CsvData}包含数据列表和行信息
* @throws IORuntimeException IO异常
*/
public CsvData read() throws IORuntimeException {
return read(this.reader, false);
}
/** /**
* 读取CSV数据此方法只能调用一次<br> * 读取CSV文件此方法只能调用一次<br>
* 调用此方法的前提是构造中传入文件路径或Reader * 调用此方法的前提是构造中传入文件路径或Reader
* *
* @param rowHandler 行处理器用于一行一行的处理数据 * @return {@link CsvData}包含数据列表和行信息
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
* @since 5.0.4 */
*/ public CsvData read() throws IORuntimeException {
public void read(CsvRowHandler rowHandler) throws IORuntimeException { return read(this.reader, false);
read(this.reader, false, rowHandler); }
}
/** /**
* 根据Reader创建{@link Stream}以便使用stream方式读取csv行 * 读取CSV数据此方法只能调用一次<br>
* * 调用此方法的前提是构造中传入文件路径或Reader
* @return {@link Stream} *
* @since 5.7.14 * @param rowHandler 行处理器用于一行一行的处理数据
*/ * @throws IORuntimeException IO异常
public Stream<CsvRow> stream() { * @since 5.0.4
return StreamSupport.stream(spliterator(), false) */
.onClose(() -> { public void read(CsvRowHandler rowHandler) throws IORuntimeException {
try { read(this.reader, false, rowHandler);
close(); }
} catch (final IOException e) {
throw new IORuntimeException(e);
}
});
}
@Override /**
public Iterator<CsvRow> iterator() { * 根据Reader创建{@link Stream}以便使用stream方式读取csv行
return parse(this.reader); *
} * @return {@link Stream}
* @since 5.7.14
*/
public Stream<CsvRow> stream() {
return StreamSupport.stream(spliterator(), false)
.onClose(() -> {
try {
close();
} catch (final IOException e) {
throw new IORuntimeException(e);
}
});
}
@Override @Override
public void close() throws IOException { public Iterator<CsvRow> iterator() {
IoUtil.close(this.reader); return parse(this.reader);
} }
@Override
public void close() throws IOException {
IoUtil.close(this.reader);
}
} }

View File

@ -12,251 +12,253 @@ import java.util.*;
*/ */
public final class CsvRow implements List<String> { public final class CsvRow implements List<String> {
/** 原始行号 */ /**
private final long originalLineNumber; * 原始行号
*/
private final long originalLineNumber;
final Map<String, Integer> headerMap; final Map<String, Integer> headerMap;
final List<String> fields; final List<String> fields;
/** /**
* 构造 * 构造
* *
* @param originalLineNumber 对应文件中的第几行 * @param originalLineNumber 对应文件中的第几行
* @param headerMap 标题Map * @param headerMap 标题Map
* @param fields 数据列表 * @param fields 数据列表
*/ */
public CsvRow(long originalLineNumber, Map<String, Integer> headerMap, List<String> fields) { public CsvRow(long originalLineNumber, Map<String, Integer> headerMap, List<String> fields) {
Assert.notNull(fields, "fields must be not null!"); Assert.notNull(fields, "fields must be not null!");
this.originalLineNumber = originalLineNumber; this.originalLineNumber = originalLineNumber;
this.headerMap = headerMap; this.headerMap = headerMap;
this.fields = fields; this.fields = fields;
} }
/** /**
* 获取原始行号多行情况下为首行行号忽略注释行 * 获取原始行号多行情况下为首行行号忽略注释行
* *
* @return the original line number 行号 * @return the original line number 行号
*/ */
public long getOriginalLineNumber() { public long getOriginalLineNumber() {
return originalLineNumber; return originalLineNumber;
} }
/** /**
* 获取标题对应的字段内容 * 获取标题对应的字段内容
* *
* @param name 标题名 * @param name 标题名
* @return 字段值null表示无此字段值 * @return 字段值null表示无此字段值
* @throws IllegalStateException CSV文件无标题行抛出此异常 * @throws IllegalStateException CSV文件无标题行抛出此异常
*/ */
public String getByName(String name) { public String getByName(String name) {
Assert.notNull(this.headerMap, "No header available!"); Assert.notNull(this.headerMap, "No header available!");
final Integer col = headerMap.get(name); final Integer col = headerMap.get(name);
if (col != null) { if (col != null) {
return get(col); return get(col);
} }
return null; return null;
} }
/** /**
* 获取本行所有字段值列表 * 获取本行所有字段值列表
* *
* @return 字段值列表 * @return 字段值列表
*/ */
public List<String> getRawList() { public List<String> getRawList() {
return fields; return fields;
} }
/** /**
* 获取标题与字段值对应的Map * 获取标题与字段值对应的Map
* *
* @return 标题与字段值对应的Map * @return 标题与字段值对应的Map
* @throws IllegalStateException CSV文件无标题行抛出此异常 * @throws IllegalStateException CSV文件无标题行抛出此异常
*/ */
public Map<String, String> getFieldMap() { public Map<String, String> getFieldMap() {
if (headerMap == null) { if (headerMap == null) {
throw new IllegalStateException("No header available"); throw new IllegalStateException("No header available");
} }
final Map<String, String> fieldMap = new LinkedHashMap<>(headerMap.size(), 1); final Map<String, String> fieldMap = new LinkedHashMap<>(headerMap.size(), 1);
String key; String key;
Integer col; Integer col;
String val; String val;
for (final Map.Entry<String, Integer> header : headerMap.entrySet()) { for (final Map.Entry<String, Integer> header : headerMap.entrySet()) {
key = header.getKey(); key = header.getKey();
col = headerMap.get(key); col = headerMap.get(key);
val = null == col ? null : get(col); val = null == col ? null : get(col);
fieldMap.put(key, val); fieldMap.put(key, val);
} }
return fieldMap; return fieldMap;
} }
/** /**
* 一行数据转换为Bean对象 * 一行数据转换为Bean对象
* *
* @param <T> Bean类型 * @param <T> Bean类型
* @param clazz bean类 * @param clazz bean类
* @return Bean * @return Bean
* @since 5.3.6 * @since 5.3.6
*/ */
public <T> T toBean(Class<T> clazz){ public <T> T toBean(Class<T> clazz) {
return BeanUtil.toBeanIgnoreError(getFieldMap(), clazz); return BeanUtil.toBeanIgnoreError(getFieldMap(), clazz);
} }
/** /**
* 获取字段格式 * 获取字段格式
* *
* @return 字段格式 * @return 字段格式
*/ */
public int getFieldCount() { public int getFieldCount() {
return fields.size(); return fields.size();
} }
@Override @Override
public int size() { public int size() {
return this.fields.size(); return this.fields.size();
} }
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return this.fields.isEmpty(); return this.fields.isEmpty();
} }
@Override @Override
public boolean contains(Object o) { public boolean contains(Object o) {
return this.fields.contains(o); return this.fields.contains(o);
} }
@Override @Override
public Iterator<String> iterator() { public Iterator<String> iterator() {
return this.fields.iterator(); return this.fields.iterator();
} }
@Override @Override
public Object[] toArray() { public Object[] toArray() {
return this.fields.toArray(); return this.fields.toArray();
} }
@Override @Override
public <T> T[] toArray(T[] a) { public <T> T[] toArray(T[] a) {
//noinspection SuspiciousToArrayCall //noinspection SuspiciousToArrayCall
return this.fields.toArray(a); return this.fields.toArray(a);
} }
@Override @Override
public boolean add(String e) { public boolean add(String e) {
return this.fields.add(e); return this.fields.add(e);
} }
@Override @Override
public boolean remove(Object o) { public boolean remove(Object o) {
return this.fields.remove(o); return this.fields.remove(o);
} }
@Override @Override
public boolean containsAll(Collection<?> c) { public boolean containsAll(Collection<?> c) {
return this.fields.containsAll(c); return this.fields.containsAll(c);
} }
@Override @Override
public boolean addAll(Collection<? extends String> c) { public boolean addAll(Collection<? extends String> c) {
return this.fields.addAll(c); return this.fields.addAll(c);
} }
@Override @Override
public boolean addAll(int index, Collection<? extends String> c) { public boolean addAll(int index, Collection<? extends String> c) {
return this.fields.addAll(index, c); return this.fields.addAll(index, c);
} }
@Override @Override
public boolean removeAll(Collection<?> c) { public boolean removeAll(Collection<?> c) {
return this.fields.removeAll(c); return this.fields.removeAll(c);
} }
@Override @Override
public boolean retainAll(Collection<?> c) { public boolean retainAll(Collection<?> c) {
return this.fields.retainAll(c); return this.fields.retainAll(c);
} }
@Override @Override
public void clear() { public void clear() {
this.fields.clear(); this.fields.clear();
} }
@Override @Override
public String get(int index) { public String get(int index) {
return index >= fields.size() ? null : fields.get(index); return index >= fields.size() ? null : fields.get(index);
} }
@Override @Override
public String set(int index, String element) { public String set(int index, String element) {
return this.fields.set(index, element); return this.fields.set(index, element);
} }
@Override @Override
public void add(int index, String element) { public void add(int index, String element) {
this.fields.add(index, element); this.fields.add(index, element);
} }
@Override @Override
public String remove(int index) { public String remove(int index) {
return this.fields.remove(index); return this.fields.remove(index);
} }
@Override @Override
public int indexOf(Object o) { public int indexOf(Object o) {
return this.fields.indexOf(o); return this.fields.indexOf(o);
} }
@Override @Override
public int lastIndexOf(Object o) { public int lastIndexOf(Object o) {
return this.fields.lastIndexOf(o); return this.fields.lastIndexOf(o);
} }
@Override @Override
public ListIterator<String> listIterator() { public ListIterator<String> listIterator() {
return this.fields.listIterator(); return this.fields.listIterator();
} }
@Override @Override
public ListIterator<String> listIterator(int index) { public ListIterator<String> listIterator(int index) {
return this.fields.listIterator(index); return this.fields.listIterator(index);
} }
@Override @Override
public List<String> subList(int fromIndex, int toIndex) { public List<String> subList(int fromIndex, int toIndex) {
return this.fields.subList(fromIndex, toIndex); return this.fields.subList(fromIndex, toIndex);
} }
@Override @Override
public String toString() { public String toString() {
final StringBuilder sb = new StringBuilder("CsvRow{"); final StringBuilder sb = new StringBuilder("CsvRow{");
sb.append("originalLineNumber="); sb.append("originalLineNumber=");
sb.append(originalLineNumber); sb.append(originalLineNumber);
sb.append(", "); sb.append(", ");
sb.append("fields="); sb.append("fields=");
if (headerMap != null) { if (headerMap != null) {
sb.append('{'); sb.append('{');
for (final Iterator<Map.Entry<String, String>> it = getFieldMap().entrySet().iterator(); it.hasNext();) { for (final Iterator<Map.Entry<String, String>> it = getFieldMap().entrySet().iterator(); it.hasNext(); ) {
final Map.Entry<String, String> entry = it.next(); final Map.Entry<String, String> entry = it.next();
sb.append(entry.getKey()); sb.append(entry.getKey());
sb.append('='); sb.append('=');
if (entry.getValue() != null) { if (entry.getValue() != null) {
sb.append(entry.getValue()); sb.append(entry.getValue());
} }
if (it.hasNext()) { if (it.hasNext()) {
sb.append(", "); sb.append(", ");
} }
} }
sb.append('}'); sb.append('}');
} else { } else {
sb.append(fields.toString()); sb.append(fields.toString());
} }
sb.append('}'); sb.append('}');
return sb.toString(); return sb.toString();
} }
} }

View File

@ -9,10 +9,10 @@ package cn.octopusyan.dmt.utils.csv;
@FunctionalInterface @FunctionalInterface
public interface CsvRowHandler { public interface CsvRowHandler {
/** /**
* 处理行数据 * 处理行数据
* *
* @param row 行数据 * @param row 行数据
*/ */
void handle(CsvRow row); void handle(CsvRow row);
} }

View File

@ -16,129 +16,129 @@ import java.nio.charset.Charset;
*/ */
public class CsvUtil { public class CsvUtil {
//----------------------------------------------------------------------------------------------------------- Reader //----------------------------------------------------------------------------------------------------------- Reader
/** /**
* 获取CSV读取器调用此方法创建的Reader须自行指定读取的资源 * 获取CSV读取器调用此方法创建的Reader须自行指定读取的资源
* *
* @param config 配置, 允许为空. * @param config 配置, 允许为空.
* @return {@link CsvReader} * @return {@link CsvReader}
*/ */
public static CsvReader getReader(CsvReadConfig config) { public static CsvReader getReader(CsvReadConfig config) {
return new CsvReader(config); return new CsvReader(config);
} }
/** /**
* 获取CSV读取器调用此方法创建的Reader须自行指定读取的资源 * 获取CSV读取器调用此方法创建的Reader须自行指定读取的资源
* *
* @return {@link CsvReader} * @return {@link CsvReader}
*/ */
public static CsvReader getReader() { public static CsvReader getReader() {
return new CsvReader(); return new CsvReader();
} }
/** /**
* 获取CSV读取器 * 获取CSV读取器
* *
* @param reader {@link Reader} * @param reader {@link Reader}
* @param config 配置, {@code null}表示默认配置 * @param config 配置, {@code null}表示默认配置
* @return {@link CsvReader} * @return {@link CsvReader}
* @since 5.7.14 * @since 5.7.14
*/ */
public static CsvReader getReader(Reader reader, CsvReadConfig config) { public static CsvReader getReader(Reader reader, CsvReadConfig config) {
return new CsvReader(reader, config); return new CsvReader(reader, config);
} }
/** /**
* 获取CSV读取器 * 获取CSV读取器
* *
* @param reader {@link Reader} * @param reader {@link Reader}
* @return {@link CsvReader} * @return {@link CsvReader}
* @since 5.7.14 * @since 5.7.14
*/ */
public static CsvReader getReader(Reader reader) { public static CsvReader getReader(Reader reader) {
return getReader(reader, null); return getReader(reader, null);
} }
//----------------------------------------------------------------------------------------------------------- Writer //----------------------------------------------------------------------------------------------------------- Writer
/** /**
* 获取CSV生成器写出器使用默认配置覆盖已有文件如果存在 * 获取CSV生成器写出器使用默认配置覆盖已有文件如果存在
* *
* @param filePath File CSV文件路径 * @param filePath File CSV文件路径
* @param charset 编码 * @param charset 编码
* @return {@link CsvWriter} * @return {@link CsvWriter}
*/ */
public static CsvWriter getWriter(String filePath, Charset charset) { public static CsvWriter getWriter(String filePath, Charset charset) {
return new CsvWriter(filePath, charset); return new CsvWriter(filePath, charset);
} }
/** /**
* 获取CSV生成器写出器使用默认配置覆盖已有文件如果存在 * 获取CSV生成器写出器使用默认配置覆盖已有文件如果存在
* *
* @param file File CSV文件 * @param file File CSV文件
* @param charset 编码 * @param charset 编码
* @return {@link CsvWriter} * @return {@link CsvWriter}
*/ */
public static CsvWriter getWriter(File file, Charset charset) { public static CsvWriter getWriter(File file, Charset charset) {
return new CsvWriter(file, charset); return new CsvWriter(file, charset);
} }
/** /**
* 获取CSV生成器写出器使用默认配置 * 获取CSV生成器写出器使用默认配置
* *
* @param filePath File CSV文件路径 * @param filePath File CSV文件路径
* @param charset 编码 * @param charset 编码
* @param isAppend 是否追加 * @param isAppend 是否追加
* @return {@link CsvWriter} * @return {@link CsvWriter}
*/ */
public static CsvWriter getWriter(String filePath, Charset charset, boolean isAppend) { public static CsvWriter getWriter(String filePath, Charset charset, boolean isAppend) {
return new CsvWriter(filePath, charset, isAppend); return new CsvWriter(filePath, charset, isAppend);
} }
/** /**
* 获取CSV生成器写出器使用默认配置 * 获取CSV生成器写出器使用默认配置
* *
* @param file File CSV文件 * @param file File CSV文件
* @param charset 编码 * @param charset 编码
* @param isAppend 是否追加 * @param isAppend 是否追加
* @return {@link CsvWriter} * @return {@link CsvWriter}
*/ */
public static CsvWriter getWriter(File file, Charset charset, boolean isAppend) { public static CsvWriter getWriter(File file, Charset charset, boolean isAppend) {
return new CsvWriter(file, charset, isAppend); return new CsvWriter(file, charset, isAppend);
} }
/** /**
* 获取CSV生成器写出器 * 获取CSV生成器写出器
* *
* @param file File CSV文件 * @param file File CSV文件
* @param charset 编码 * @param charset 编码
* @param isAppend 是否追加 * @param isAppend 是否追加
* @param config 写出配置null则使用默认配置 * @param config 写出配置null则使用默认配置
* @return {@link CsvWriter} * @return {@link CsvWriter}
*/ */
public static CsvWriter getWriter(File file, Charset charset, boolean isAppend, CsvWriteConfig config) { public static CsvWriter getWriter(File file, Charset charset, boolean isAppend, CsvWriteConfig config) {
return new CsvWriter(file, charset, isAppend, config); return new CsvWriter(file, charset, isAppend, config);
} }
/** /**
* 获取CSV生成器写出器 * 获取CSV生成器写出器
* *
* @param writer Writer * @param writer Writer
* @return {@link CsvWriter} * @return {@link CsvWriter}
*/ */
public static CsvWriter getWriter(Writer writer) { public static CsvWriter getWriter(Writer writer) {
return new CsvWriter(writer); return new CsvWriter(writer);
} }
/** /**
* 获取CSV生成器写出器 * 获取CSV生成器写出器
* *
* @param writer Writer * @param writer Writer
* @param config 写出配置null则使用默认配置 * @param config 写出配置null则使用默认配置
* @return {@link CsvWriter} * @return {@link CsvWriter}
*/ */
public static CsvWriter getWriter(Writer writer, CsvWriteConfig config) { public static CsvWriter getWriter(Writer writer, CsvWriteConfig config) {
return new CsvWriter(writer, config); return new CsvWriter(writer, config);
} }
} }