在上篇文章01 初识缓存-了解缓存
中简单了介绍了下缓存的历程以及几种常见的技术进行简单介绍,本着学习的目的本节针对GuavaCache进行一个专题介绍,可能理解有限欢迎指正。
一 简介
Guava Cache,是Google 出品的 Java 核心增强库的缓存部分,一种非常优秀本地缓存解决方案,提供了基于容量,时间和引用的缓存回收方式。guava cache可以按照多种策略来清理存储在其中的缓存值且保持很高的并发读写性能。基于容量的方式内部实现采用LRU算法,基于引用回收很好的利用了Java虚拟机的垃圾回收机制。其中的缓存构造器CacheBuilder采用构建者模式提供了设置好各种参数的缓存对象,缓存核心类LocalCache里面的内部类Segment与jdk1.7及以前的ConcurrentHashMap非常相似,都继承于ReetrantLock,还有六个队列,以实现丰富的本地缓存方案。
二 GuavaCache使用
2.1 缓存加载器 CacheLoader
CacheLoader是GuavaCache提供的一种加载缓存的机制,可以通过指定的键值进行计算/加载需要进行匹配的缓存数据。GuavaCache虽然提供了这种方式,但是它并不要求编程者一定需要遵循它这种方案,也可以根据自定的实际需求来定制自己加载逻辑。根据GuavaCache提出的如果想复写其缓存的加载方式,但是仍要保留“get-if-absent-compute”语义,GuavaCache在进行缓存获取的时候提供了一种解决方案:可以在调用get方法时传入一个Callable实例,来达到目的。缓存的对象可以通过Cache.put直接插入,但是自动加载是首选,因为自动加载可以更加容易的判断所有缓存信息的一致性。
Guava Cache针对开发者提供了非常友好的编程方式,使用非常简单:
下面例子分别从不同的角度:
From a CacheLoader
LoadingCache 缓存是通过一个CacheLoader来构建缓存。创建一个CacheLoader仅需要实现V load(K key) throws Exception方法即可。通过方法get(K)可以对LoadingCache进行查询。该方法要不返回已缓存的值,要不通过CacheLoader来自动加载相应的值到缓存中。
From a Callable
所有类型的Guava Cache,不论是否会自动加载,都支持get(K, Callable(V)) 方法。当给定键的缓存值已存在时则直接返回,否则通过指定的Callable方法进行计算并将值存放到缓存中。直到加载完成时,相应的缓存才会被更改。该方法简单实现了"if cached, return; otherwise create, cache and return"("如果有缓存则返回;否则运算、缓存、然后返回")语义。
Cache.put
使用cache.put(key, value)方法可以直接向缓存中插入值,这会直接覆盖掉给定键之前映射的值。使用Cache.asMap()视图提供的任何方法也能修改缓存。但请注意,asMap视图的任何方法都不能保证缓存项被原子地加载到缓存中。进一步说,asMap视图的原子运算在Guava Cache的原子加载范畴之外,所以相比于Cache.asMap().putIfAbsent(K,V),Cache.get(K, Callable) 应该总是优先使用。
/**
* @author mzw
* @version V1.0.0
* @description
* @data 2019-05-29 14:53
* @see
**/
public class GuavaCache {
private static final Logger LOGGER = LoggerFactory.getLogger(GuavaCache.class);
public static void main(String[] args) throws ExecutionException {
//构建缓存加载器
CacheLoader loader = new CacheLoader() {
@Override
public String load(String key) {
LOGGER.info(key + " is loaded from a cacheLoader!");
return key;
}
};
//构建移除监听器:非必要
RemovalListener removalListener =
removal -> LOGGER.info("[" + removal.getKey() + ":" + removal.getValue() + "] is evicted!");
//构建缓存
LoadingCache cache = CacheBuilder.newBuilder()
.maximumSize(4)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(removalListener)
.build(loader);
//直接放入数据
for (int i = 0; i < 3; i++) {
cache.put("key" + i, "value" + i);
}
//获取数据
LOGGER.info("key2 > " + cache.getIfPresent("key2"));
LOGGER.info("key > " + cache.getIfPresent("key"));
//From a CacheLoade
LOGGER.info("key > " + cache.get("key"));
//From a Callable
LOGGER.info("key > " + cache.get("k1", () -> "dd"));
}
}
输出结果
17:36:12.084 [main] INFO com.maozw.quartz.cache.GuavaCache - key2 > value2
17:36:12.086 [main] INFO com.maozw.quartz.cache.GuavaCache - key > null
17:36:12.090 [main] INFO com.maozw.quartz.cache.GuavaCache - key is loaded from a cacheLoader!
17:36:12.092 [main] INFO com.maozw.quartz.cache.GuavaCache - key > key
17:36:12.094 [main] INFO com.maozw.quartz.cache.GuavaCache - [key0:value0] is evicted!
17:36:12.094 [main] INFO com.maozw.quartz.cache.GuavaCache - k1 > dd
2.1 缓存回收
guava中数据的移除分为被动移除和主动移除两种, 被动移除数据的方式,简介中曾说过guava cache可以按照多种策略来清理存储在其中的缓存值且保持很高的并发读写性能。基于容量的方式内部实现采用LRU算法,基于引用回收很好的利用了Java虚拟机的垃圾回收机制。Guava Cache提供了三种基本的缓存回收方式:
基于缓存容量大小的移除
当缓存中的元素数量超过指定值时,会把不常用的键值对从cache中移除。
LoadingCache cache = CacheBuilder.newBuilder()
.maximumSize(4)//缓存容量大小
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(removalListener)
.build(loader);
基于缓存时间的移除
guava提供了两个基于时间移除的方法
expireAfterAccess(long, TimeUnit):这个方法是根据某个键值对最后一次访问之后多少时间后移除。缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。
expireAfterWrite(long, TimeUnit):这个方法是根据某个键值对被创建或值被替换后多少时间移除。缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的.
LoadingCache cache = CacheBuilder.newBuilder()
.maximumSize(4)//缓存容量大小
.expireAfterWrite(10, TimeUnit.MINUTES)//缓存时间
.removalListener(removalListener)
.build(loader);
基于引用回收(Reference-based Eviction)
这种移除方式主要是基于java的垃圾回收机制,根据键或者值的引用关系决定移除
CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。
CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。
CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。
主动移除数据方式
主动移除有三种方法:
单独缓存项移除 Cache.invalidate(key)
批量缓存项移除 Cache.invalidateAll(keys)
清除所有缓存项 Cache.invalidateAll()
移除监听器
通过CacheBuilder.removalListener(RemovalListener),你可以声明一个监听器,以便缓存项被移除时做一些额外操作。缓存项被移除时,RemovalListener会获取移除通知[RemovalNotification],其中包含移除原因[RemovalCause]、键和值。如果需要改成异步形式,可以考虑使用RemovalListeners.asynchronous(RemovalListener, Executor) 。
缓存储移除的时机
Guava cache中通过CacheBuilder构建的缓存数据不会"自动"执行清理和回收工作,也不会在某个缓存项过期后马上清理,它会在读/写操作 后同步进行清理工作,只是读操作 时可能执行的机会会少少一些。 原因:如果自动清理缓存,就必须存在一个线程,这个线程会和用户操作竞争共享锁。此外,某些环境下线程创建可能受限制,这样CacheBuilder就不可用了。
同时用户可以自主的去控制清除时机,比如固定的时间间隔调用Cache.cleanUp()。
2.2 刷新
刷新和回收不太一样。正如LoadingCache.refresh(K)所声明,刷新表示为键加载新值,这个过程可以是异步的。在刷新操作进行时,缓存仍然可以向其他线程返回旧值, 而不像回收操作,读缓存的线程必须等待新值加载完成。如果刷新过程抛出异常,缓存将保留旧值,而异常会在记录到日志后被丢弃。
重载CacheLoader.reload(K, V)可以扩展刷新时的行为,这个方法允许开发者在计算新值时使用旧的值。 示例如下:
/**
* @author mzw
* @version V1.0.0
* @description
* @data 2019-05-29 14:53
* @see
**/
public class GuavaCacheDemo {
private static final Logger LOGGER = LoggerFactory.getLogger(GuavaCacheDemo.class);
public static void main(String[] args) throws ExecutionException, InterruptedException {
//构建移除监听器:非必要
RemovalListener removalListener =
removal -> LOGGER.info("[" + removal.getKey() + ":" + removal.getValue() + "] is evicted!");
//构建缓存
LoadingCache cache = CacheBuilder.newBuilder()
.maximumSize(5)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(removalListener)
.build(new CacheLoader() {
@Override
public String load(String s) {
return s + " -> load";
}
@Override
public ListenableFuture reload(String key, String oldValue) {
LOGGER.info(key + " > reload : " + oldValue);
ListenableFutureTask reloadTask = ListenableFutureTask.create(() -> oldValue + " -> reload");
Executors.newFixedThreadPool(1).submit(reloadTask);
return reloadTask;
}
});
//放入数据
for (int i = 0; i < 3; i++) {
cache.put("key" + i, "value" + i);
}
//获取数据
LOGGER.info("key > " + cache.getIfPresent("key"));
LOGGER.info("key1 > " + cache.getIfPresent("key1"));
cache.refresh("key1");
LOGGER.info("refresh : k > " + cache.get("key1"));
}
}
输出结果
19:32:01.069 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - key > null
19:32:01.071 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - key1 > value1
19:32:01.077 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - k > aa
19:32:01.077 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - key1 > reload : value1
19:32:01.085 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - [key1:value1] is evicted!
19:32:01.086 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - refresh : k > value1 -> reload
统计
简单介绍 CacheBuilder.recordStats():用来开启Guava Cache的统计功能。统计打开后,Cache.stats()方法会返回CacheS tats 对象以提供如下统计信息: hitRate():缓存命中率; averageLoadPenalty():加载新值的平均时间,单位为纳秒; evictionCount():缓存项被回收的总数,不包括显式清除。
三 Guava Cache 解读
在解读之前,先提一下Guava Cache的一些组件: Guava Cache组件中核心的类和接口列举如下: 接口:
Cache 顶级接口,定义get、put、invalidate等操作,以及获取缓存统计数据的方法等。
LoadingCache 继承自Cache,并另外提供了一些当get数据不存在时自动去load相关key(s)所对应的value(s)的契约(即接口中的抽象方法),具体实现见LoadingCache的具体实现类。
RemovalListener 监听器接口,在缓存被移除的时候用来做一些操作,与下面的RemovalNotification、RemovalCause配套使用。很明显这是个观察者模式的应用。
Weigher 权重的接口,提供int weigh(K key, V value)抽象方法,给缓存中的Entry赋予权重信息。
抽象类:
AbstractCache 本身是抽象类,实现自Cache接口,基本没做什么实际的工作,大多数方法的实现只是简单抛出UnsupportedOperationException.该抽象类提供了Cache接口的骨架,为了避免子类直接继承Cache接口时必须实现所有抽象方法,这种手法在其他地方也很常见,个人觉得都算得上是一种设计模式了。
AbstractLoadingCache 继承自AbstractCache并实现了LoadingCache接口,目的也是提供一个骨架,其中的某些方法提供了在get不到数据时会自动Load数据的契约。
CacheLoader 抽象类,最核心的方法就是load,封装load数据的操作,具体如何laod与该抽象类的具体子类有关,只需要重写laod方法,就可以在get不到数据时自动去load数据到缓存中。
ForwardingCache 装饰器模式的用法,所有对缓存的操作都委托给其他的实现了Cache接口的类,该抽象类中有一个抽象方法protected abstract Cache delegate();不难推测出来,其他的方法中均使用了该代理。即类似get(key){delegate().get(key)}
ForwardingLoadingCache 自行推断,不解释。
实现类:
CacheBuilder 建造者模式的应用,缓存构建器。构建缓存的入口,指定缓存配置参数并初始化本地缓存。通过该类来组装Cache,最后调用build方法来生成Cache的实例。
CacheBuilderSpec 用来构建CacheBuilder的实例,其中提供了一些配置参数,用这些配置的参数来通过CacheBuilder实例最终构建Cache实例。
CacheStats 缓存使用情况统计信息,比如命中多少次,缺失多少次等等。
LocalCache 本地缓存最核心的类 ,Cache接口实例的代理人,Cache接口提供的一些方法内部均采委托给LocalCache实例来实现,LocalCache的具体实现类似于ConcurrentHashMap,也采用了分段的方式。
CacheBuilder
CacheBuilder是缓存配置和构建入口。
在上面例子中我们构建缓存使用如下方式:现在解析CacheBuilder这个建造者的结构
LoadingCache cache = CacheBuilder.newBuilder()
.maximumSize(4)//缓存容量大小
.expireAfterWrite(10, TimeUnit.MINUTES)//缓存时间
.removalListener(removalListener)
.build(loader);
整个类内容太长,分段进行解说:
CacheBuilder 属性
DEFAULT_INITIAL_CAPACITY://缓存的默认初始化大小;
DEFAULT_CONCURRENCY_LEVEL:LocalCache默认并发数,用来评估Segment的个数;
DEFAULT_EXPIRATION_NANOS://默认的缓存过期时间;
initialCapacity://初始缓存大小
concurrencyLevel:用于计算有并发量
maximumSize:cache中最多能存放的缓存entry个数
expireAfterWriteNanos://缓存超时时间(起点:缓存被创建或被修改)
expireAfterAccessNanos://缓存超时时间(起点:缓存被创建或被修改或被访问)
public final class CacheBuilder {
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private static final int DEFAULT_CONCURRENCY_LEVEL = 4;
private static final int DEFAULT_EXPIRATION_NANOS = 0;
private static final int DEFAULT_REFRESH_NANOS = 0;
static final Supplier NULL_STATS_COUNTER = Suppliers.ofInstance(
new StatsCounter() {
...//省去无关代码
});
static final CacheStats EMPTY_STATS = new CacheStats(0, 0, 0, 0, 0, 0);
static final Supplier CACHE_STATS_COUNTER =
new Supplier() {
@Override
public StatsCounter get() {
return new SimpleStatsCounter();
}
};
...//省去无关代码
private static final Logger logger = Logger.getLogger(CacheBuilder.class.getName());
static final int UNSET_INT = -1;
boolean strictParsing = true;
int initialCapacity = UNSET_INT;
int concurrencyLevel = UNSET_INT;
long maximumSize = UNSET_INT;
long maximumWeight = UNSET_INT;
Weigher weigher;
Strength keyStrength;//键的引用类型(strong、weak、soft)
Strength valueStrength;//值的引用类型(strong、weak、soft)
long expireAfterWriteNanos = UNSET_INT;
long expireAfterAccessNanos = UNSET_INT;
long refreshNanos = UNSET_INT;
//key比较策略
Equivalence keyEquivalence;
Equivalence valueEquivalence;
RemovalListener removalListener;//元素被移除的监听器
Ticker ticker;
//状态计数器,默认为NULL_STATS_COUNTER,即不启动计数功能
Supplier statsCounterSupplier = NULL_STATS_COUNTER;
...
}
build方法
CacheBuilder构建缓存有两个方法:
public LoadingCache build(
CacheLoader loader) {
checkWeightWithWeigher();
return new LocalCache.LocalLoadingCache(this, loader);
}
public Cache build() {
checkWeightWithWeigher();
checkNonLoadingCache();
return new LocalCache.LocalManualCache(this);
}
至此我们就需要先了解一下LocalCache了,因为我们此时调用该类的构造方法LocalCache() 和实例方法LocalLoadingCache()/LocalManualCache()
LocalCache
LocalCache是guava cache的核心类。
LocalCache的数据结构与ConcurrentHashMap很相似,都由多个segment组成,且各segment相对独立,互不影响,所以能支持并行操作。每个segment由一个table和若干队列组成。缓存数据存储在table中,其类型为AtomicReferenceArray>,即一个数组,数组中每个元素是一个链表。两个队列分别是writeQueue和accessQueue,用来存储写入的数据和最近访问的数据,当数据过期,需要刷新整体缓存(见上述示例最后一次cache.getIfPresent("key5"))时,遍历队列,如果数据过期,则从table中删除。
LocalCache 数据结构
Segment[] segments;
Segment继承于ReetrantLock,减小锁粒度,提高并发效率。
AtomicReferenceArray> table;
类似于HasmMap中的table一样,相当于entry的容器。
ReferenceEntry referenceEntry;
基于引用的Entry,其实现类有弱引用Entry,强引用Entry等
ReferenceQueue keyReferenceQueue;
已经被GC,需要内部清理的键引用队列。
ReferenceQueue valueReferenceQueue;
已经被GC,需要内部清理的值引用队列。
Queue> recencyQueue;
记录升级可访问列表清单时的entries,当segment上达到临界值或发生写操作时该队列会被清空。
Queue> writeQueue;
按照写入时间进行排序的元素队列,写入一个元素时会把它加入到队列尾部。
Queue> accessQueue;
按照访问时间进行排序的元素队列,访问(包括写入)一个元素时会把它加入到队列尾部
LocalCache 构造器
构造器是通过CacheBuilder的方法对变量进行初始化。具体变量解说可参照CacheBuilder 属性
解说。
/**
* Creates a new, empty map with the specified strategy, initial capacity and concurrency level.
*/
LocalCache(
CacheBuilder builder, @Nullable CacheLoader loader) {
concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS);
//默认为强引用
keyStrength = builder.getKeyStrength();
valueStrength = builder.getValueStrength();
keyEquivalence = builder.getKeyEquivalence();
valueEquivalence = builder.getValueEquivalence();
maxWeight = builder.getMaximumWeight();
weigher = builder.getWeigher();
expireAfterAccessNanos = builder.getExpireAfterAccessNanos();
expireAfterWriteNanos = builder.getExpireAfterWriteNanos();
refreshNanos = builder.getRefreshNanos();
removalListener = builder.getRemovalListener();
removalNotificationQueue = (removalListener == NullListener.INSTANCE)
? LocalCache.>discardingQueue()
: new ConcurrentLinkedQueue>();
ticker = builder.getTicker(recordsTime());
entryFactory = EntryFactory.getFactory(keyStrength, usesAccessEntries(), usesWriteEntries());
globalStatsCounter = builder.getStatsCounterSupplier().get();
defaultLoader = loader;
int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY);
if (evictsBySize() && !customWeigher()) {
initialCapacity = Math.min(initialCapacity, (int) maxWeight);
}
...
int segmentShift = 0;
int segmentCount = 1;
while (segmentCount < concurrencyLevel
&& (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
++segmentShift;
segmentCount <<= 1;
}
this.segmentShift = 32 - segmentShift;
segmentMask = segmentCount - 1;
//初始化segments大小
this.segments = newSegmentArray(segmentCount);
int segmentCapacity = initialCapacity / segmentCount;
if (segmentCapacity * segmentCount < initialCapacity) {
++segmentCapacity;
}
int segmentSize = 1;
while (segmentSize < segmentCapacity) {
segmentSize <<= 1;
}
//初始化Segments
if (evictsBySize()) {
// Ensure sum of segment max weights = overall max weights
long maxSegmentWeight = maxWeight / segmentCount + 1;
long remainder = maxWeight % segmentCount;
for (int i = 0; i < this.segments.length; ++i) {
if (i == remainder) {
maxSegmentWeight--;
}
this.segments[i] =
createSegment(segmentSize, maxSegmentWeight, builder.getStatsCounterSupplier().get());
}
} else {
for (int i = 0; i < this.segments.length; ++i) {
this.segments[i] =
createSegment(segmentSize, UNSET_INT, builder.getStatsCounterSupplier().get());
}
}
}
Segment初始化操作
初始化容器
Segment(LocalCache map, int initialCapacity, long maxSegmentWeight,
StatsCounter statsCounter) {
this.map = map;
this.maxSegmentWeight = maxSegmentWeight;
this.statsCounter = checkNotNull(statsCounter);
initTable(newEntryArray(initialCapacity));//初始化table
keyReferenceQueue = map.usesKeyReferences()
? new ReferenceQueue() : null;//key引用队列
valueReferenceQueue = map.usesValueReferences()
? new ReferenceQueue() : null;//value引用队列
recencyQueue = map.usesAccessQueue()
? new ConcurrentLinkedQueue>()
: LocalCache.>discardingQueue();
writeQueue = map.usesWriteQueue()
? new WriteQueue()
: LocalCache.>discardingQueue();//写入元素队列
accessQueue = map.usesAccessQueue()
? new AccessQueue()
: LocalCache.>discardingQueue();//访问元素队列
}
以上是整个初始化流程
LocalCache put加载缓存
Cache 接口声明
@Beta
@GwtCompatible
public interface Cache {
void put(K key, V value);
}
LocalCache的实现
@GwtCompatible(emulated = true)
class LocalCache extends AbstractMap implements ConcurrentMap {
@Override
public V put(K key, V value) {
checkNotNull(key);
checkNotNull(value);
int hash = hash(key);
return segmentFor(hash).put(key, hash, value, false);
}
}
segmentFor(hash).put(key, hash, value, false)
下面第四行代码可以看出preWriteCleanup 在每次put之前都会清理动作,我在缓存储移除的时机
小段中进行过提示,缓存的清除时机是在读/写 操作的时候进行的。
@Nullable
V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
long now = map.ticker.read();
preWriteCleanup(now);
int newCount = this.count + 1;//localCache的Count+1
if (newCount > this.threshold) { // ensure capacity 是否要进行扩容
expand();//扩容
newCount = this.count + 1;
}
//获取当前Entry中的HashTable的Entry数组
AtomicReferenceArray> table = this.table;
int index = hash & (table.length() - 1);//计算散列值对应table的索引位置
ReferenceEntry first = table.get(index);//通过索引获取ReferenceEntry
// Look for an existing entry.进行遍历 如果找到则进行下面逻辑
for (ReferenceEntry e = first; e != null; e = e.getNext()) {
K entryKey = e.getKey();
if (e.getHash() == hash && entryKey != null
&& map.keyEquivalence.equivalent(key, entryKey)) {
// We found an existing entry.如果找到则进行下面逻辑
// 对应的值引用
ValueReference valueReference = e.getValueReference();
V entryValue = valueReference.get();//获取值
// cache 提供基于引用的回收策略,此处可能为null:即可能会GC了
if (entryValue == null) {
++modCount;
if (valueReference.isActive()) {
enqueueNotification(key, hash, valueReference, RemovalCause.COLLECTED);
setValue(e, key, value, now);//存储数据,并且将新增加的元素写入两个队列中
newCount = this.count; // count remains unchanged
} else {
setValue(e, key, value, now);
newCount = this.count + 1;
}
this.count = newCount; // write-volatile
evictEntries();//淘汰缓存
return null;
} else if (onlyIfAbsent) {
// Mimic
// "if (!map.containsKey(key)) ...
// else return map.get(key);
recordLockedRead(e, now);
return entryValue;
} else {
// clobber existing entry, count remains unchanged
// 如果存在且值不为null 则进行更新value
++modCount;
enqueueNotification(key, hash, valueReference, RemovalCause.REPLACED);
setValue(e, key, value, now);
evictEntries();
return entryValue;
}
}
}
// Create a new entry. 不存在则新创建newEntry
++modCount;
ReferenceEntry newEntry = newEntry(key, hash, first);
setValue(newEntry, key, value, now);
table.set(index, newEntry);
newCount = this.count + 1;
this.count = newCount; // write-volatile
evictEntries();
return null;
} finally {
unlock();
postWriteCleanup();
}
}
LocalCache preWriteCleanup(now);
简单解说下preWriteCleanup(now);preWriteCleanup在每次put之前都会清理动作
@GuardedBy("this")
void preWriteCleanup(long now) {
runLockedCleanup(now);
}
void runLockedCleanup(long now) {
if (tryLock()) {
try {
drainReferenceQueues();
expireEntries(now); // calls drainRecencyQueue
readCount.set(0);
} finally {
unlock();
}
}
}
//排空键和值引用队列,清除包含垃圾收集的键或值的内部条目
@GuardedBy("this")
void drainReferenceQueues() {
if (map.usesKeyReferences()) {
drainKeyReferenceQueue();
}
if (map.usesValueReferences()) {
drainValueReferenceQueue();
}
}
//基于过期时间的清除
@GuardedBy("this")
void expireEntries(long now) {
drainRecencyQueue();
ReferenceEntry e;
while ((e = writeQueue.peek()) != null && map.isExpired(e, now)) {
if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
throw new AssertionError();
}
}
while ((e = accessQueue.peek()) != null && map.isExpired(e, now)) {
if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
throw new AssertionError();
}
}
}
drainKeyReferenceQueue 下面就是清理过程:
@GuardedBy("this")
void drainKeyReferenceQueue() {
Reference ref;
int i = 0;
while ((ref = keyReferenceQueue.poll()) != null) {
@SuppressWarnings("unchecked")
ReferenceEntry entry = (ReferenceEntry) ref;
map.reclaimKey(entry);
if (++i == DRAIN_MAX) {
break;
}
}
}
void reclaimKey(ReferenceEntry entry) {
int hash = entry.getHash();
segmentFor(hash).reclaimKey(entry, hash);
}
/**
* Removes an entry whose key has been garbage collected.
*/
boolean reclaimKey(ReferenceEntry entry, int hash) {
lock();
try {
int newCount = count - 1;
AtomicReferenceArray> table = this.table;
int index = hash & (table.length() - 1);
ReferenceEntry first = table.get(index);
for (ReferenceEntry e = first; e != null; e = e.getNext()) {
if (e == entry) {
++modCount;
ReferenceEntry newFirst = removeValueFromChain(
first, e, e.getKey(), hash, e.getValueReference(), RemovalCause.COLLECTED);
newCount = this.count - 1;
table.set(index, newFirst);
this.count = newCount; // write-volatile
return true;
}
}
return false;
} finally {
unlock();
postWriteCleanup();
}
}
LocalCache get获取缓存值
Cache 接口声明
@Beta
@GwtCompatible
public interface Cache {
V get(K key) throws ExecutionException;
}
LocalLoadingCache的实现
下面是get方法的调用链:最终执行segmentFor(hash).get(key, hash, loader);
首先通过key 与 散列值获取Entry,如果获取Entry不为null;继续获取对应的value;如果value不为null,并更新访问时间,加入recencyQueue,之后进行判断是否进行刷新逻辑 返回。
如果value为null,检测是否进行loading,如果是则等待,等待结果waitForLoadingValue(e, key, valueReference); ;
如果获取Entry为null,则lockedGetOrLoad(key, hash, loader); ,其方法逻辑实现与put方法非常相似,这里就不在做介绍。有兴趣的读者可以去看看。也需要说明的时候,此时该方法相当有就是写缓存,所以也会进行加锁。
@Override
public V get(K key) throws ExecutionException {
return localCache.getOrLoad(key);
}
V getOrLoad(K key) throws ExecutionException {
return get(key, defaultLoader);
}
V get(K key, CacheLoader loader) throws ExecutionException {
int hash = hash(checkNotNull(key));
return segmentFor(hash).get(key, hash, loader);
}
V get(K key, int hash, CacheLoader loader) throws ExecutionException {
checkNotNull(key);
checkNotNull(loader);
try {
if (count != 0) { // read-volatile volatile读会刷新缓存,尽量保证可见性,如果为0那么直接load
// don't call getLiveEntry, which would ignore loading values
ReferenceEntry e = getEntry(key, hash);
if (e != null) {//判断通过key与hash获取的Entry是否为null,不为null则存在
long now = map.ticker.read();//获取当前的访问时间
V value = getLiveValue(e, now);//根据当前访问时间获取Live的数据
if (value != null) {
recordRead(e, now);//设置entry的AccessTime。并且加入recencyQueue
statsCounter.recordHits(1);//记录缓存命中
return scheduleRefresh(e, key, hash, value, now, loader)// 如果定时刷新,尝试刷新value
}
//value为null,如果此时value正在刷新,那么此时等待刷新结果
ValueReference valueReference = e.getValueReference();
if (valueReference.isLoading()) {
return waitForLoadingValue(e, key, valueReference);
}
}
}
// at this point e is either null or expired;
return lockedGetOrLoad(key, hash, loader);
} catch (ExecutionException ee) {
Throwable cause = ee.getCause();
if (cause instanceof Error) {
throw new ExecutionError((Error) cause);
} else if (cause instanceof RuntimeException) {
throw new UncheckedExecutionException(cause);
}
throw ee;
} finally {
postReadCleanup();//每次Put和get之后都要进行一次Clean
}
}
至此针对 Guava Cache的使用,以及它的运转流程做了一个简单的介绍。可能水平有限如果有不正确的地方请指正。
你可能感兴趣的:(02 初识缓存-GuavaCache)
广汽本田销量暴跌近三成,李进没有交出好答卷?
财经三剑客
人工智能 汽车
全年累计销量47.06万辆,同比下滑26.52%,新能源车型全年累计销量仅1万多辆。这就是广汽本田2024年交出的“答卷”。同时,这也是广汽本田自2021年以来连续第四年年销量下滑。随着2024年车市数据的揭晓,广汽本田的销量表现无疑成为了众人瞩目的焦点。这家曾经稳坐年度销量榜单前十的车企,如今却面临着销量骤降近三成的严峻挑战,而且这场溃败似乎仍未触底。回顾过去一年,我国汽车市场呈现出整体增长的态
2025年:信创替代的关键之年
了不起的云计算V
人工智能 大数据
2025年,将成为信创替代进程中的关键一年。在国家政策的强力推动下,信创产业正加速实现国产化替代,从技术突破到市场拓展,从产业链完善到应用场景的全面铺开,信创产业即将迎来全面爆发的临界点。一、政策引领:信创替代的加速引擎自2020年信创产业进入规模化推广阶段以来,国家政策持续加码。2023年9月底下发的79号文更是为信创替代按下了“快进键”,明确要求到2027年央企国企100%完成信创替代,涵盖芯
C#上位机工作感想1(2020.7.1-2021.4.4)
有追求的菜鸟
C#WinForm c#
C#上位机工作感想(2020.7.1-2021.4.4)前言一、2020年二、2021年总结前言不知不觉工作已九个月有余了,从刚来公司还是个啥也不太懂得小菜鸟,现在已经是负责两个项目的大菜鸟啦。这大半年的时间内,师傅教给了我很多业务上的知识,自己也在工作和闲暇时间里学到了很多编程知识。对C#语言的底层实现和一些简单设计模式的应用都有了更深的理解。一、2020年先来说说项目路线吧,去年七月份刚来公司
2025年智慧化工园区整体解决方案-下载:安全生产管控,全生命周期数字管理架构
百家方案
解决方案 安全 架构 智慧化工园区
随着化工产业的快速发展,化工园区作为产业集聚的重要载体,面临着安全生产、环境保护、高效运营等诸多挑战。传统管理模式已难以满足现代化发展需求,智慧化工园区的建设成为必然趋势。本文将介绍智慧化工园区的整体解决方案,重点聚焦于安全生产管控和全生命周期数字管理架构。智慧化工园区标准规范及顶层设计指南、整体解决方案及售前PPT、大厂解决方案宣讲视频案例等全套资料。11名专业售前方案工程师,工作中精心总结准备
Win7本地化部署deepseek-r1等大模型详解
mygodalien
语言模型 Windows7 本地化部署 大模型 llama
参考链接在Windows7操作系统,基于llama.cpp本地化部署deepseek-r1模型的方法2025-02-082G内存Windows7运行deepseek-r1:1.5b这两个链接写的可能不够详细,有同学私信问实现过程,这里进一步解释一下。一、准备需要准备的大模型、工具等文件,已放到网盘,可自取。网盘的figures目录是配置过程中的一些截图,可参考。百度网盘:https://pan.b
ADF动态内容的使用:基于文件名过滤和增量加载
t0_54coder
编程问题解决手册 flask python 后端 个人开发
在使用AzureDataFactory(ADF)进行数据处理时,经常会遇到需要根据文件名中的日期进行过滤和增量加载的情况。本文将通过一个具体的例子,详细讲解如何在ADF中实现这一需求。背景介绍假设我们有一个FTP服务器,存储着每周更新的文件,这些文件的命名方式如下:a_2023-01-01.csvb_2023-01-01.csvc_2023-01-01.csv同时,这些文件也可能以.zip和.ok
一、C#上位机语法篇学习笔记
飞翔的老宫本
C#上位机 c# visual studio windows 笔记 学习
0、基础中的基础Write和WriteLine方法对比#region1Write和WriteLine方法对比staticvoidTest1()//方法{stringemal1="915992026@qq.com";stringuserName=emal1.Substring(0,7);Console.WriteLine(userName);//输出后换行stringuserName1=emal1.
深入浅出链表:Python实现与应用全面解析
吴师兄大模型
链表 python 数据结构 算法 编程 开发语言 单链表
系列文章目录01-从零开始掌握Python数据结构:提升代码效率的必备技能!02-算法复杂度全解析:时间与空间复杂度优化秘籍03-线性数据结构解密:数组的定义、操作与实际应用04-深入浅出链表:Python实现与应用全面解析文章目录系列文章目录前言一、链表的定义与特点1.1链表的基本结构1.1.1链表节点结构图示1.2链表的特点1.2.1优点1.2.2缺点二、单链表、双链表、循环链表的区别2.1单
【鸿蒙开发实战教程】在MacOS上如何使用 Flutter 构建鸿蒙APP
「已注销」
harmonyos macos flutter 鸿蒙
前言根据研究机构CounterpointResearch发布的最新数据,2024年第一季度,鸿蒙OS份额由去年一季度的8%上涨至17%,iOS份额则从20%下降至16%。这意味着,华为鸿蒙OS在中国市场的份额超越苹果iOS,已成中国第二大操作系统。随着鸿蒙市场份额的不断提升,相应的岗位也会迎来一个爆发式的增长。这对于想要换赛道的程序员来说是一个非常好的消息,话说大家最近有想法转型鸿蒙开发吗?目前各
速度超越DeepSeek!Le Chat 1100tok/s闪电回答,ChatGPT 4o和DeepSeek R1被秒杀?
LinkTime_Cloud
chatgpt DeepSeek Le Chat
2023年,当全球科技界还在ChatGPT引发的AI狂潮中沉浮时,一场来自欧洲的"静默革命"正悄然改变游戏规则。法国人工智能公司MistralAI推出的聊天机器人LeChat以"比ChatGPT快10倍"的惊人宣言震动业界,其背后承载的不仅是技术突破,更折射出全球AI版图重构的深层暗涌。一、法兰西奇迹:Mistral的逆袭密码在巴黎第十区一栋不起眼的办公楼里,Mistral团队用9个月时间创造了令
2024信创答卷:技术引领,荣誉加持,构筑数字世界安全防线
亚信安全官方账号
安全 大数据
随着数字化转型的持续推进,网络安全已成为国家战略和企业发展的核心支撑。作为中国网络安全软件领域的领导者,2024年亚信安全在信创领域取得了显著成就,不仅在产品创新方面实现了重大突破,还在信创生态合作、技术创新和市场荣誉等多个维度取得了跨越式进展。产品技术引领守护信创安全亚信安全坚持以技术创新为核心,重点发力自有知识产权产品的研发和创新。近几年,亚信安全基于国产化硬件平台和国产操作系统,以用户需求为
亚信安全春节14天双倍假期通告
亚信安全官方账号
网络安全 信息安全 亚信安全 网络安全
亚信安全14天双倍假期来袭“网安福利王”再次实至名归2024年8773小时,31582680秒亚信安全一直驰骋于云网安世界奋战在“安全+数智化”的壮阔征途上如今,新春的脚步渐近长达14天的春节长假能让我们暂且放下忙碌的工作去除班味,裹满年味咱就是说,这假期长到能在家“躺平”追完一整部剧,或者把厨艺练得“炉火纯青”,尽情享受欢乐时光!与家人团圆,共享温馨时刻。看烟火盛放,感受岁月静好。再以饱满的热情
阿里巴巴“大中台“战略(合)在2015年缔造双11奇迹,2021年又拆分为六大业务集团(分),组织敏捷度提升40%
百态老人
人工智能 机器学习 深度学习
阿里巴巴的"大中台"战略与后续组织变革体现了其在不同发展阶段对敏捷性的探索。具体发展脉络如下:一、2015年"大中台"战略缔造双11奇迹2015年张勇主导实施"大中台、小前台"战略,通过整合数据技术、产品研发等共性能力形成统一支撑平台。这种架构在当年双11中发挥关键作用:中台系统实现超过75亿次数据调用,支撑起淘宝、天猫等前台业务的爆发式增长,创造了912亿元成交额的商业奇迹。中台战略不仅减少了业
python票务系统_python基础学习:模拟火车订票系统
一愫
python票务系统
2020-12-20str1=["车次","出发站-到达站","出发时间","到达时间","历时"]train_numbers=["T40","T298","Z158"]train_place=["长春-北京","哈尔滨-北京","青岛-北京"]train_outTime=["00:12","00:06","12:48"]train_arriveTime=["12:20","10:50","21:
2024年编写软件需求规格说明(SRS)的指南
reddishz
需求分析 需求分析 软件工程
2024年编写SRS-软件需求规格说明每个创新项目起初都源自一个灵光闪现的念头,而要将这粒种子培育成参天大树,特别是开发数字化产品时,旅程的起跑线是一份核心文件:软件需求规范(SRS)。你的创意或许璀璨夺目,独树一帜,但真正的考验在于如何将之变为现实,SRS便是这趟旅程中的北极星。设想一下,建造一艘船却不备蓝图,那会怎样?施工混乱无序,频繁修改不仅耗资巨大,还拖延时间,甚至可能导致项目搁浅。软件开
【JPCS(ISSN:1742-6596)独立出版】2025年应用物理与材料科学国际学术研讨会(ICAPMS 2025)
艾思科蓝 AiScholar
学术会议 材料科学与工程 材料科学 网络 自然语言处理 语言模型 人工智能 开发语言 材料工程 物联网
2025年应用物理与材料科学国际学术研讨会(ICAPMS2025)将于2025年3月21-23日于广州隆重召开。本次研讨会旨在汇聚来自全球的科学家、研究人员和行业专家,共同探讨应用物理与材料科学领域的前沿进展与创新应用。会议将提供丰富的学术交流平台,包括特邀报告、主题讨论和互动研讨,涵盖纳米材料、半导体材料、生物材料和先进制造工艺等多个研究方向。与会者将有机会分享他们的新研究成果,深入交流学术观点
网络安全(黑客)——自学2025
网安大师兄
web安全 安全 网络 网络安全 密码学
基于入门网络安全/黑客打造的:黑客&网络安全入门&进阶学习资源包前言什么是网络安全网络安全可以基于攻击和防御视角来分类,我们经常听到的“红队”、“渗透测试”等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。如何成为一名黑客很多朋友在学习安全方面都会半路转行,因为不知如何去学,在这里,我将这个整份答案分为黑客(网络安全)入门必备、黑客(网络安全)职业指南、黑客(网络安全)学习
2021-04-30 VSC++:EAN13条形码校验。
智者知已应修善业
c++ 算法 经验分享 c语言
voidEAN13条形码校验(){//缘由https://bbs.csdn.net/topics/3991705046901234567892691234567890269234567899996901028075763stringt="";inta=0,q=0;std::cin>>t;while(++a<13)q+=(t[a-1]-'0')*((a%2)?1:3);if((((10-q%10)%
利用gensim生成词袋模型(基于频次和基于TF-IDF)
weixin_50291342
文本表示 自然语言处理 python 机器学习
前言参考文献:胡盼盼编著.自然语言处理从入门到实战[M].中国铁道出版社,2020.最近在学习文本表示的一种最简单方式——词袋模型,书中给出了使用gensim生成词袋模型的代码,原代码就来自于这本书,我加了一些注释,方便理解代码。一、引入库fromgensim.modelsimportTfidfModelfromgensim.corporaimportDictionaryimportjieba二、
【计算机组成原理】1_绪论
BroccoliKing
计组 网络 硬件架构 fpga开发 arm开发 iot mcu
chap1绪论1.国产芯片现状MIPS阵营:龙芯X86阵营(常见于桌面和服务器):兆芯(VIA),海光(AMD)ARM阵营(常见于移动嵌入式、手机平板等):飞腾,海思,展讯,松果RISC-V阵营:阿里平头哥玄铁9102.冯·诺伊曼结构计算机2.1组成硬件系统:运行程序的基本组成部分运算器控制器:控制信号的产生方式包括微程序和硬布线存储器输入输出设备软件系统:系统软件和应用软件2.2层次结构3.指令
RAG中的双编码器与跨编码器模型
人工智能
RAG中的双编码器与跨编码器模型阅读时长:19分钟发布时间:2025-02-13近日热文:全网最全的神经网络数学原理(代码和公式)直观解释欢迎关注知乎和公众号的专栏内容LLM架构专栏知乎LLM专栏知乎【柏企】公众号【柏企科技说】【柏企阅文】检索增强生成(RAG)是一个强大的框架,它结合了基于检索和基于生成的自然语言处理(NLP)任务方法。RAG不只是依赖生成模型,而是通过检索相关文档或段落来利用外
安科瑞推出预防电动自行车火灾组合方案
安科瑞 华楠
能源
安科瑞华楠01概述.电动自行车火灾隐患,正受到越来越多的重视。作为全球电动自行车生产、消费第一大国,中国的电动自行车保有量已达3.5亿辆。电动自行车在解决居民短途出行需求的同时,也考验着城市安全监管和治理能力,其中充电环节的隐患是一大痛点。国家消防救援局统计数据显示,2023年全国共接报电动自行车火灾2.1万起,相比2022年上升17.4%。其中,80%的电动自行车火灾发生在充电时,由锂电池燃爆引
Github 2024-06-05 开源项目日报 Top10
老孙正经胡说
github 开源 Github趋势分析 开源项目 Python Golang
根据GithubTrendings的统计,今日(2024-06-05统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下:开发语言项目数量Python项目7JupyterNotebook项目2Rust项目1初学者的生成式人工智能(第2版)创建周期:259天开发语言:JupyterNotebook协议类型:MITLicenseStar数量:25764个Fork数量:16105次关注人数:
【AI论文】使用大型推理模型进行竞技编程
东临碣石82
人工智能
摘要:我们的研究表明,将强化学习应用于大型语言模型(LLMs)能显著提升复杂编码和推理任务的性能。此外,我们将两个通用推理模型——OpenAI的o1模型和o3模型的一个早期检查点——与一个特定领域的系统o1-ioi进行了比较。o1-ioi采用了为参加2024年国际信息学奥林匹克竞赛(IOI)而手工设计的推理策略。我们使用o1-ioi实时参加了2024年IOI竞赛,并凭借手工制定的测试时策略取得了第
2024年办公协作新趋势:8种值得瞩目的工作方式
不秃头的UI设计师
远程工作 远程办公 协作 团队协作
过去两年中,疫情的爆发推动了远程办公业务的发展,并且随着疫情的常态化和企业数字化转型的加速,中国企业对协作办公软件的需求显著增加。数据显示,2021年中国协作办公市场规模已达264.2亿元,预计到2023年将增长至330.1亿元。在线团队协作就像在不同应用场景中搭建了一座虚拟桥梁,打破了企业内部的数据孤岛,促进了各部门之间的多维协作。在线协作设计软件则成为企业数字化转型的理想选择,满足了企业在数字
【好书推荐2】AI提示工程实战:从零开始利用提示工程学习应用大语言模型
是Yu欸
粉丝福利 人工智能 深度学习 bert AIGC prompt gpt AI写作
【好书推荐2】AI提示工程实战:从零开始利用提示工程学习应用大语言模型写在最前面AI辅助研发方向一:AI辅助研发的技术进展方向二:行业应用案例方向三:面临的挑战与机遇方向四:未来趋势预测方向五:与法规的影响方向六:人才培养与教育《AI提示工程实战:从零开始利用提示工程学习应用大语言模型》关键点内容简介作者简介你好呀!我是是Yu欸2024每日百字篆刻时光,感谢你的陪伴与支持~欢迎一起踏上探险之旅,挖
DeepSeek与ChatGPT正在改写学历规则?2025教育革命深度解析
笑傲江湖2023
人工智能 chatgpt
一、颠覆性现状:AI如何解构学历价值1.知识获取民主化随着AI技术的不断进步,知识获取的方式正在发生翻天覆地的变化:DeepSeek-R1通过仅10%的训练成本,实现了与GPT-4o相当的性能,技术文档的生成效率提升了70%。这种高效的知识生成方式,使得人人都可以轻松获取和应用知识。斯坦福大学的研究显示,使用ChatGPT的大学生平均GPA提升了0.43分,但课程通过率却下降了11%(2024)。
分布式数据库面试整理
Necther
数据库 面试 分布式
5.3.1redis面试专题1、redis和memcached什么区别?为什么高并发下有时单线程的redis比多线程的memcached效率要高?区别:mc可缓存图片和视频。rd支持除k/v更多的数据结构;rd可以使用虚拟内存,rd可持久化和aof灾难恢复,rd通过主从支持数据备份;3.rd可以做消息队列。原因:mc多线程模型引入了缓存一致性和锁,加锁带来了性能损耗。2、redis主从复制如何实现
定了,2025年数据库运维就这样干
我科绝伦(Huanhuan Zhou)
mysql oracle 数据库 运维 oracle
在数字化浪潮中,数据库作为企业数据资产的核心载体,其稳定运行与高效管理至关重要。新的一年,为应对日益增长的数据量和业务复杂度,我们为数据库运维制定了一系列全面且细致的计划,旨在提升数据库性能、保障数据安全,为企业业务发展筑牢坚实基础。一、强化巡检,防患未然数据库的稳定运行离不开日常的精心呵护。2025年,我们将进一步加强巡检工作,利用专业工具和自主研发的系统,对数据库进行全面“体检”。不仅关注数据
BusinessException与GlobalExceptionHandler的使用
Coder LM Wang
Java java 前端 服务器
BusinessException这是一个自定义异常类,用于表示业务逻辑相关的异常使用场景:在业务代码中抛出具体的业务异常使用方式://在业务代码中抛出异常if(userExists){thrownewBusinessException("4002","该邮箱已注册");}//或者只传消息thrownewBusinessException("参数错误");//或者带上原始异常try{//某些操作}
HttpClient 4.3与4.3版本以下版本比较
spjich
java httpclient
网上利用java发送http请求的代码很多,一搜一大把,有的利用的是java.net.*下的HttpURLConnection,有的用httpclient,而且发送的代码也分门别类。今天我们主要来说的是利用httpclient发送请求。
httpclient又可分为
httpclient3.x
httpclient4.x到httpclient4.3以下
httpclient4.3
Essential Studio Enterprise Edition 2015 v1新功能体验
Axiba
.net
概述:Essential Studio已全线升级至2015 v1版本了!新版本为JavaScript和ASP.NET MVC添加了新的文件资源管理器控件,还有其他一些控件功能升级,精彩不容错过,让我们一起来看看吧!
syncfusion公司是世界领先的Windows开发组件提供商,该公司正式对外发布Essential Studio Enterprise Edition 2015 v1版本。新版本
[宇宙与天文]微波背景辐射值与地球温度
comsci
背景
宇宙这个庞大,无边无际的空间是否存在某种确定的,变化的温度呢?
如果宇宙微波背景辐射值是表示宇宙空间温度的参数之一,那么测量这些数值,并观测周围的恒星能量输出值,我们是否获得地球的长期气候变化的情况呢?
&nbs
lvs-server
男人50
server
#!/bin/bash
#
# LVS script for VS/DR
#
#./etc/rc.d/init.d/functions
#
VIP=10.10.6.252
RIP1=10.10.6.101
RIP2=10.10.6.13
PORT=80
case $1 in
start)
/sbin/ifconfig eth2:0 $VIP broadca
java的WebCollector爬虫框架
oloz
爬虫
WebCollector主页:
https://github.com/CrawlScript/WebCollector
下载:webcollector-版本号-bin.zip将解压后文件夹中的所有jar包添加到工程既可。
接下来看demo
package org.spider.myspider;
import cn.edu.hfut.dmic.webcollector.cra
jQuery append 与 after 的区别
小猪猪08
1、after函数
定义和用法:
after() 方法在被选元素后插入指定的内容。
语法:
$(selector).after(content)
实例:
<html>
<head>
<script type="text/javascript" src="/jquery/jquery.js"></scr
mysql知识充电
香水浓
mysql
索引
索引是在存储引擎中实现的,因此每种存储引擎的索引都不一定完全相同,并且每种存储引擎也不一定支持所有索引类型。
根据存储引擎定义每个表的最大索引数和最大索引长度。所有存储引擎支持每个表至少16个索引,总索引长度至少为256字节。
大多数存储引擎有更高的限制。MYSQL中索引的存储类型有两种:BTREE和HASH,具体和表的存储引擎相关;
MYISAM和InnoDB存储引擎
我的架构经验系列文章索引
agevs
架构
下面是一些个人架构上的总结,本来想只在公司内部进行共享的,因此内容写的口语化一点,也没什么图示,所有内容没有查任何资料是脑子里面的东西吐出来的因此可能会不准确不全,希望抛砖引玉,大家互相讨论。
要注意,我这些文章是一个总体的架构经验不针对具体的语言和平台,因此也不一定是适用所有的语言和平台的。
(内容是前几天写的,现附上索引)
前端架构 http://www.
Android so lib库远程http下载和动态注册
aijuans
andorid
一、背景
在开发Android应用程序的实现,有时候需要引入第三方so lib库,但第三方so库比较大,例如开源第三方播放组件ffmpeg库, 如果直接打包的apk包里面, 整个应用程序会大很多.经过查阅资料和实验,发现通过远程下载so文件,然后再动态注册so文件时可行的。主要需要解决下载so文件存放位置以及文件读写权限问题。
二、主要
linux中svn配置出错 conf/svnserve.conf:12: Option expected 解决方法
baalwolf
option
在客户端访问subversion版本库时出现这个错误:
svnserve.conf:12: Option expected
为什么会出现这个错误呢,就是因为subversion读取配置文件svnserve.conf时,无法识别有前置空格的配置文件,如### This file controls the configuration of the svnserve daemon, if you##
MongoDB的连接池和连接管理
BigCat2013
mongodb
在关系型数据库中,我们总是需要关闭使用的数据库连接,不然大量的创建连接会导致资源的浪费甚至于数据库宕机。这篇文章主要想解释一下mongoDB的连接池以及连接管理机制,如果正对此有疑惑的朋友可以看一下。
通常我们习惯于new 一个connection并且通常在finally语句中调用connection的close()方法将其关闭。正巧,mongoDB中当我们new一个Mongo的时候,会发现它也
AngularJS使用Socket.IO
bijian1013
JavaScript AngularJS Socket.IO
目前,web应用普遍被要求是实时web应用,即服务端的数据更新之后,应用能立即更新。以前使用的技术(例如polling)存在一些局限性,而且有时我们需要在客户端打开一个socket,然后进行通信。
Socket.IO(http://socket.io/)是一个非常优秀的库,它可以帮你实
[Maven学习笔记四]Maven依赖特性
bit1129
maven
三个模块
为了说明问题,以用户登陆小web应用为例。通常一个web应用分为三个模块,模型和数据持久化层user-core, 业务逻辑层user-service以及web展现层user-web,
user-service依赖于user-core
user-web依赖于user-core和user-service
依赖作用范围
Maven的dependency定义
【Akka一】Akka入门
bit1129
akka
什么是Akka
Message-Driven Runtime is the Foundation to Reactive Applications
In Akka, your business logic is driven through message-based communication patterns that are independent of physical locatio
zabbix_api之perl语言写法
ronin47
zabbix_api之perl
zabbix_api网上比较多的写法是python或curl。上次我用java--http://bossr.iteye.com/blog/2195679,这次用perl。for example: #!/usr/bin/perl
use 5.010 ;
use strict ;
use warnings ;
use JSON :: RPC :: Client ;
use
比优衣库跟牛掰的视频流出了,兄弟连Linux运维工程师课堂实录,更加刺激,更加实在!
brotherlamp
linux运维工程师 linux运维工程师教程 linux运维工程师视频 linux运维工程师资料 linux运维工程师自学
比优衣库跟牛掰的视频流出了,兄弟连Linux运维工程师课堂实录,更加刺激,更加实在!
-----------------------------------------------------
兄弟连Linux运维工程师课堂实录-计算机基础-1-课程体系介绍1
链接:http://pan.baidu.com/s/1i3GQtGL 密码:bl65
兄弟连Lin
bitmap求哈密顿距离-给定N(1<=N<=100000)个五维的点A(x1,x2,x3,x4,x5),求两个点X(x1,x2,x3,x4,x5)和Y(
bylijinnan
java
import java.util.Random;
/**
* 题目:
* 给定N(1<=N<=100000)个五维的点A(x1,x2,x3,x4,x5),求两个点X(x1,x2,x3,x4,x5)和Y(y1,y2,y3,y4,y5),
* 使得他们的哈密顿距离(d=|x1-y1| + |x2-y2| + |x3-y3| + |x4-y4| + |x5-y5|)最大
map的三种遍历方法
chicony
map
package com.test;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class TestMap {
public static v
Linux安装mysql的一些坑
chenchao051
linux
1、mysql不建议在root用户下运行
2、出现服务启动不了,111错误,注意要用chown来赋予权限, 我在root用户下装的mysql,我就把usr/share/mysql/mysql.server复制到/etc/init.d/mysqld, (同时把my-huge.cnf复制/etc/my.cnf)
chown -R cc /etc/init.d/mysql
Sublime Text 3 配置
daizj
配置 Sublime Text
Sublime Text 3 配置解释(默认){// 设置主题文件“color_scheme”: “Packages/Color Scheme – Default/Monokai.tmTheme”,// 设置字体和大小“font_face”: “Consolas”,“font_size”: 12,// 字体选项:no_bold不显示粗体字,no_italic不显示斜体字,no_antialias和
MySQL server has gone away 问题的解决方法
dcj3sjt126com
SQL Server
MySQL server has gone away 问题解决方法,需要的朋友可以参考下。
应用程序(比如PHP)长时间的执行批量的MYSQL语句。执行一个SQL,但SQL语句过大或者语句中含有BLOB或者longblob字段。比如,图片数据的处理。都容易引起MySQL server has gone away。 今天遇到类似的情景,MySQL只是冷冷的说:MySQL server h
javascript/dom:固定居中效果
dcj3sjt126com
JavaScript
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml&
使用 Spring 2.5 注释驱动的 IoC 功能
e200702084
spring bean 配置管理 IOC Office
使用 Spring 2.5 注释驱动的 IoC 功能
developerWorks
文档选项
将打印机的版面设置成横向打印模式
打印本页
将此页作为电子邮件发送
将此页作为电子邮件发送
级别: 初级
陈 雄华 (quickselect@163.com), 技术总监, 宝宝淘网络科技有限公司
2008 年 2 月 28 日
&nb
MongoDB常用操作命令
geeksun
mongodb
1. 基本操作
db.AddUser(username,password) 添加用户
db.auth(usrename,password) 设置数据库连接验证
db.cloneDataBase(fromhost)
php写守护进程(Daemon)
hongtoushizi
PHP
转载自: http://blog.csdn.net/tengzhaorong/article/details/9764655
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。php也可以实现守护进程的功能。
1、基本概念
&nbs
spring整合mybatis,关于注入Dao对象出错问题
jonsvien
DAO spring bean mybatis prototype
今天在公司测试功能时发现一问题:
先进行代码说明:
1,controller配置了Scope="prototype"(表明每一次请求都是原子型)
@resource/@autowired service对象都可以(两种注解都可以)。
2,service 配置了Scope="prototype"(表明每一次请求都是原子型)
对象关系行为模式之标识映射
home198979
PHP 架构 企业应用 对象关系 标识映射
HELLO!架构
一、概念
identity Map:通过在映射中保存每个已经加载的对象,确保每个对象只加载一次,当要访问对象的时候,通过映射来查找它们。其实在数据源架构模式之数据映射器代码中有提及到标识映射,Mapper类的getFromMap方法就是实现标识映射的实现。
二、为什么要使用标识映射?
在数据源架构模式之数据映射器中
//c
Linux下hosts文件详解
pda158
linux
1、主机名: 无论在局域网还是INTERNET上,每台主机都有一个IP地址,是为了区分此台主机和彼台主机,也就是说IP地址就是主机的门牌号。 公网:IP地址不方便记忆,所以又有了域名。域名只是在公网(INtERNET)中存在,每个域名都对应一个IP地址,但一个IP地址可有对应多个域名。 局域网:每台机器都有一个主机名,用于主机与主机之间的便于区分,就可以为每台机器设置主机
nginx配置文件粗解
spjich
java nginx
#运行用户#user nobody;#启动进程,通常设置成和cpu的数量相等worker_processes 2;#全局错误日志及PID文件#error_log logs/error.log;#error_log logs/error.log notice;#error_log logs/error.log inf
数学函数
w54653520
java
public
class
S {
// 传入两个整数,进行比较,返回两个数中的最大值的方法。
public
int
get(
int
num1,
int
nu