主頁 > 後端開發 > 【Java】 DirectByteBuffer堆外記憶體回收

【Java】 DirectByteBuffer堆外記憶體回收

2022-10-09 07:32:09 後端開發

PhantomReference虛參考

在分析堆外記憶體回收之前,先了解下PhantomReference虛參考,

PhantomReference需要與ReferenceQueue參考佇列結合使用,在GC進行垃圾回收的時候,如果發現一個物件只有虛參考在參考它,則認為該物件需要被回收,會將參考該物件的虛參考加入到與其關聯的ReferenceQueue佇列中,開發者可以通過ReferenceQueue獲取需要被回收的物件,然后做一些清理操作,從佇列中獲取過的元素會從佇列中清除,之后GC就可以對該物件進行回收,

虛參考提供了一種追蹤物件垃圾回收狀態的機制,讓開發者知道哪些物件準備進行回收,在回收之前開發者可以進行一些清理作業,之后GC就可以將物件進行真正的回收,

來看一個虛參考的使用例子:

  1. 創建一個ReferenceQueue佇列queue,用于跟蹤物件的回收;
  2. 創建一個obj物件,通過new創建的是強參考,只要強參考存在,物件就不會被回收;
  3. 創建一個虛參考PhantomReference,將obj物件和ReferenceQueue佇列傳入,此時phantomReference里面參考了obj物件,并關聯著參考佇列queue;
  4. 同樣的方式創建另一個obj1物件和虛參考物件phantomReference1;
  5. 將obj置為NULL,此時強參考關系失效;
  6. 呼叫 System.gc()進行垃圾回收;
  7. 由于obj的強參考關系失效,所以GC認為該物件需要被回收,會將參考該物件的虛參考phantomReference物件放入到與其關聯的參考佇列queue中;
  8. 通過poll從參考佇列queue中獲取物件,可以發現會獲取到phantomReference物件,poll獲取之后會將物件從參考佇列中洗掉,之后會被垃圾回收器回收;
  9. obj1的強參考關系還在,所以從queue中并不會獲取到;
   public static void main(String[] args) {
        // 創建參考佇列
        ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
        // 創建obj物件
        Object obj = new Object();
        // 創建虛參考,虛參考參考了obj物件,并與queue進行關聯
        PhantomReference<Object> phantomReference = new PhantomReference<Object>(obj, queue);
        // 創建obj1物件
        Object obj1 = new Object();
        PhantomReference<Object> phantomReference1 = new PhantomReference<Object>(obj1, queue);
        // 將obj置為NULL,強參考關系失效
        obj = null;
        // 垃圾回收
        System.gc();
        // 從參考佇列獲取物件
        Object o = queue.poll();
        if (null != o) {
            System.out.println(o.toString());
        }
    }

輸出結果:

java.lang.ref.PhantomReference@277c0f21

Reference實體的幾種狀態

Active:初始狀態,創建一個Reference型別的實體之后處于Active狀態,以上面虛參考為例,通過new創建一個PhantomReference虛參考物件之后,虛參考物件就處于Active狀態,

Pending:當GC檢測到物件的可達性發生變化時,會根據是否關聯了參考佇列來決定是否將狀態更改為Pending或者Inactive,虛參考必須與參考佇列結合使用,所以對于虛參考來說,如果它實際參考的物件需要被回收,垃圾回收器會將這個虛參考物件加入到一個Pending串列中,此時處于Pending狀態,

同樣以上面的的虛參考為例,因為obj的強參考關系失效,GC就會把參考它的虛參考物件放入到pending串列中,

Enqueued:表示參考物件被加入到了參考佇列,Reference有一個后臺執行緒去檢測是否有處于Pending狀態的參考物件,如果有會將參考物件加入到與其關聯的參考佇列中,此時由Pending轉為Enqueued狀態表示物件已加入到參考佇列中,

Inactive:通過參考佇列的poll方法可以從參考佇列中獲取參考物件,同時參考物件會從佇列中移除,此時參考物件處于Inactive狀態,之后會被GC回收,

DirectByteBuffer堆外記憶體回收

DirectByteBuffer的建構式中,在申請記憶體之前,先呼叫了BitsreserveMemory方法回收記憶體,申請記憶體之后,呼叫Cleanercreate方法創建了一個Cleaner物件,并傳入了當前物件(DirectByteBuffer)和一個Deallocator型別的物件:

class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer {
    private final Cleaner cleaner;
        
