类型的成员变量queue,那么我们需要介绍下Java中的四种引用和引用队列ReferenceQueue,只有熟悉这两个东西才能搞懂WeakHashMap的工作原理。
中四种引用类型
Java的引用类放在Java.lang.ref包中,这个包是Java 类库中比较特殊的,这个包在用来实现与缓存相关的应用时特别有用。同时该包也提供了在对象的“可达”性发生改变时,进行提醒的机制。
先看下这个包下的类结构:
Java开发者无需关心内存的申请、释放和垃圾回收,这些事情都由JVM处理,以我所知,JVM提供了Reference(引用)机制,让我们能够在应用的层次利用内存或者GC特性,从而更好的使用内存。
StrongReference(强引用) 每当new一个对象出来,这种引用便是强引用。 JVM利用Finalizer来管理每个强引用对象 , 并将其被标记要清理时加入 ReferenceQueue, 并逐一调用该对象的 finalize() 方法。
SoftReference(软引用) 当内存足够的时候,软引用所指向的对象没有其他强引用指向的话,GC的时候并不会被回收,当且只当内存不够时(Heap 内存是否临近阈值)才会被GC回收(调用finalize方法)。强度仅次于强引用; get()
方法取得对象的强引用从而访问目标对象;当垃圾回收器决定对其回收时,会先清空它的 SoftReference,也就是说 SoftReference 的 get()
方法将会返回 null,并在下一轮 GC 中调用对象的 finalize() 方法对其真正进行回收。
WeakReference(弱引用) 基本与软引用相似,指向的对象没有任何强引用指向的话,GC的时候会进行回收,比SoftReference更容易被回收;get()
方法取得对象的强引用从而访问目标对象。
PhantomReference(虚引用) 虚引用在系统垃圾回收器开始回收对象时 , 将直接调用 finalize() 方法 , 但不会立即将其加入回收队列,只有在真正对象被 GC 清除时,才会将其加入引用队列中去。它类似强引用,不会自动根据内存情况自动对目标对象回收,在Heap里不断开辟新空间,当达到heap阈值时,系统报出 OOM异常。
其实后三种我们都可以称之为“弱引用”,看下他们的源码。 SoftReference的源码:
public class SoftReference extends Reference {
static private long clock;
private long timestamp;
public SoftReference(T referent) {
super(referent);
this.timestamp = clock;
}
public SoftReference(T referent, ReferenceQueue q) {
super(referent, q);
this.timestamp = clock;
}
public T get() {
T o = super.get();
if (o != null && this.timestamp != clock)
this.timestamp = clock;
return o;
}
}
WeakReference的源码:
public class WeakReference extends Reference {
public WeakReference(T referent) {
super(referent);
}
public WeakReference(T referent, ReferenceQueue q) {
super(referent, q);
}
}
PhantomReference的源码:
public class PhantomReference extends Reference {
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue q) {
super(referent, q);
}
}
在PhantomReference的源码中,get()
方法永远返回null,所以无法获取对象也无法使用该对象,而且和其它两个“弱引用”不一样的是,PhantomReference只提供了一个构造器,而其它两个“弱引用”都有两个构造器,那么PhantomReference 到底什么用呢? 首先一个知识点我们需要知道:虚引用的目标对象被回收前,它的引用会被放入一个 ReferenceQueue 对象中 ,这也是虚引用只提供一个构造器的原因(这个构造器的第二个参数就是引用队列)。因此虚引用主要被用来跟踪对象被垃圾回收的状态,通过查看引用队列中是否包含对象所对应的虚引用来判断它是否被被垃圾回收,从而做一些自己想做的东西或操作。虚引用对目标对象并不感兴趣也不想获取它的强引用,所以get()
方法永远返回null。
后三种引用被回收的时机以及用法各不相同,下面总结的表格参考自IBM的文章-深入探讨 java.lang.ref 包:
引用类型
取得目标对象方式
垃圾回收条件
是否可能内存泄漏
强引用
直接调用
不回收
可能
软引用
通过 get() 方法
视内存情况回收
不可能
弱引用
通过 get() 方法
永远回收
不可能
虚引用
无法取得
不回收
可能
举个SoftReference的例子1:
public static void main(String args[]) {//1
String t = new String("T");//2
ReferenceQueue refQueue = new ReferenceQueue();//3
SoftReference referent = new SoftReference(new String("T"), refQueue);//4
System.out.println(referent.get());//5
System.gc();//6
System.runFinalization();//7
System.out.println(referent.get());//8
Object pO = refQueue.poll();//9
System.out.println(pO + " " + (pO == referent));//10
}
要读懂上面的代码,您需要明白第6行和第7行的意思(我不知道怎么让代码显示行数,心碎)。System.gc()
意思是告诉垃圾收集器打算进行垃圾收集,只是有了打算但不一定执行回收;System.runFinalization()
必须在垃圾回收器有了回收垃圾的打算才有效,所以必须配合System.gc()
使用,它会强制调用已经失去所有强引用的对象的finalize方法进行回收。 第五行获取目标对象的强引用,所以打印T
,经过第6行和第7行的操作,由于软引用回收的条件是目标对象没有强引用并且内存紧张,我写测试代码的时候电脑的内存足够的,所以引用不会被回收,引用队列也是空的,因此第8行打印T
,第10行打印null false
:
$ java -XX:+HeapDumpOnOutOfMemoryError -Xms200m -Xmx200m -Xss512k -Xmn2g Test
T
T
null false
举个WeakReference的例子2:
public static void main(String args[]) {//1
String t = new String("T");//2
ReferenceQueue refQueue = new ReferenceQueue();//3
WeakReference referent = new WeakReference(new String("T"), refQueue);//4
System.out.println(referent.get());//5
System.gc();//6
System.runFinalization();//7
System.out.println(referent.get());//8
Object pO = refQueue.poll();//9
System.out.println(pO + " " + (pO == referent));//10
}
第五行获取目标对象的强引用,所以打印T
,经过第6行和第7行的操作,由于目标对象没有强引用,它的弱引用会放到引用队列中,所以第8行打印null
,第10行打印java.lang.ref.WeakReference@1c20c684 true
:
$ java -XX:+HeapDumpOnOutOfMemoryError -Xms200m -Xmx200m -Xss512k -Xmn2g Test
T
null
java.lang.ref.WeakReference@1c20c684 true
举个PhantomReference的例子3:
public static void main(String args[]) {//1
String t = new String("T");//2
ReferenceQueue refQueue = new ReferenceQueue();//3
PhantomReference referent = new PhantomReference(new String("T"), refQueue);//4
System.out.println(referent.get());//5
System.gc();//6
System.runFinalization();//7
System.out.println(referent.get());//8
Object pO = refQueue.poll();//9
System.out.println(pO + " " + (pO == referent));//10
}
第五行获取目标对象的强引用,所以打印null
,经过第6行和第7行的操作,由于目标对象没有强引用,它的弱引用会放到引用队列中,所以第8行打印null
,第10行打印java.lang.ref.WeakReference@1c20c684 true
:
$ java -XX:+HeapDumpOnOutOfMemoryError -Xms200m -Xmx200m -Xss512k -Xmn2g Test
null
null
java.lang.ref.WeakReference@1c20c684 true
前面写了三个例子,如果把每个例子的第4行构造器的第一个参数改为字符串常量"T"
即PhantomReference referent = new PhantomReference("T", refQueue);//4
,您可以自己实验会发生什么结果。解释下,如果参数改成了字符串常量,那么它是存在强引用的,这个强引用就是第二行的t,因此对象不会被垃圾回收,您需要对字符串的常量池机制有所了解就会明白原因,没什么技术含量。
以及
FinalReference 是java.lang.ref里的一个包权限的类,因此不能被开发者使用,Finalizer是它的子类并且是final类和包权限,它俩肯定只能被JVM调用,那么它俩什么作用呢?
java.lang.ref包下并没有StrongReference这个类,那么强引用的概念怎么来的呀?其实FinalReference代表的正是强引用,如这样的代码 :String fs = new String("fsfss");
JVM就给它创建一个看门狗(watchdog)来引用它,这个看门狗就是一个Finalizer实例, Finalizer在JVM中定义了一个FinalizerThread守护线程,JVM依靠这个线程的run方法对所有的解除了强引用内存进行清理。
让我们来看看 Finalizer工作流程。Finalizer含有五个成员变量:
private static ReferenceQueue queue = new ReferenceQueue<>();
private static Finalizer unfinalized = null;
private static final Object lock = new Object();
private Finalizer next = null, prev = null;
这个类有个static初始化块:
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread finalizer = new FinalizerThread(tg);
finalizer.setPriority(Thread.MAX_PRIORITY - 2);
finalizer.setDaemon(true);
finalizer.start();
}
这个初始化块声明并实例化一个FinalizerThread,将它设置为守护线程后,加入系统线程中去。在 GC 的过程中,当一个对象没了强引用(不包含Finalizer类的引用,这个引用是看门狗),垃圾收集器会标记该对象,然后会被加入到Finalizer对象中的static变量queue引用队列中去,FinalizerThread正在那里通过for死循环守株待兔地等着加入到引用队列的Finalizer实例,不信的话请看源码:
private static class FinalizerThread extends Thread {
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
super(g, "Finalizer");
}
public void run() {
// in case of recursive call to run()
if (running)
return;
// Finalizer thread starts before System.initializeSystemClass
// is called. Wait until JavaLangAccess is available
while (!VM.isBooted()) {
// delay until VM completes initialization
try {
VM.awaitBooted();
} catch (InterruptedException x) {
// ignore and continue
}
}
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
}
上面的源码清晰地告诉我们:这个线程的run方法中有一个for死循环,每当引用队列里加入了一个对象,那么线程就取出来并调用对象的runFinalizer方法,看下runFinalizer源码:
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
remove();
}
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee);
/* Clear stack slot containing this variable, to decrease
the chances of false retention with a conservative GC */
finalizee = null;
}
} catch (Throwable x) { }
super.clear();
}
上面的代码中调用了invokeFinalize方法。
再来看下Finalizer的构造器:
private Finalizer(Object finalizee) {
super(finalizee, queue);
add();
}
看到上面的代码,你有什么疑惑吗?这个构造器给我们留下三个疑问:首先,是谁调用了这个构造器,然后在什么时候调用的呢?最后,add方法干啥的? 对于第一个疑问,有一点可以肯定的是,开发者无法主动调用构造器,那么只能是JVM调用了,Finalizer提供了一个register方法:
static void register(Object finalizee) {
new Finalizer(finalizee);
}
register方法中调用了构造器,JVM就是通过register方法来创建Finalizer对象的。
第二个疑问,JVM什么时候调用register方法呢?这个疑问让我有点抓狂,网上看了一些分析,头大。 对象的创建是分为很多步骤的,一步是先执行new分配好对象空间,一步是再执行invokespecial调用构造函数,jvm里其实可以让用户选择在这两个步骤中的任意一个将当前对象传递给Finalizer.register方法来注册。《JVM源码分析之Java对象的创建过程》一文中分析了Java对象创建的整个过程。另外需要提一点的是当通过clone的方式复制一个对象的时候,如果当前类是一个finalizer类,那么在clone完成的时候将调用Finalizer.register方法进行注册。 一个构造函数执行的时候,会去调用父类的构造函数,主要是为了能对继承自父类的属性也能做初始化,那么任何一个对象的初始化最终都会调用到Object的空构造函数里,任何空的构造函数其实并不空,会含有三条字节码指令,如下:
0: aload_0
1: invokespecial #21 // Method java/lang/Object."":()V
4: return
为了不对所有的类的构造函数都做埋点调用Finalizer.register方法,hotspot的实现是在Object这个类在做初始化的时候将构造函数里的return指令替换为_return_register_finalizer指令,该指令并不是标准的字节码指令,是hotspot扩展的指令,这样在处理该指令的时候调用Finalizer.register方法,这样就在侵入性很小的情况下完美地在构造函数执行完毕后调用Finalizer.register。在JVM中通过JavaCalls::call触发register方法。
需要注意的是我们的类在被加载过程中其实就已经被标记为需要注册了(遍历所有方法,包括父类的方法,只要有一个非空的参数为空返回void的finalize方法并且方法体不为空,那么就注册)。
第三个疑问,add方法干啥的?看下源码:
private void add() {
synchronized (lock) {
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
}
unfinalized = this;
}
}
代码很简单,其实就是插入到Finalizer双向链中,而且是从头部插入的;Finalizer双向链里的对象和Finalizer类静态相关联,言外之意是在这个链里的对象都无法被gc掉,除非将这种引用关系剥离掉(因为Finalizer类无法被unload)。
那么问题来了,如何剥离Finalizer双向链呢?之前我们介绍了那个守护线程FinalizerThread,这个线程正在那里通过for死循环守株待兔地等着加入到引用队列的Finalizer实例,通过引用队列的remove方法将实例取出,并调用Finalizer实例的runFinalizer
方法,这个方法源码在前面已经贴出来了,这里需要仔细分析下runFinalizer
的源码,在源码中首先调用了remove
方法,看下源码:
private void remove() {
synchronized (lock) {
if (unfinalized == this) {
if (this.next != null) {
unfinalized = this.next;
} else {
unfinalized = this.prev;
}
}
if (this.next != null) {
this.next.prev = this.prev;
}
if (this.prev != null) {
this.prev.next = this.next;
}
this.next = this; /* Indicates that this has been finalized */
this.prev = this;
}
}
这个代码是不是很熟悉呢?没错,就是双向链表的删除节点的操作,这就把Finalizer对象从Finalizer对象链里剥离出来即意味着下次gc发生的时候就可能将其关联的finalizer对象gc掉了。最后调用了invokeFinalize方法,这个方法其实调用的就是Object的finalize方法。
如果在Finalizer对象的finalize方法里重新将当前对象赋值出去,变成可达对象,当这个Finalizer对象再次变成不可达的时候还会被执行finalize方法吗?答案是否定的,因为在执行完第一次finalize方法之后,这个finalizer对象已经和之前的Finalizer对象关系剥离了,也就是下次gc的时候不会再发现Finalizer对象指向该finalizer对象了,自然也就不会调用这个finalizer对象的finalize方法了。
当gc发生的时候,gc算法会判断对象是不是只被看门狗引用(对象被看门狗引用,然后放到Finalizer对象链里),如果这个对象仅仅被看门狗引用的时候,说明这个对象在不久的将来会被回收了现在可以执行它的finalize方法了,于是会将这个对象的看门狗放到Finalizer类的ReferenceQueue里,但是这个对象其实并没有被回收,因为看门狗还对他们持有引用,在gc完成之前,JVM会调用ReferenceQueue里的lock对象的notify方法(当ReferenceQueue为空的时候,FinalizerThread线程会调用ReferenceQueue的lock对象的wait方法直到被jvm唤醒),此时就会执行上面FinalizeThread线程里看到的其它逻辑了。
还有一个问题,护线程FinalizerThread在守株待兔地等着加入到引用队列的Finalizer实例,那么Finalizer实例是在什么情况下才会被插入到ReferenceQueue队列中?
这要看Finalizer的祖父类Reference的源码,它中定义了ReferenceHandler线程,实现如下:
private static class ReferenceHandler extends Thread {
private static void ensureClassInitialized(Class clazz) {
try {
Class.forName(clazz.getName(), true, clazz.getClassLoader());
} catch (ClassNotFoundException e) {
throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
}
}
static {
// pre-load and initialize InterruptedException and Cleaner classes
// so that we don't get into trouble later in the run loop if there's
// memory shortage while loading/initializing them lazily.
ensureClassInitialized(InterruptedException.class);
ensureClassInitialized(Cleaner.class);
}
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run() {
while (true) {
tryHandlePending(true);
}
}
}
static boolean tryHandlePending(boolean waitForNotify) {
Reference r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;
// 'instanceof' might throw OutOfMemoryError sometimes
// so do this before un-linking 'r' from the 'pending' chain...
c = r instanceof Cleaner ? (Cleaner) r : null;
// unlink 'r' from 'pending' chain
pending = r.discovered;
r.discovered = null;
} else {
// The waiting on the lock may cause an OutOfMemoryError
// because it may try to allocate exception objects.
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
// Give other threads CPU time so they hopefully drop some live references
// and GC reclaims some space.
// Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
// persistently throws OOME for some time...
Thread.yield();
// retry
return true;
} catch (InterruptedException x) {
// retry
return true;
}
// Fast path for cleaners
if (c != null) {
c.clean();
return true;
}
ReferenceQueue q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
这个线程在Reference类的static构造块中启动,并且被设置为高优先级Thread.MAX_PRIORITY
和daemon状态。此线程要做的事情,是不断的检查pending 是否为null,如果pending不为null,则将pending进行enqueue,否则线程进入wait状态。当pending被设置时,会调用ReferenceQueue的enqueue方法把Finalizer对象插入到ReferenceQueue队列中,接着通过notifyAll方法唤醒FinalizerThread线程执行后续逻辑,实现如下:
boolean enqueue(Reference r) { /* Called only by Reference class */
synchronized (lock) {
// Check that since getting the lock this reference hasn't already been
// enqueued (and even then removed)
ReferenceQueue queue = r.queue;
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
r.queue = ENQUEUED;
r.next = (head == null) ? r : head;
head = r;
queueLength++;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
那么问题来了,pending字段什么时候会被设置?
在GC过程的引用处理阶段,通过oopDesc::atomic_exchange_oop方法把发现的引用列表设置在pending字段所在的地址:
你可能还不太理解pending状态是什么意思,那么需要看下所有引用类的父类Reference
,这个类包含四个状态:
Active: queue = ReferenceQueue with which instance is registered, or ReferenceQueue.NULL if it was not registered with a queue; next = null.
Pending: queue = ReferenceQueue with which instance is registered; next = Following instance in queue, or this if at end of list.
Enqueued: queue = ReferenceQueue.ENQUEUED; next = Following instance in queue, or this if at end of list.
Inactive: queue = ReferenceQueue.NULL; next = this.
下面的表格引用自资料:
Reference State
queue
next
discovered
Active
ReferenceQueue(registered)或ReferenceQueue.NULL(unregistered)
NULL
next element in a discovered reference list by GC(or this if last)
Pending
ReferenceQueue(registered)
this
next element in the pending list(or NULL if last)
Enqueued
ReferenceQueue.ENQUEUED
next reference in queue(or this if last)
NULL
Inactive
ReferenceQueue.NULL
this
NULL
下面的图片同样引用自资料:
还有一点需要特别注意,那就是Finalizer的守护线程的优先级比主线程低,如果对象自己实现了finalize()方法,那么调用finalize()方法就会发生在主线程而非守护线程,主线程的操作会和我们的守护线程进行竞争,不过由于我们的守护线程的优先级较低,得到的CPU时间较少,因此它永远比主线程慢一个节拍,这时候主线程做了一些危险的操作就可能导致OOM。
所以,这里给出一个启示录:当你考虑使用finalize()方法而不是使用常规的JVM的gc方式来清理对象的时候,最好三思而后行;你可能会因为覆盖了finalize()这样的奇技淫巧而沾沾自喜,但是不停增长的Finalizer队列也许会撑爆你的年老代区域(tenured space),你需要考虑一下重新设计你的方案。参考文献
核心操作
用WeakHashMap的get、put、remove方法都会有一个副作用,即通过方法expungeStaleEntries
来清除无效key对应的Entry。
public V put(K key, V value) {
Object k = maskNull(key);
int h = hash(k);
Entry[] tab = getTable();
int i = indexFor(h, tab.length);
for (Entry e = tab[i]; e != null; e = e.next) {
if (h == e.hash && eq(k, e.get())) {
V oldValue = e.value;
if (value != oldValue)
e.value = value;
return oldValue;
}
}
modCount++;
Entry e = tab[i];
tab[i] = new Entry<>(k, value, queue, h, e);
if (++size >= threshold)
resize(tab.length * 2);
return null;
}
可以看到首先调用getTable
方法获取原来的hash桶。有人会问,原来的hash桶不就是成员变量table
吗,直接使用table
不就行了吗?您说的没错,但是原来的table
包含无效的key,那么就要先清理调。怎么清理呢?那么需要看下getTable
的源码:
private Entry[] getTable() {
expungeStaleEntries();
return table;
}
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry e = (Entry) x;
int i = indexFor(e.hash, table.length);
Entry prev = table[i];
Entry p = prev;
while (p != null) {
Entry next = p.next;
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
可以看到是使用expungeStaleEntries
进行清理的,要读懂expungeStaleEntries()
这个方法的源码,你必须明白引用队列queue
的作用,所有的失效的key都会放到这个引用队列中,因此用一个for循环取出队列里的key对象e,利用这个key的hash计算它的桶位i,用一个while循环遍历桶位i里的链表,找到和对象e相等的key,删掉并size--;
。
清理完无效的key,那么直接插入新的key就好了,这段插入的代码和Hashtable的代码是一模一样的,不清楚Hashtable的话可以看下这篇文章:Java集合源码分析-Hashtable,都是插入到链的头部,这和HashMap不太一样,HashMap是将新节点插入到链的尾部:Java集合源码分析-HashMap。插入完之后判断是否需要进行扩容,扩容是以2倍方式进行的,看下扩容方法resize
的源码:
void resize(int newCapacity) {
Entry[] oldTable = getTable();
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = newTable(newCapacity);
transfer(oldTable, newTable);
table = newTable;
/*
* If ignoring null elements and processing ref queue caused massive
* shrinkage, then restore old table. This should be rare, but avoids
* unbounded expansion of garbage-filled tables.
*/
if (size >= threshold / 2) {
threshold = (int)(newCapacity * loadFactor);
} else {
expungeStaleEntries();
transfer(newTable, oldTable);
table = oldTable;
}
}
源码中间有一段古怪的注释,注释前面的代码很好理解:首先以2倍容量新建一个数组newTable
,然后通过transfer
方法将老的hash桶的数据完全拷贝到新的hash桶newTable
,通俗易懂。但是注释下面的ifelse
代码是什么作用呢?这段代码是注释前面代码的逆操作,意思是说我后悔扩容了,为啥后悔了呢?跟size
这个变量有关吧,我需要节省点内存,就改变阈值threshold
,甚至如果size
不太大就干脆反向操作不扩容了,这波操作666呀,代码还可以反悔(注释后面的代码含义是我的猜测)。
public V get(Object key) {
Object k = maskNull(key);
int h = hash(k);
Entry[] tab = getTable();
int index = indexFor(h, tab.length);
Entry e = tab[index];
while (e != null) {
if (e.hash == h && eq(k, e.get()))
return e.value;
e = e.next;
}
return null;
}
对于get操作,需要注意的是调用了getTable
方法进行无效key的清理。
public V remove(Object key) {
Object k = maskNull(key);
int h = hash(k);
Entry[] tab = getTable();
int i = indexFor(h, tab.length);
Entry prev = tab[i];
Entry e = prev;
while (e != null) {
Entry next = e.next;
if (h == e.hash && eq(k, e.get())) {
modCount++;
size--;
if (prev == e)
tab[i] = next;
else
prev.next = next;
return e.value;
}
prev = e;
e = next;
}
return null;
}
对于remove操作,需要注意的是调用了getTable
方法进行无效key的清理。
遍历
WeakHashMap提供了三个迭代器进行遍历KeyIterator、ValueIterator、EntryIterator,这三个迭代器有一个共同的基类:HashIterator,所以遍历操作都是HashIterator操作的,遍历是从hash桶的尾部向头部进行的,看下源码:
public boolean hasNext() {
Entry[] t = table;
while (nextKey == null) {
Entry e = entry;
int i = index;
while (e == null && i > 0)
e = t[--i];
entry = e;
index = i;
if (e == null) {
currentKey = null;
return false;
}
nextKey = e.get(); // hold on to key in strong ref
if (nextKey == null)
entry = entry.next;
}
return true;
}
/** The common parts of next() across different types of iterators */
protected Entry nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextKey == null && !hasNext())
throw new NoSuchElementException();
lastReturned = entry;
entry = entry.next;
currentKey = nextKey;
nextKey = null;
return lastReturned;
}
有点不可思议的是,hasNext
方法有两个while循环,nextEntry
没有循环,这和我前面介绍的几个集合的遍历方式恰恰相反,比如HashMap的迭代器基类HashIterator,它的hasNext
方法没有循环,nextNode
只有一个while循环,由于HashMap的设计者极力保证链表的长度是1,所以这个while循环的时间复杂度可以认为是O(1),但是从源码上看,WeakHashMap遍历的时间复杂度有点复杂,如果遍历到链表的表尾(即(e = entry) == null
),那么时间复杂度达到了恐怖的。
文章结束了,知识点有点多,有错误的地方希望指出。
参考文献: 深入分析Object.finalize方法的实现原理-
你可能感兴趣的:(Java集合源码分析-WeakHashMap)
华为云专家出品《深入理解边缘计算》电子书上线
华为云PaaS服务小智
华为云 边缘计算 人工智能
华为开发者大会PaaS生态电子书推荐,助你成为了不起的开发者!什么是边缘计算?边缘计算的应用场景有哪些?华为云出品《深入理解边缘计算》电子书上线带你系统理解云、边、端协同的相关原理了解开源项目的源码分析流程学成能够对云、边、端主流开源实现进行定制开发!【适用人群】1.对云原生感兴趣的开发者2.对边缘计算有学习需求或想拓展业务之外开发技能的开发者【精彩导读】首先,介绍边缘计算概念、边缘计算系统具体组
Spring Bean 生命周期
金州小铁匠
spring python java
SpringBean生命周期一、Bean的创建方式与底层实现Spring中Bean的创建方式及其底层源码实现是理解生命周期的关键。以下是常见的Bean注册方式及其源码层面的核心逻辑:1.XML配置方式源码分析:解析阶段:XmlBeanDefinitionReader解析XML文件,生成BeanDefinition并注册到BeanFactory的beanDefinitionMap。注册逻辑:Defa
【Hive】学习路线:架构、运维、Hsql实战、源码分析
roman_日积跬步-终至千里
# hive hive 学习 架构
文章目录一.Hive基础学习1.基础知识2.安装与配置3.数据存储与表结构二.hive运维三.Hive实战1.HiveSQL基础2.高级查询与数据分析3.数据存储优化4.性能调优四.Hive源码分析一.Hive基础学习1.基础知识hive简介架构说明【hive-design】hive架构详解:描述了hive架构,hive主要组件的作用、hsql在hive执行过程中的底层细节、hive各组件作用2.
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_process_options
若云止水
nginx 运维
ngx_process_options声明在src\core\nginx.cstaticngx_int_tngx_process_options(ngx_cycle_t*cycle);定义在src\core\nginx.cstaticngx_int_tngx_process_options(ngx_cycle_t*cycle){u_char*p;size_tlen;if(ngx_prefix){l
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_atoi 函数
若云止水
nginx 运维
ngx_atoi声明在src/core/ngx_string.hngx_int_tngx_atoi(u_char*line,size_tn);定义在src/core/ngx_string.cngx_int_tngx_atoi(u_char*line,size_tn){ngx_int_tvalue,cutoff,cutlim;if(n==0){returnNGX_ERROR;}cutoff=NGX_
深入源码分析 kotlin的CoroutineExceptionHandler机制
古苏
kotlin android
启动一个协程,然后内部启动子协程,那么最内层如果发生异常,是怎么传递异常的?valrootExceptionHandler=CoroutineExceptionHandler{_,throwable->println("调用【根】协程异常处理器:${throwable.message}")}valparentExceptionHandler=CoroutineExceptionHandler{_,
Java 集合:单列集合和双列集合的深度剖析
刘小炮吖i
Java后端开发面试题 Java 集合 java
引言在Java编程中,集合是一个非常重要的概念。它就像是一个容器,能够存储多个数据元素,帮助我们更方便地管理和操作数据。Java集合框架主要分为单列集合和双列集合两大类,它们各自有着独特的特点和适用场景。接下来,让我们深入探究这两种集合。单列集合单列集合就像是一列整齐排列的数据队伍,每个元素都是独立的个体,按照一定的规则存储和组织。在Java中,单列集合的根接口是java.util.Collect
QT OpenGL高级编程
QT性能优化QT原理源码QT界面美化
qt qt6.3 qt5 QT教程 c++
QTOpenGL高级编程使用AI技术辅助生成QT界面美化视频课程QT性能优化视频课程QT原理与源码分析视频课程QTQMLC++扩展开发视频课程免费QT视频课程您可以看免费1000+个QT技术视频免费QT视频课程QT统计图和QT数据可视化视频免费看免费QT视频课程QT性能优化视频免费看免费QT视频课程QT界面美化视频免费看1QT_OpenGL基础1.1OpenGL简介1.1.1OpenGL简介Ope
QT 3D光照与阴影
QT性能优化QT原理源码QT界面美化
qt qt6.3 qt5 QT教程 c++ 3d
QT3D光照与阴影使用AI技术辅助生成QT界面美化视频课程QT性能优化视频课程QT原理与源码分析视频课程QTQMLC++扩展开发视频课程免费QT视频课程您可以看免费1000+个QT技术视频免费QT视频课程QT统计图和QT数据可视化视频免费看免费QT视频课程QT性能优化视频免费看免费QT视频课程QT界面美化视频免费看1QT_3D光照基础1.1光照概念与重要性1.1.1光照概念与重要性光照概念与重要性
Java集合框架与线程安全:深入解析与最佳实践
bdawn
java java 开发语言 集合 多线程 线程 安全 list
目录一、Java集合框架概览二、线程安全挑战与解决方案典型线程安全问题示例传统同步方案现代并发集合解析三、性能对比与选型策略基准测试数据(单位:ops/ms)选型决策树四、最佳实践与陷阱规避五、未来演进趋势结语一、Java集合框架概览Java集合框架(JavaCollectionsFramework)是Java开发者最常用的工具包之一,其核心接口构成清晰的层级体系:List接口:有序可重复集合Ar
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_crc32_table_init 函数
若云止水
nginx 运维
ngx_crc32_table_init声明在src/core/ngx_crc32.hngx_int_tngx_crc32_table_init(void);实现在src/core/ngx_crc32.cngx_int_tngx_crc32_table_init(void){void*p;if(((uintptr_t)ngx_crc32_table_short&~((uintptr_t)ngx_c
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_os_init 函数
若云止水
nginx 运维
ngx_os_init声明在src/os/unix/ngx_os.hngx_int_tngx_os_init(ngx_log_t*log);定义在src\os\unix\ngx_posix_init.cngx_int_tngx_os_init(ngx_log_t*log){ngx_time_t*tp;ngx_uint_tn;#if(NGX_HAVE_LEVEL1_DCACHE_LINESIZE)l
Spark源码分析
陈同学�
spark big data scala
Spark源码分析SparkonYarnclientCluster本质区别,driver位置不同1)有哪些不同得进程?2)分别有什么作用?3)Spark作业执行流程是什么样的跑yarn有--masteryarnCoarseGrainedExecutorBackend默认executor有两个CoarseGrainedExecutorBackendSparkSubmitApplicationMast
Spark源码分析 – Shuffle
weixin_34292924
大数据
参考详细探究Spark的shuffle实现,写的很清楚,当前设计的来龙去脉HadoopHadoop的思路是,在mapper端每次当memorybuffer中的数据快满的时候,先将memory中的数据,按partition进行划分,然后各自存成小文件,这样当buffer不断的spill的时候,就会产生大量的小文件所以Hadoop后面直到reduce之前做的所有的事情其实就是不断的merge,基于文件
【QT教程】QT6图形渲染与OpenGL编程 QT
QT性能优化QT原理源码QT界面美化
qt qt6.3 qt5 c++ QT教程
QT6图形渲染与OpenGL编程使用AI技术辅助生成QT界面美化视频课程QT性能优化视频课程QT原理与源码分析视频课程QTQMLC++扩展开发视频课程免费QT视频课程您可以看免费1000+个QT技术视频免费QT视频课程QT统计图和QT数据可视化视频免费看免费QT视频课程QT性能优化视频免费看免费QT视频课程QT界面美化视频免费看1QT6图形渲染机制1.1QT6图形渲染概述1.1.1QT6图形渲染概
2024年网络安全最全【玄机】常见攻击事件分析--钓鱼邮件,网络相关+网络安全三方库的源码分析+数据结构与算法
2401_84302583
程序员 网络安全 学习 面试
还有兄弟不知道网络安全面试可以提前刷题吗?费时一周整理的160+网络安全面试题,金九银十,做网络安全面试里的显眼包!王岚嵚工程师面试题(附答案),只能帮兄弟们到这儿了!如果你能答对70%,找一个安全工作,问题不大。对于有1-3年工作经验,想要跳槽的朋友来说,也是很好的温习资料!【完整版领取方式在文末!!】93道网络安全面试题内容实在太多,不一一截图了黑客学习资源推荐最后给大家分享一份全套的网络安全
Java集合类归纳+思维导图
web2u
Java 基础 java 开发语言
Java集合框架主要分为两大类:Collection接口和Map接口。Collection接口(存储对象)分为三大类:Set:HashSetLinkedHashSet(基于链表和哈希表)TreeSetQueue:PriorityQueue(基于优先级,元素按自然排序或指定比较器排序)LinkedList(作为队列使用)List:ArrayListLinkedListVectorMap接口(存储键值
责任链模式原理详解和源码实例以及Spring AOP拦截器链的执行源码如何使用责任链模式?
一个儒雅随和的男子
spring 设计模式 责任链模式 spring java
前言 本文首先介绍了责任链的基本原理,并附带一个例子说明责任链模式,确保能够理解责任链的前提下,在进行SpringAOP执行责任链的源码分析。责任链模式允许将多个处理对象连接成链,请求沿着链传递,直到被处理或结束。每个处理者可以选择处理请求或传递给下一个。 SpringAOP的拦截器链,拦截器或者过滤器链,都是典型的责任链应用。比如,当一个方法被调用时,多个拦截器按顺序执行,每个拦截器可以决定
Java——列表(List)
不会Hello World的小苗
Java java list python
概述在Java中,列表(List)是一种有序的集合,它允许元素重复,并且每个元素都有一个对应的索引值。Java提供了List接口及其实现类,用于表示和操作列表数据。常用的实现类包括ArrayList、LinkedList和Vector。1、List接口概述List是Java集合框架中的一种接口,继承自Collection接口。它定义了许多常见的操作,如:添加元素:add(Ee)、add(intin
OpenMetadata MySQL 数据库使用率提取管道实现解析
10年JAVA大数据技术研究者
数据治理 数据库 mysql openmetadata 源码分析
目录架构概述核心组件源码分析使用率指标定义数据提取流程图源码类图配置与扩展指南架构概述OpenMetadata通过可插拔的元数据摄取框架实现对MySQL使用率数据的采集,核心流程包含三个阶段:数据采集层:从MySQLperformance_schema和sysschema获取原始指标指标处理层:将原始数据转换为统一的使用率指标模型数据存储层:将处理后的指标持久化到OpenMetadata服务核心组
Python3.5源码分析-sys模块及site模块导入
小屋子大侠
python Python分析 python源码
Python3源码分析本文环境python3.5.2。参考书籍>python官网Python3的sys模块初始化根据分析完成builtins初始化后,继续分析sys模块的初始化,继续分析_Py_InitializeEx_Private函数的执行,void_Py_InitializeEx_Private(intinstall_sigs,intinstall_importlib){...sysmod=
吐血整理Java集合框架,免费送
聪明马的博客
Java java 数据结构
Java集合框架(JavaCollectionsFramework)是Java标准库中的一个重要部分。它为Java开发人员提供了一组常用的数据结构,如列表、集合、映射等,使其更容易地处理数据。在这篇博客中,我将详细介绍Java集合框架,包括它的主要特点、常用的集合类型以及如何使用它们来解决实际问题。一、Java集合框架的主要特点Java集合框架的主要特点是:统一的接口。Java集合框架提供了一组统
Java集合之ArrayList(含源码解析 超详细)
&星辰入梦来&
Java集合 java python 开发语言
1.ArrayList简介ArrayList的底层是数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加ArrayList实例的容量。这可以减少递增式再分配的数量。ArrayList继承于AbstructList,实现了List,RandomAccess,Cloneable,Java.io.Serializa
深入理解Java的集合框架
一碗黄焖鸡三碗米饭
java
深入理解Java的集合框架Java集合框架(JavaCollectionsFramework,简称JCF)是Java语言中最常用的API之一,它为开发者提供了强大且灵活的数据结构支持。集合框架通过一系列的接口和实现类,帮助我们管理、存储和操作数据。Java集合框架包括常见的List、Set、Map等接口及其具体实现类,合理选择适当的集合类型,对于程序性能和代码可维护性至关重要。本文将深入解析Jav
Android从源码分析handler.post(runnable),view.post(runnable),runOnUiThread(runnable)执行时机
听者110
Android高级开发系列笔记 Android 线程
大家好,我是听者,耳听心受的听,孙行者的者,感谢大家阅读我的文章。废话不说直接进入主题,不管是Android还是其他语言,线程之间通信都是一个比较“头疼”问题,开发Android的码农应该都知道回到主线程的方式有handler.post(runnable),view.post(runnable),runOnUiThread(runnable)。但是这三种方式的区别以及其执行的时机如何呢?今天就给大
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_localtime 函数
若云止水
nginx 运维
ngx_localtime函数声明在src\os\unix\ngx_time.h中:voidngx_localtime(time_ts,ngx_tm_t*tm);定义在src/os/unix/ngx_time.c中voidngx_localtime(time_ts,ngx_tm_t*tm){#if(NGX_HAVE_LOCALTIME_R)(void)localtime_r(&s,tm);#els
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_pnalloc函数
若云止水
nginx 运维
ngx_pnalloc声明在src\core\ngx_palloc.hvoid*ngx_pnalloc(ngx_pool_t*pool,size_tsize);定义在src\core\ngx_palloc.cvoid*ngx_pnalloc(ngx_pool_t*pool,size_tsize){#if!(NGX_DEBUG_PALLOC)if(sizemax){returnngx_palloc_
十四、Flink源码阅读--JobGraph生成过程
灰二和杉菜
Apache Flink Flink JobGraph生成源码分析
上篇分析了client整个提交任务过程,最终提交的是一个JobGraph对象,那么是如何从jar或sql任务转为JobGraph的呢,这篇我们仔细研究一下,版本为1.6.3源码分析上篇我们介绍client端提交任务最终会到到ClusterClient.run()方法,就在这个方法中封装了JobGraph的步骤。publicJobSubmissionResultrun(FlinkPlancompil
RocketMQ源码分析-Rpc通信模块(remoting)二
吃水果的猪儿虫
java-rocketmq rocketmq rpc
前言今天继续RocketMQ-Rpc通信模块(remoting)的源码分析。上一章提到了主要的start()方法执行流程,如果有不清楚的地方可以一起讨论哈,这篇文章会继续解读主要方法,按照惯例先看看NettyRemotingAbstract的类图,看类图知方法。和NettyEventExecutor以及MQ的交互流程。按照惯例先看看NettyRemotingAbstract的类图,看类图知方法,文
QT 3D渲染技术详解
QT性能优化QT原理源码QT界面美化
qt 3d qt6.3 qt5 c++ QT教程
QT3D渲染技术详解使用AI技术辅助生成QT界面美化视频课程QT性能优化视频课程QT原理与源码分析视频课程QTQMLC++扩展开发视频课程免费QT视频课程您可以看免费1000+个QT技术视频免费QT视频课程QT统计图和QT数据可视化视频免费看免费QT视频课程QT性能优化视频免费看免费QT视频课程QT界面美化视频免费看1QT_3D渲染技术概述1.13D渲染技术简介1.1.13D渲染技术简介3D渲染技
Nginx负载均衡
510888780
nginx 应用服务器
Nginx负载均衡一些基础知识:
nginx 的 upstream目前支持 4 种方式的分配
1)、轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
2)、weight
指定轮询几率,weight和访问比率成正比
RedHat 6.4 安装 rabbitmq
bylijinnan
erlang rabbitmq redhat
在 linux 下安装软件就是折腾,首先是测试机不能上外网要找运维开通,开通后发现测试机的 yum 不能使用于是又要配置 yum 源,最后安装 rabbitmq 时也尝试了两种方法最后才安装成功
机器版本:
[root@redhat1 rabbitmq]# lsb_release
LSB Version: :base-4.0-amd64:base-4.0-noarch:core
FilenameUtils工具类
eksliang
FilenameUtils common-io
转载请出自出处:http://eksliang.iteye.com/blog/2217081 一、概述
这是一个Java操作文件的常用库,是Apache对java的IO包的封装,这里面有两个非常核心的类FilenameUtils跟FileUtils,其中FilenameUtils是对文件名操作的封装;FileUtils是文件封装,开发中对文件的操作,几乎都可以在这个框架里面找到。 非常的好用。
xml文件解析SAX
不懂事的小屁孩
xml
xml文件解析:xml文件解析有四种方式,
1.DOM生成和解析XML文档(SAX是基于事件流的解析)
2.SAX生成和解析XML文档(基于XML文档树结构的解析)
3.DOM4J生成和解析XML文档
4.JDOM生成和解析XML
本文章用第一种方法进行解析,使用android常用的DefaultHandler
import org.xml.sax.Attributes;
通过定时任务执行mysql的定期删除和新建分区,此处是按日分区
酷的飞上天空
mysql
使用python脚本作为命令脚本,linux的定时任务来每天定时执行
#!/usr/bin/python
# -*- coding: utf8 -*-
import pymysql
import datetime
import calendar
#要分区的表
table_name = 'my_table'
#连接数据库的信息
host,user,passwd,db =
如何搭建数据湖架构?听听专家的意见
蓝儿唯美
架构
Edo Interactive在几年前遇到一个大问题:公司使用交易数据来帮助零售商和餐馆进行个性化促销,但其数据仓库没有足够时间去处理所有的信用卡和借记卡交易数据
“我们要花费27小时来处理每日的数据量,”Edo主管基础设施和信息系统的高级副总裁Tim Garnto说道:“所以在2013年,我们放弃了现有的基于PostgreSQL的关系型数据库系统,使用了Hadoop集群作为公司的数
spring学习——控制反转与依赖注入
a-john
spring
控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心。 控制反转一般分为两种类型,依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。依赖注入应用比较广泛。
用spool+unixshell生成文本文件的方法
aijuans
xshell
例如我们把scott.dept表生成文本文件的语句写成dept.sql,内容如下:
set pages 50000;
set lines 200;
set trims on;
set heading off;
spool /oracle_backup/log/test/dept.lst;
select deptno||','||dname||','||loc
1、基础--名词解析(OOA/OOD/OOP)
asia007
学习基础知识
OOA:Object-Oriented Analysis(面向对象分析方法)
是在一个系统的开发过程中进行了系统业务调查以后,按照面向对象的思想来分析问题。OOA与结构化分析有较大的区别。OOA所强调的是在系统调查资料的基础上,针对OO方法所需要的素材进行的归类分析和整理,而不是对管理业务现状和方法的分析。
OOA(面向对象的分析)模型由5个层次(主题层、对象类层、结构层、属性层和服务层)
浅谈java转成json编码格式技术
百合不是茶
json编码 java转成json编码
json编码;是一个轻量级的数据存储和传输的语言
在java中需要引入json相关的包,引包方式在工程的lib下就可以了
JSON与JAVA数据的转换(JSON 即 JavaScript Object Natation,它是一种轻量级的数据交换格式,非
常适合于服务器与 JavaScript 之间的数据的交
web.xml之Spring配置(基于Spring+Struts+Ibatis)
bijian1013
java web.xml SSI spring配置
指定Spring配置文件位置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring-dao-bean.xml,/WEB-INF/spring-resources.xml,
/WEB-INF/
Installing SonarQube(Fail to download libraries from server)
sunjing
Install Sonar
1. Download and unzip the SonarQube distribution
2. Starting the Web Server
The default port is "9000" and the context path is "/". These values can be changed in &l
【MongoDB学习笔记十一】Mongo副本集基本的增删查
bit1129
mongodb
一、创建复本集
假设mongod,mongo已经配置在系统路径变量上,启动三个命令行窗口,分别执行如下命令:
mongod --port 27017 --dbpath data1 --replSet rs0
mongod --port 27018 --dbpath data2 --replSet rs0
mongod --port 27019 -
Anychart图表系列二之执行Flash和HTML5渲染
白糖_
Flash
今天介绍Anychart的Flash和HTML5渲染功能
HTML5
Anychart从6.0第一个版本起,已经逐渐开始支持各种图的HTML5渲染效果了,也就是说即使你没有安装Flash插件,只要浏览器支持HTML5,也能看到Anychart的图形(不过这些是需要做一些配置的)。
这里要提醒下大家,Anychart6.0版本对HTML5的支持还不算很成熟,目前还处于
Laravel版本更新异常4.2.8-> 4.2.9 Declaration of ... CompilerEngine ... should be compa
bozch
laravel
昨天在为了把laravel升级到最新的版本,突然之间就出现了如下错误:
ErrorException thrown with message "Declaration of Illuminate\View\Engines\CompilerEngine::handleViewException() should be compatible with Illuminate\View\Eng
编程之美-NIM游戏分析-石头总数为奇数时如何保证先动手者必胜
bylijinnan
编程之美
import java.util.Arrays;
import java.util.Random;
public class Nim {
/**编程之美 NIM游戏分析
问题:
有N块石头和两个玩家A和B,玩家A先将石头随机分成若干堆,然后按照BABA...的顺序不断轮流取石头,
能将剩下的石头一次取光的玩家获胜,每次取石头时,每个玩家只能从若干堆石头中任选一堆,
lunce创建索引及简单查询
chengxuyuancsdn
查询 创建索引 lunce
import java.io.File;
import java.io.IOException;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Docume
[IT与投资]坚持独立自主的研究核心技术
comsci
it
和别人合作开发某项产品....如果互相之间的技术水平不同,那么这种合作很难进行,一般都会成为强者控制弱者的方法和手段.....
所以弱者,在遇到技术难题的时候,最好不要一开始就去寻求强者的帮助,因为在我们这颗星球上,生物都有一种控制其
flashback transaction闪回事务查询
daizj
oracle sql 闪回事务
闪回事务查询有别于闪回查询的特点有以下3个:
(1)其正常工作不但需要利用撤销数据,还需要事先启用最小补充日志。
(2)返回的结果不是以前的“旧”数据,而是能够将当前数据修改为以前的样子的撤销SQL(Undo SQL)语句。
(3)集中地在名为flashback_transaction_query表上查询,而不是在各个表上通过“as of”或“vers
Java I/O之FilenameFilter类列举出指定路径下某个扩展名的文件
游其是你
FilenameFilter
这是一个FilenameFilter类用法的例子,实现的列举出“c:\\folder“路径下所有以“.jpg”扩展名的文件。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
C语言学习五函数,函数的前置声明以及如何在软件开发中合理的设计函数来解决实际问题
dcj3sjt126com
c
# include <stdio.h>
int f(void) //括号中的void表示该函数不能接受数据,int表示返回的类型为int类型
{
return 10; //向主调函数返回10
}
void g(void) //函数名前面的void表示该函数没有返回值
{
//return 10; //error 与第8行行首的void相矛盾
}
in
今天在测试环境使用yum安装,遇到一个问题: Error: Cannot retrieve metalink for repository: epel. Pl
dcj3sjt126com
centos
今天在测试环境使用yum安装,遇到一个问题:
Error: Cannot retrieve metalink for repository: epel. Please verify its path and try again
处理很简单,修改文件“/etc/yum.repos.d/epel.repo”, 将baseurl的注释取消, mirrorlist注释掉。即可。
&n
单例模式
shuizhaosi888
单例模式
单例模式 懒汉式
public class RunMain {
/**
* 私有构造
*/
private RunMain() {
}
/**
* 内部类,用于占位,只有
*/
private static class SingletonRunMain {
priv
Spring Security(09)——Filter
234390216
Spring Security
Filter
目录
1.1 Filter顺序
1.2 添加Filter到FilterChain
1.3 DelegatingFilterProxy
1.4 FilterChainProxy
1.5
公司项目NODEJS实践0.1
逐行分析JS源代码
mongodb nginx ubuntu nodejs
一、前言
前端如何独立用nodeJs实现一个简单的注册、登录功能,是不是只用nodejs+sql就可以了?其实是可以实现,但离实际应用还有距离,那要怎么做才是实际可用的。
网上有很多nod
java.lang.Math
liuhaibo_ljf
java Math lang
System.out.println(Math.PI);
System.out.println(Math.abs(1.2));
System.out.println(Math.abs(1.2));
System.out.println(Math.abs(1));
System.out.println(Math.abs(111111111));
System.out.println(Mat
linux下时间同步
nonobaba
ntp
今天在linux下做hbase集群的时候,发现hmaster启动成功了,但是用hbase命令进入shell的时候报了一个错误 PleaseHoldException: Master is initializing,查看了日志,大致意思是说master和slave时间不同步,没办法,只好找一种手动同步一下,后来发现一共部署了10来台机器,手动同步偏差又比较大,所以还是从网上找现成的解决方
ZooKeeper3.4.6的集群部署
roadrunners
zookeeper 集群 部署
ZooKeeper是Apache的一个开源项目,在分布式服务中应用比较广泛。它主要用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步、集群管理、配置文件管理、同步锁、队列等。这里主要讲集群中ZooKeeper的部署。
1、准备工作
我们准备3台机器做ZooKeeper集群,分别在3台机器上创建ZooKeeper需要的目录。
数据存储目录
Java高效读取大文件
tomcat_oracle
java
读取文件行的标准方式是在内存中读取,Guava 和Apache Commons IO都提供了如下所示快速读取文件行的方法: Files.readLines(new File(path), Charsets.UTF_8); FileUtils.readLines(new File(path)); 这种方法带来的问题是文件的所有行都被存放在内存中,当文件足够大时很快就会导致
微信支付api返回的xml转换为Map的方法
xu3508620
xml map 微信api
举例如下:
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><