|
|
|
package com.alibaba.excel.cache;
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.LinkedList;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Set;
|
|
|
|
import java.util.UUID;
|
|
|
|
|
|
|
|
import org.ehcache.CacheManager;
|
|
|
|
import org.ehcache.PersistentCacheManager;
|
|
|
|
import org.ehcache.config.builders.CacheConfigurationBuilder;
|
|
|
|
import org.ehcache.config.builders.CacheManagerBuilder;
|
|
|
|
import org.ehcache.config.builders.ResourcePoolsBuilder;
|
|
|
|
import org.ehcache.config.units.MemoryUnit;
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
|
|
import com.alibaba.excel.context.AnalysisContext;
|
|
|
|
import com.alibaba.excel.util.FileUtils;
|
|
|
|
import com.alibaba.excel.util.StringUtils;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Default cache
|
|
|
|
*
|
|
|
|
* @author Jiaju Zhuang
|
|
|
|
*/
|
|
|
|
public class Ehcache implements ReadCache {
|
|
|
|
|
|
|
|
private static final Logger LOGGER = LoggerFactory.getLogger(Ehcache.class);
|
|
|
|
|
|
|
|
private static final int BATCH_COUNT = 1000;
|
|
|
|
private static final int CHECK_INTERVAL = 1000;
|
|
|
|
private static final String CACHE = "cache";
|
|
|
|
private static final String DATA_SEPARATOR = "@";
|
|
|
|
private static final String KEY_VALUE_SEPARATOR = "!";
|
|
|
|
private static final String SPECIAL_SEPARATOR = "&";
|
|
|
|
private static final String ESCAPED_DATA_SEPARATOR = "&d;";
|
|
|
|
private static final String ESCAPED_KEY_VALUE_SEPARATOR = "&kv;";
|
|
|
|
private static final String ESCAPED_SPECIAL_SEPARATOR = "&s;";
|
|
|
|
|
|
|
|
private static final int DEBUG_WRITE_SIZE = 100 * 10000;
|
|
|
|
private static final int DEBUG_CACHE_MISS_SIZE = 1000;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Key index
|
|
|
|
*/
|
|
|
|
private int index = 0;
|
|
|
|
/**
|
|
|
|
* Maximum number of cache activation
|
|
|
|
*/
|
|
|
|
private int maxCacheActivate = 100;
|
|
|
|
private StringBuilder data = new StringBuilder();
|
|
|
|
private CacheManager cacheManager;
|
|
|
|
/**
|
|
|
|
* Bulk storage data
|
|
|
|
*/
|
|
|
|
private org.ehcache.Cache<Integer, String> cache;
|
|
|
|
/**
|
|
|
|
* Currently active cache
|
|
|
|
*/
|
|
|
|
private Map<Integer, Map<Integer, String>> cacheMap = new HashMap<Integer, Map<Integer, String>>();
|
|
|
|
/**
|
|
|
|
* Count how many times get
|
|
|
|
*/
|
|
|
|
private int getCount = 0;
|
|
|
|
/**
|
|
|
|
* Count active cache
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
private LinkedList<Integer> countList = new LinkedList<Integer>();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Count the last {@link #CHECK_INTERVAL} used
|
|
|
|
*/
|
|
|
|
private Set<Integer> lastCheckIntervalUsedSet = new HashSet<Integer>();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Count the number of cache misses
|
|
|
|
*/
|
|
|
|
private int cacheMiss = 0;
|
|
|
|
|
|
|
|
public Ehcache() {}
|
|
|
|
|
|
|
|
public Ehcache(int maxCacheActivate) {
|
|
|
|
this.maxCacheActivate = maxCacheActivate;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void init(AnalysisContext analysisContext) {
|
|
|
|
File readTempFile = analysisContext.readWorkbookHolder().getTempFile();
|
|
|
|
if (readTempFile == null) {
|
|
|
|
readTempFile = FileUtils.createCacheTmpFile();
|
|
|
|
analysisContext.readWorkbookHolder().setTempFile(readTempFile);
|
|
|
|
}
|
|
|
|
File cacheFile = new File(readTempFile.getPath(), UUID.randomUUID().toString());
|
|
|
|
PersistentCacheManager persistentCacheManager =
|
|
|
|
CacheManagerBuilder.newCacheManagerBuilder().with(CacheManagerBuilder.persistence(cacheFile))
|
|
|
|
.withCache(CACHE, CacheConfigurationBuilder.newCacheConfigurationBuilder(Integer.class, String.class,
|
|
|
|
ResourcePoolsBuilder.newResourcePoolsBuilder().disk(10, MemoryUnit.GB)))
|
|
|
|
.build(true);
|
|
|
|
cacheManager = persistentCacheManager;
|
|
|
|
cache = persistentCacheManager.getCache(CACHE, Integer.class, String.class);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void put(String value) {
|
|
|
|
data.append(index).append(KEY_VALUE_SEPARATOR).append(escape(value)).append(DATA_SEPARATOR);
|
|
|
|
if ((index + 1) % BATCH_COUNT == 0) {
|
|
|
|
cache.put(index / BATCH_COUNT, data.toString());
|
|
|
|
data = new StringBuilder();
|
|
|
|
}
|
|
|
|
index++;
|
|
|
|
if (LOGGER.isDebugEnabled()) {
|
|
|
|
if (index % DEBUG_WRITE_SIZE == 0) {
|
|
|
|
LOGGER.debug("Already put :{}", index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private String escape(String str) {
|
|
|
|
if (StringUtils.isEmpty(str)) {
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
str = str.replaceAll(SPECIAL_SEPARATOR, ESCAPED_SPECIAL_SEPARATOR);
|
|
|
|
str = str.replaceAll(DATA_SEPARATOR, ESCAPED_DATA_SEPARATOR);
|
|
|
|
str = str.replaceAll(KEY_VALUE_SEPARATOR, ESCAPED_KEY_VALUE_SEPARATOR);
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
private String unescape(String str) {
|
|
|
|
if (StringUtils.isEmpty(str)) {
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
str = str.replaceAll(ESCAPED_KEY_VALUE_SEPARATOR, KEY_VALUE_SEPARATOR);
|
|
|
|
str = str.replaceAll(ESCAPED_DATA_SEPARATOR, DATA_SEPARATOR);
|
|
|
|
str = str.replaceAll(ESCAPED_SPECIAL_SEPARATOR, SPECIAL_SEPARATOR);
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String get(Integer key) {
|
|
|
|
if (key == null || key < 0) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
getCount++;
|
|
|
|
int route = key / BATCH_COUNT;
|
|
|
|
if (cacheMap.containsKey(route)) {
|
|
|
|
lastCheckIntervalUsedSet.add(route);
|
|
|
|
String value = cacheMap.get(route).get(key);
|
|
|
|
checkClear();
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
Map<Integer, String> tempCacheMap = new HashMap<Integer, String>(BATCH_COUNT / 3 * 4 + 1);
|
|
|
|
String batchData = cache.get(route);
|
|
|
|
String[] dataStrings = batchData.split(DATA_SEPARATOR);
|
|
|
|
for (String dataString : dataStrings) {
|
|
|
|
String[] keyValue = dataString.split(KEY_VALUE_SEPARATOR);
|
|
|
|
tempCacheMap.put(Integer.valueOf(keyValue[0]), unescape(keyValue[1]));
|
|
|
|
}
|
|
|
|
countList.add(route);
|
|
|
|
cacheMap.put(route, tempCacheMap);
|
|
|
|
if (LOGGER.isDebugEnabled()) {
|
|
|
|
if (cacheMiss++ % DEBUG_CACHE_MISS_SIZE == 0) {
|
|
|
|
LOGGER.debug("Cache misses count:{}", cacheMiss);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lastCheckIntervalUsedSet.add(route);
|
|
|
|
String value = tempCacheMap.get(key);
|
|
|
|
checkClear();
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void checkClear() {
|
|
|
|
if (countList.size() > maxCacheActivate) {
|
|
|
|
Integer route = countList.getFirst();
|
|
|
|
countList.removeFirst();
|
|
|
|
cacheMap.remove(route);
|
|
|
|
}
|
|
|
|
if (getCount++ % CHECK_INTERVAL != 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Iterator<Map.Entry<Integer, Map<Integer, String>>> iterator = cacheMap.entrySet().iterator();
|
|
|
|
while (iterator.hasNext()) {
|
|
|
|
Map.Entry<Integer, Map<Integer, String>> entry = iterator.next();
|
|
|
|
if (lastCheckIntervalUsedSet.contains(entry.getKey())) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Last 'CHECK_INTERVAL' not use
|
|
|
|
iterator.remove();
|
|
|
|
if (LOGGER.isDebugEnabled()) {
|
|
|
|
LOGGER.debug("Cache remove because {} times unused.", CHECK_INTERVAL);
|
|
|
|
}
|
|
|
|
Iterator<Integer> countIterator = countList.iterator();
|
|
|
|
while (countIterator.hasNext()) {
|
|
|
|
Integer route = countIterator.next();
|
|
|
|
if (route.equals(entry.getKey())) {
|
|
|
|
countIterator.remove();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lastCheckIntervalUsedSet.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void putFinished() {
|
|
|
|
if (StringUtils.isEmpty(data.toString())) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
cache.put(index / BATCH_COUNT, data.toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void destroy() {
|
|
|
|
cacheManager.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|