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 cache; /** * Currently active cache */ private Map> cacheMap = new HashMap>(); /** * Count how many times get */ private int getCount = 0; /** * Count active cache * */ private LinkedList countList = new LinkedList(); /** * Count the last {@link #CHECK_INTERVAL} used */ private Set lastCheckIntervalUsedSet = new HashSet(); /** * 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 tempCacheMap = new HashMap(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>> iterator = cacheMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry> 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 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(); } }