    DirectByteBuffer(int cap) {                   // package-private
        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        // 清理記憶體
        Bits.reserveMemory(size, cap);
        long base = 0;
        try {
            // 分配記憶體
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
        // 創建Cleader,傳入了當前物件和Deallocator
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;
    }
}

Cleaner從名字上可以看出與清理有關,BitsreserveMemory方法底層也是通過Cleaner來進行清理,所以Cleaner是重點關注的類,

DeallocatorDirectByteBuffer的一個內部類,并且實作了Runnable介面,在run方法中可以看到對記憶體進行了釋放,接下來就去看下在哪里觸發Deallocator任務的執行:

class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer {

    private static class Deallocator implements Runnable {
        // ...
        
        private Deallocator(long address, long size, int capacity) {
            assert (address != 0);
            this.address = address; // 設定記憶體地址
            this.size = size;
            this.capacity = capacity;
        }

        public void run() {
            if (address == 0) {
                // Paranoia
                return;
            }
            // 釋放記憶體
            unsafe.freeMemory(address);
            address = 0;
            Bits.unreserveMemory(size, capacity);
        }

    }
}

Cleaner

Cleaner繼承了PhantomReferencePhantomReferenceReference的子類,所以Cleaner是一個虛參考物件

創建Cleaner

虛參考需要與參考佇列結合使用,所以在Cleaner中可以看到有一個ReferenceQueue,它是一個靜態的變數,所以創建的所有Cleaner物件都會共同使用這個參考佇列

在創建Cleaner的create方法中,處理邏輯如下:

  1. 通過建構式創建了一個Cleaner物件,建構式中的referent引數為DirectByteBuffer,thunk引數為Deallocator物件,在建構式中又呼叫了父類的建構式完成實體化;
  2. 呼叫add方法將創建的Cleaner物件加入到鏈表中,添加到鏈表的時候使用的是頭插法,新加入的節點放在鏈表的頭部,first成員變數是一個靜態變數,它指向鏈表的頭結點,創建的Cleaner都會加入到這個鏈表中;

創建后的Cleaner物件處于Active狀態,

 public class Cleaner extends PhantomReference<Object>{

    // ReferenceQueue佇列
    private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();

    // 靜態變數,鏈表的頭結點,創建的Cleaner都會加入到這個鏈表中
    static private Cleaner first = null;
     
    // thunk
    private final Runnable thunk;
     
    public static Cleaner create(Object ob, Runnable thunk) {
        if (thunk == null)
            return null;
        // 創建一個Cleaner并加入鏈表
        return add(new Cleaner(ob, thunk));
    }
    
    private Cleaner(Object referent, Runnable thunk) {
        super(referent, dummyQueue); // 呼叫父類建構式,傳入參考物件和參考佇列
        this.thunk = thunk; // thunk指向傳入的Deallocator
    }
     
    private static synchronized Cleaner add(Cleaner cl) {
        // 如果頭結點不為空
        if (first != null) {
            // 將新加入的節點作為頭結點
            cl.next = first; 
            first.prev = cl;
        }
        first = cl;
        return cl;
    }
}

Cleaner呼叫父類建構式時,最侄訓進入到父類Reference中的建構式中:

referent:指向實際的參考物件,上面創建的是DirectByteBuffer,所以這里指向的是DirectByteBuffer

queue:參考佇列,指向Cleaner中的參考佇列dummyQueue

public class PhantomReference<T> extends Reference<T> {
    // ...
    
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q); // 呼叫父類建構式
    }

}

public abstract class Reference<T> {
    /* 參考物件 */
    private T referent;         
    // 參考佇列
    volatile ReferenceQueue<? super T> queue;
    
    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        // 設定參考佇列
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }

}

啟動ReferenceHandler執行緒

Reference中有一個靜態方法,里面創建了一個ReferenceHandler并設定為守護執行緒,然后啟動了該執行緒,并創建了JavaLangRefAccess物件設定到SharedSecrets中:

public abstract class Reference<T> {
    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        // 創建ReferenceHandler
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        // 設定優先級為最高
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();

        // 這里設定了JavaLangRefAccess
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                // 呼叫了tryHandlePending
                return tryHandlePending(false);
            }
        });
    }
}

ReferenceHandlerReference的內部類,繼承了Thread,在run方法中開啟了一個回圈,不斷的執行tryHandlePending方法,處理Reference中pending串列:

public abstract class Reference<T> {
    
    private static class ReferenceHandler extends Thread {
        
        // ...
        
        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }

