当前位置: 首页 > 工具软件 > SuperCSV > 使用案例 >

【SuperCsv】java.lang.StringIndexOutOfBoundsException: null

毛博
2023-12-01

本人使用SuperCsv进行Csv文件导出时,在进行表头写入时发生了以下异常。

java.lang.StringIndexOutOfBoundsException: null
	at java.lang.AbstractStringBuilder.delete(AbstractStringBuilder.java:760)
	at java.lang.StringBuilder.delete(StringBuilder.java:244)
	at org.supercsv.encoder.DefaultCsvEncoder.encode(DefaultCsvEncoder.java:42)
	at org.supercsv.io.AbstractCsvWriter.escapeString(AbstractCsvWriter.java:102)
	at org.supercsv.io.AbstractCsvWriter.writeRow(AbstractCsvWriter.java:196)
	at org.supercsv.io.AbstractCsvWriter.writeHeader(AbstractCsvWriter.java:228)

我之前是这么用的

...
ICsvListWriter csvWrite = new CsvListWriter(writer,CsvPreference.STANDARD_PREFERENCE)
...
csvWrite.writeHeader(xxx); //在此处抛了上述异常
...

经过分析发现,异常的抛出点是在 AbstractStringBuilder.delete()方法中,看到这个StringBuilder我们应该一下就能想到是它线程不安全的,那么我们继续往下看。

在声明CsvWriter对象时,我们传入的CsvPreference.STANDARD_PREFERENCE 其实是一个定义在CsvPreference中的一个final类型的静态常量,那么多线程环境下使用CsvWriter导出Csv文件时其实操作的都是同一个CsvPreference对象。

 //这是CsvPreference类的代码
 /* 
 * @author Kasper B. Graversen
 * @author James Bassett
 */
public final class CsvPreference {
	
	/**
	 * Ready to use configuration that should cover 99% of all usages.
	 */
	public static final CsvPreference STANDARD_PREFERENCE = new CsvPreference.Builder('"', ',', "\r\n").build();
	
	/**
	 * Ready to use configuration for Windows Excel exported CSV files.
	 */
	public static final CsvPreference EXCEL_PREFERENCE = new CsvPreference.Builder('"', ',', "\n").build();
	
	/**
	 * Ready to use configuration for north European excel CSV files (columns are separated by ";" instead of ",")
	 */
	public static final CsvPreference EXCEL_NORTH_EUROPE_PREFERENCE = new CsvPreference.Builder('"', ';', "\n").build();
	...
	}

这样一看似乎就有点眉目了,再看一下CsvWriter的构造方法

public CsvListWriter(final Writer writer, final CsvPreference preference) {
		super(writer, preference);//调用了父类的构造方法
	}
...
//看一下它的父抽象类的构造方法
public AbstractCsvWriter(final Writer writer, final CsvPreference preference) {
		if( writer == null ) {
			throw new NullPointerException("writer should not be null");
		} else if( preference == null ) {
			throw new NullPointerException("preference should not be null");
		}
		
		this.writer = new BufferedWriter(writer);
		this.preference = preference;
		this.encoder = preference.getEncoder();
	}	

CsvWriter 写入表头时会调用 encoder.encode()方法,那么CsvWriter使用的encoder其实就是定义在CsvPreference 内部的encoder,上面说过了多线程下操作的都是同一个CsvPreference ,即操作的是同一个encoder

我们继续来看一下这个encode方法

public class DefaultCsvEncoder implements CsvEncoder {
	
	private final StringBuilder currentColumn = new StringBuilder();
	
	/**
	 * Constructs a new <tt>DefaultCsvEncoder</tt>.
	 */
	public DefaultCsvEncoder() {
	}
	
	/**
	 * {@inheritDoc}
	 */
	public String encode(final String input, final CsvContext context, final CsvPreference preference) {
		
		currentColumn.delete(0, currentColumn.length()); // reusing builder object
		
		final int delimiter = preference.getDelimiterChar();
		final char quote = (char) preference.getQuoteChar();
		final String eolSymbols = preference.getEndOfLineSymbols();
		final int lastCharIndex = input.length() - 1;
		
		boolean quotesRequiredForSpecialChar = false;
		
		boolean skipNewline = false;
		...
		}
	}	

encode方法的一开始就执行了 currentColumn.delete(),我们可以看到这个currentColumn 对象实际上就是定义在encoder内部的一个final类型的StringBuilder对象,也正是因为如此,在多线程的情况下,使用这种方法导出Csv文件是有线程安全问题的

解决方案就是,在创建CsvWriter时,传入我们自己构造出来的CsvPreference 对象即可,例如:

ICsvListWriter csvWrite = new ICsvListWriter (writer,new CsvPreference.Builder(CsvPreference.STANDARD_PREFERENCE).useEncoder(new DefaultCsvEncoder()).build())

好了,问题解决

 类似资料:

相关阅读

相关文章

相关问答