package com.brucezee.jspider.berkeley; import com.sleepycat.bind.EntryBinding; import com.sleepycat.bind.serial.SerialBinding; import com.sleepycat.bind.serial.StoredClassCatalog; import com.sleepycat.bind.tuple.TupleBinding; import com.sleepycat.collections.StoredMap; import com.sleepycat.collections.StoredSortedMap; import com.sleepycat.je.*; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.util.AbstractQueue; import java.util.Iterator; import java.util.concurrent.atomic.AtomicLong; /** * 持久化队列,基于BDB实现,也继承Queue,以及可以序列化.但不等同于Queue的时,不再使用后需要关闭 * 相比一般的内存Queue,插入和获取值需要多消耗一定的时间 * 这里为什么是继承AbstractQueue而不是实现Queue接口,是因为只要实现offer,peek,poll几个方法即可, * 其他如remove,addAll,AbstractQueue会基于这几个方法去实现 * Created by brucezee on 2017/1/13. */ public class BdbPersistentQueue<E extends Serializable> extends AbstractQueue<E> implements Serializable { private static Logger logger = LoggerFactory.getLogger(BdbPersistentQueue.class); private transient BdbEnvironment dbEnv; // 数据库环境,无需序列化 private transient Database queueDb; // 数据库,用于保存值,使得支持队列持久化,无需序列化 private transient StoredMap<Long, E> queueMap; // 持久化Map,Key为指针位置,Value为值,无需序列化 private transient String dbDir; // 数据库所在目录 private transient String dbName; // 数据库名字 private AtomicLong headIndex; // 头部指针 private AtomicLong tailIndex; // 尾部指针 private transient E peekItem = null; // 当前获取的值 /** * 构造函数,传入BDB数据库 * @param db 数据库 * @param valueClass 值的类型 * @param classCatalog StoredClassCatalog */ public BdbPersistentQueue(Database db, Class<E> valueClass, StoredClassCatalog classCatalog) { this.queueDb = db; this.dbName = db.getDatabaseName(); headIndex = new AtomicLong(0); tailIndex = new AtomicLong(0); bindDatabase(queueDb, valueClass, classCatalog); } /** * 构造函数,传入BDB数据库位置和名字,自己创建数据库 * @param dbDir 数据文件路径 * @param dbName 数据库名 * @param valueClass 值类型 */ public BdbPersistentQueue(String dbDir, String dbName, Class<E> valueClass) { headIndex = new AtomicLong(0); tailIndex = new AtomicLong(0); this.dbDir = dbDir; this.dbName = dbName; createAndBindDatabase(dbDir, dbName, valueClass); } /** * 绑定数据库 * @param db 数据库 * @param valueClass 值类型 * @param classCatalog StoredClassCatalog */ public void bindDatabase(Database db, Class<E> valueClass, StoredClassCatalog classCatalog) { EntryBinding<E> valueBinding = TupleBinding.getPrimitiveBinding(valueClass); if(valueBinding == null) { valueBinding = new SerialBinding<E>(classCatalog, valueClass); // 序列化绑定 } queueDb = db; queueMap = new StoredSortedMap<Long,E>( db, // db TupleBinding.getPrimitiveBinding(Long.class), //Key valueBinding, // Value true); // allow write } /** * 创建以及绑定数据库 * @param dbDir 数据库文件路径 * @param dbName 数据库名 * @param valueClass 值类型 * @throws DatabaseException 数据库异常 * @throws IllegalArgumentException 参数异常 */ private void createAndBindDatabase(String dbDir, String dbName,Class<E> valueClass) throws DatabaseException, IllegalArgumentException{ File envFile = null; EnvironmentConfig envConfig = null; DatabaseConfig dbConfig = null; Database db = null; try { // 数据库位置 envFile = new File(dbDir); if (!envFile.exists()) { envFile.mkdirs(); } // 数据库环境配置 envConfig = new EnvironmentConfig(); envConfig.setAllowCreate(true); envConfig.setTransactional(false); // 数据库配置 dbConfig = new DatabaseConfig(); dbConfig.setAllowCreate(true); dbConfig.setTransactional(false); dbConfig.setDeferredWrite(true); // 创建环境 dbEnv = new BdbEnvironment(envFile, envConfig); // 打开数据库 db = dbEnv.openDatabase(null, dbName, dbConfig); // 绑定数据库 bindDatabase(db, valueClass, dbEnv.getClassCatalog()); } catch (DatabaseNotFoundException e) { throw e; } catch (DatabaseExistsException e) { throw e; } catch (DatabaseException e) { throw e; } catch (IllegalArgumentException e) { throw e; } } @Override public Iterator<E> iterator() { return queueMap.values().iterator(); } @Override public int size() { synchronized(tailIndex) { synchronized(headIndex) { return (int) (tailIndex.get() - headIndex.get()); } } } @Override public boolean offer(E e) { synchronized(tailIndex) { queueMap.put(tailIndex.getAndIncrement(), e); // 从尾部插入 } return true; } @Override public E peek() { synchronized(headIndex) { if(peekItem != null) { return peekItem; } E headItem=null; while(headItem == null && headIndex.get() < tailIndex.get()) { // 没有超出范围 headItem = queueMap.get(headIndex.get()); if(headItem != null) { peekItem = headItem; continue; } headIndex.incrementAndGet(); // 头部指针后移 } return headItem; } } @Override public E poll() { synchronized(headIndex) { E headItem = peek(); if(headItem != null) { queueMap.remove(headIndex.getAndIncrement()); peekItem = null; return headItem; } } return null; } /** * 关闭所用的BDB数据库但不关闭数据库环境 */ public void close() { try { if(queueDb != null) { queueDb.sync(); queueDb.close(); } } catch (DatabaseException e) { logger.error(e.getMessage(), e); } catch (UnsupportedOperationException e) { logger.error(e.getMessage(), e); } } /** * 清空数据库,并且删掉数据库所在目录,慎用.如果想保留数据,请调用close() */ @Override public void clear() { try { close(); if(dbEnv != null&&queueDb != null) { dbEnv.removeDatabase(null, dbName == null ? queueDb.getDatabaseName() : dbName); dbEnv.close(); } } catch (DatabaseNotFoundException e) { logger.error(e.getMessage(), e); } catch (DatabaseException e) { logger.error(e.getMessage(), e); } finally{ try { if(this.dbDir != null) { FileUtils.deleteDirectory(new File(this.dbDir)); } } catch (IOException e) { logger.error(e.getMessage(), e); } } } }