        public void run() {
            while (true) {
                // 處理pending串列
                tryHandlePending(true);
            }
        }
    }
 }

Cleaner會啟動一個優先級最高的守護執行緒,不斷呼叫tryHandlePending來檢測是否有需要回收的參考物件(還未進行真正的回收),然后進行處理,

處理pending串列

垃圾回收器會將要回收的參考物件放在Referencepending變數中,從資料型別上可以看出pending只是一個Reference型別的物件,并不是一個list,如果有多個需要回收的物件,如何將它們全部放入pending物件中?

可以把pengding看做是一個鏈表的頭結點,假如有參考物件被判定需要回收,如果pengding為空直接放入即可,如果不為空,將使用頭插法將新的物件加入到鏈表中,也就是將新物件的discovered指向pending物件,然后將pending指向當前要回收的這個物件,這樣就形成了一個鏈表,pending指向鏈表的頭結點,

在pending鏈表中的參考物件處于pending狀態,

接下來看tryHandlePending方法的處理邏輯:

  1. 如果pending不為空,表示有需要回收的物件,此時將pengding指向的物件放在臨時變數r中,并判斷是否是Cleaner型別,如果是將其強制轉為Cleaner,記錄在臨時變數c中,接著更新pending的值為r的discovered,因為discovered中記錄了下一個需要被回收的物件,pengding需要指向下一個需要被回收的物件;

    pending如果為NULL,會進入到else的處理邏輯,回傳值為引數傳入的waitForNotify的值,

  2. 判斷Cleaner物件是否為空,如果不為空,呼叫Cleaner的clean方法進行清理

  3. 獲取參考物件關聯的參考佇列,然后呼叫enqueue方法將參考物件加入到參考佇列中

  4. 回傳true;

public abstract class Reference<T> {
  
    // 指向pending串列中的下一個節點
    transient private Reference<T> discovered; 

    // 靜態變數pending串列,可以看做是一個鏈表,pending指向鏈表的頭結點
    private static Reference<Object> pending = null;
  
    static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
                // 如果pending不為空
                if (pending != null) {
                    // 獲取pending執行的物件
                    r = pending;
                    // 如果是Cleaner型別
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    // 將pending指向下一個節點
                    pending = r.discovered;
                    // 將discovered置為空
                    r.discovered = null;
                } else {
                    // 等待
                    if (waitForNotify) {
                        lock.wait();
                    }
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            Thread.yield();
            // retry
            return true;
        } catch (InterruptedException x) {
            // retry
            return true;
        }
        if (c != null) {
            // 呼叫clean方法進行清理
            c.clean();
            return true;
        }
        // 獲取參考佇列
        ReferenceQueue<? super Object> q = r.queue;
        // 如果佇列不為空,將物件加入到參考佇列中
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        // 回傳true
        return true;
    }
}
釋放記憶體

Cleaner的clean方法中,可以看到,呼叫了thunk的run方法,前面內容可知,thunk指向的是Deallocator物件,所以會執行Deallocator的run方法,Deallocator的run方法前面也已經看過,里面會對DirectByteBuffer的堆外記憶體進行釋放

public class Cleaner extends PhantomReference<Object> {

    public void clean() {
        if (!remove(this))
            return;
        try {
            // 呼叫run方法
            thunk.run();
        } catch (final Throwable x) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        if (System.err != null)
                            new Error("Cleaner terminated abnormally", x)
                                .printStackTrace();
                        System.exit(1);
                        return null;
                    }});
        }
    }
}

總結

Cleaner是一個虛參考,它實際參考的物件DirectByteBuffer如果被GC判定為需要回收,會將參考該物件的Cleaner加入到pending串列,ReferenceHandler執行緒會不斷檢測pending是否為空,如果不為空,就對其進行處理:

  1. 如果物件型別為Cleaner,就呼叫Cleaner的clean方法進行清理,Cleaner的clean方法又會呼叫Deallocator的run方法,里面呼叫了freeMemory方法對DirectByteBuffer分配的堆外記憶體進行釋放;
  2. 將Cleaner物件加入到與其關聯的參考佇列中;

參考佇列

ReferenceQueue名字聽起來是一個佇列,實際使用了一個鏈表,使用頭插法將加入的節點串起來,ReferenceQueue中的head變數指向鏈表的頭節點,每個節點是一個Reference型別的物件:

public class ReferenceQueue<T> {

    // head為鏈表頭節點
    private volatile Reference<? extends T> head = null;
}

