切分数据
按指定的 pieces 段数, 将数据切分, 并送入队列.
private final LinkedBlockingQueue<List<User>> usersQueue = new LinkedBlockingQueue<>();
private void feedSubUsers(ArrayList<User> users, int pieces) throws InterruptedException {
int size = users.size();
int step = Math.floorDiv(size, pieces);
int topIndex = 0;
int endIndex = 0;
int track = 0;
// the loop condition of (endIndex < size) will pick up the rest elements
while (track < pieces || endIndex < size) {
if (endIndex == 0) {
endIndex = topIndex + step;
} else if ((size - endIndex) > step) {
topIndex = endIndex;
endIndex = endIndex + step;
} else if ((size - endIndex) > 0 && (size - endIndex) <= step) {
// size - endIndex <= step
topIndex = endIndex;
endIndex = size;
}
usersQueue.put(users.subList(topIndex, endIndex));
track++;
}
}
队列与线程
我们使用 LinkedBlockingList 作为任务队列, 因为数据集切分是动态的, 所以用 linked. 切分后将数据集作为一个任务添加进队列, 等待全部切分完毕后才开始线程池提交任务, 并开始执行数据集的表写入操作. 这里的队列还只是作为任务队列, 并不是完全动态的 cons-prod 模式. 因为任务已经提前生产出来, 且已完备. 然后线程池中的线程根据队列中的任务数量来进行消费. ( 由于我们用的是 FixedThreadPool, 所以如果任务数超过线程数, 不会增多线程, 当有空闲线程时就会去消费, 如果任务数小于线程数, 那么每个线程消费一个任务)
多页同时写入
- 使用 EasyExcel 多页写入时, 要对
excelWriter
进行同步, 防止写入错误. - 这里使用 Thread-No 线程号来作为 sheet 分页的名字.
- 如果固定线程池的线程数少于分页数的话, 会将新数据以 append 形式同页写入, 而不会覆盖.
- 整体代码中还可以优化的地方.
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
public void operateOnExcel(ArrayList<User> users) throws InterruptedException, ExecutionException {
ExcelWriter excelWriter = EasyExcel.write("/Users/wong/Desktop/helloworld.xlsx", User.class).build();
// separating List and feed sublist to queue
try {
feedSubUsers(users, 20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ArrayList<Future<Boolean>> futures = new ArrayList<>();
// get the sublist from the queue and wrap it into task
Callable<Boolean> task = () -> {
WriteSheet sheet = EasyExcel.writerSheet(String.valueOf(Thread.currentThread().getId())).build();
synchronized (excelWriter) {
excelWriter.write(usersQueue.poll(), sheet);
}
return true;
};
Instant begin = Instant.now();
// executing tasks
for (int i = 0; i < usersQueue.size(); i++) {
Future<Boolean> submit = executorService.submit(task);
futures.add(submit);
}
for (var future : futures) {
future.get();
}
Instant end = Instant.now();
// close workbook io
excelWriter.finish();
executorService.shutdown();
System.out.println("Writing WorkBook Time Consuming: " + Duration.between(begin, end).toMillis() + " MS");
}
测试结果
/**
* Hello world!
*/
public class App {
public static void main(String[] args) throws ExecutionException, InterruptedException, IOException {
Operators operators = new Operators();
ArrayList<User> users = operators.yieldUser(1000000);
// operators.testSheet(users);
operators.operateOnExcel(users);
}
}
整体来说, 生成百万个Users花费5秒, 写表花了7秒. 两个阶段是 synchronous. 所以总共花了12秒. 如果用完全形态的 cons-prod 模式, 还可以更快…单论写表操作, 貌似还可以使用其他写策略来提高速度.