Reference中除了discovered變數之外,還有一個next變數,discovered指向的是處于pending狀態時pending串列中的下一個元素,next變數指向的是處于Enqueued狀態時,參考佇列中的下一個元素:

public abstract class Reference<T> {

    /* When active:   處于active狀態時為NULL
     *     pending:   this
     *    Enqueued:   Enqueued狀態時,指向參考佇列中的下一個元素
     *    Inactive:   this
     */
    @SuppressWarnings("rawtypes")
    Reference next;
    
    /* When active:   active狀態時,指向GC維護的一個discovered鏈表中的下一個元素
     *     pending:   pending狀態時,指向pending串列中的下一個元素
     *   otherwise:   其他情況為NULL
     */
    transient private Reference<T> discovered;  /* used by VM */
}

enqueue入隊

進入參考佇列中的參考物件處于enqueue狀態,

enqueue的處理邏輯如下:

  1. 判斷要加入的物件關聯的參考佇列,對佇列進行判斷,如果佇列為慷訓者佇列等于ReferenceQueue中的空佇列ENQUEUED,表示該物件之前已經加入過佇列,不能重復操作,回傳false,如果未加入過繼續下一步;
  2. 將物件所關聯的參考佇列置為ENQUEUED,它是一個空佇列,表示節點已經加入到佇列中;
  3. 判斷頭節點是否為空,如果為空,表示鏈表還沒有節點,將當前物件的next指向自己,如果頭結點不為空,將當前物件的next指向頭結點,然后更新頭結點的值為當前物件(頭插法插入鏈表);
  4. 增加佇列的長度,也就是鏈表的長度;
public class ReferenceQueue<T> {

    // 空佇列
    static ReferenceQueue<Object> ENQUEUED = new Null<>();
    
    // 入隊,將節點加入參考佇列,佇列實際上是一個鏈表
    boolean enqueue(Reference<? extends T> r) {
        synchronized (lock) {
            // 獲取關聯的參考佇列
            ReferenceQueue<?> queue = r.queue;
            // 如果為慷訓者已經添加到過佇列
            if ((queue == NULL) || (queue == ENQUEUED)) {
                return false;
            }
            assert queue == this;
            // 將參考佇列置為一個空佇列,表示該節點已經入隊
            r.queue = ENQUEUED;
            // 如果頭結點為空將下一個節點置為自己,否則將next置為鏈表的頭結點,可以看出同樣使用的是頭插法將節點插入鏈表
            r.next = (head == null) ? r : head;
            // 更新頭結點為當前節點
            head = r;
            // 增加長度
            queueLength++;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(1);
            }
            lock.notifyAll();
            return true;
        }
    }
}
poll出隊

在呼叫poll方法從參考佇列中獲取一個元素并出隊的時候,首先對head頭結點進行判空,如果為空表示參考佇列中沒有資料,回傳NULL,否則呼叫reallyPoll從參考佇列中獲取元素,

出隊的處理邏輯如下:

  1. 獲取鏈表中的第一個節點也就是頭結點,如果不為空進行下一步;

  2. 如果頭節點的下一個節點是自己,表示鏈表只有一個節點,頭結點出隊之后鏈表為空,所以將頭結點的值更新為NULL;

    如果頭節點的下一個節點不是自己,表示鏈表中還有其他節點,更新head頭節點的值為下一個節點,也就是next指向的物件;

  3. 將需要出隊的節點的參考佇列置為NULL,next節點置為自己,表示節點已從佇列中洗掉;

  4. 參考佇列的長度減一;

  5. 回傳要出隊的節點;

從出隊的邏輯中可以看出,參考佇列中的物件是后進先出的,poll出隊之后的參考物件處于Inactive狀態,表示可以被GC回收掉,

public class ReferenceQueue<T> {
    /**
     * 從參考佇列中獲取一個節點,進行出隊操作
     */
    public Reference<? extends T> poll() {
        // 如果頭結點為空,表示沒有資料 
        if (head == null)
            return null;
        synchronized (lock) {
            return reallyPoll();
        }
    }
    
    @SuppressWarnings("unchecked")
    private Reference<? extends T> reallyPoll() {     、  /* Must hold lock */
        // 獲取頭結點
        Reference<? extends T> r = head;
        if (r != null) {
            // 如果頭結點的下一個節點是自己,表示鏈表只有一個節點,head置為null,否則head值為r的下一個節點,也就是next指向的物件
            head = (r.next == r) ?
                null :
                r.next;
            // 將參考佇列置為NULL
            r.queue = NULL;
            // 下一個節點置為自己
            r.next = r;
            // 長度減一
            queueLength--;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(-1);
            }
            // 回傳鏈表中的第一個節點
            return r;
        }
        return null;
    }
}

reserveMemory記憶體清理

最開始在DirectByteBuffer的建構式中看到申請記憶體之前會呼叫Bits的reserveMemory方法,如果沒有足夠的記憶體,它會從SharedSecrets獲取JavaLangRefAccess物件進行一些處理,由前面的內容可知,Reference中的靜態方法啟動ReferenceHandler之后,創建了JavaLangRefAccess并設定到SharedSecrets中,所以這里呼叫JavaLangRefAccesstryHandlePendingReference實際上依舊呼叫的是Reference中的tryHandlePending方法,

在呼叫Reference中的tryHandlePending方法處理需要回收的物件之后,呼叫tryReserveMemory方法判斷是否有足夠的記憶體,如果記憶體依舊不夠,會呼叫` System.gc()觸發垃圾回收,然后開啟一個回圈,處理邏輯如下:

  1. 判斷記憶體是否充足,如果充足直接回傳;

  2. 判斷睡眠次數是否小于限定的最大值,如果小于繼續下一步,否則終止回圈;

  3. 呼叫tryHandlePendingReference處理penging串列中的參考物件,前面在處理pending串列的邏輯中可以知道,如果pending串列不為空,會回傳true,tryHandlePendingReference也會回傳true,此時意味著清理了一部分物件,所以重新進入到第1步進行檢查;

    如果pending串列為空,會回傳引數中傳入的waitForNotify的值,從JavaLangRefAccess的tryHandlePendingReference中可以看出這里傳入的是false,所以會進行如下處理:

    • 通過 Thread.sleep(sleepTime)讓當前執行緒睡眠一段時間,這樣可以避免reserveMemory方法一直在占用資源;
    • 對睡眠次數加1;
  4. 如果以上步驟處理之后還沒有足夠的空間會拋出拋出OutOfMemoryError例外;

reserveMemory方法的作用是保證在申請記憶體之前有足夠的記憶體,如果沒有足夠的記憶體會進行清理,達到指定清理次數之后依舊沒有足夠的記憶體空間,將拋出OutOfMemoryError例外,

class Bits {

    static void reserveMemory(long size, int cap) {

        if (!memoryLimitSet && VM.isBooted()) {
            maxMemory = VM.maxDirectMemory();
            memoryLimitSet = true;
        }

        // 是否有足夠記憶體
        if (tryReserveMemory(size, cap)) {
            return;
        }
        // 獲取JavaLangRefAccess
        final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();
        // 呼叫tryHandlePendingReference
        while (jlra.tryHandlePendingReference()) {
            // 判斷是否有足夠的記憶體
            if (tryReserveMemory(size, cap)) {
                return;
            }
        }

        // 呼叫gc進行垃圾回收
        System.gc();

        boolean interrupted = false;
        try {
            long sleepTime = 1;
            int sleeps = 0;
            // 開啟回圈
            while (true) {
                // 是否有足夠記憶體
                if (tryReserveMemory(size, cap)) {
                    return;
                }
                // 如果次數小于最大限定次數,終止
                if (sleeps >= MAX_SLEEPS) {
                    break;
                }
                // 再次處理penging串列中的物件
                if (!jlra.tryHandlePendingReference()) {
                    try {
                        // 睡眠一段時間
                        Thread.sleep(sleepTime);
                        sleepTime <<= 1;
                        sleeps++; // 睡眠次數增加1
                    } catch (InterruptedException e) {
                        interrupted = true;
                    }
                }
            }
            // 拋出OutOfMemoryError例外
            throw new OutOfMemoryError("Direct buffer memory");

        } finally {
            if (interrupted) {
                // don't swallow interrupts
                Thread.currentThread().interrupt();
            }
        }
    }
}

public abstract class Reference<T> {
    static {
        // ...
        // 這里設定了JavaLangRefAccess
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                // 呼叫tryHandlePending,這里waitForNotify引數傳入的是false
                return tryHandlePending(false);
            }
        });
    }
}

參考

Reference原始碼決議

一文讀懂java中的Reference和參考型別

Java 原始碼剖析——徹底搞懂 Reference 和 ReferenceQueue

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/511032.html

標籤:其他

上一篇:Java基礎合集

下一篇:python表白玫瑰花繪制、

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more