主頁 > 後端開發 > Java Stream 原始碼分析

Java Stream 原始碼分析

2020-12-04 06:45:30 後端開發

前言

Java 8 的 Stream 使得代碼更加簡潔易懂,本篇文章深入分析 Java Stream 的作業原理,并探討 Steam 的性能問題,


Java 8 集合中的 Stream 相當于高級版的 Iterator,它可以通過 Lambda 運算式對集合進行各種非常便利、高效的聚合操作(Aggregate Operation),或者大批量資料操作 (Bulk Data Operation),

Stream的聚合操作與資料庫SQL的聚合操作sorted、filter、map等類似,我們在應用層就可以高效地實作類似資料庫SQL的聚合操作了,而在資料操作方面,Stream不僅可以通過串行的方式實作資料操作,還可以通過并行的方式處理大批量資料,提高資料的處理效率,

操作分類

官方將 Stream 中的操作分為兩大類:

  • 中間操作(Intermediate operations),只對操作進行了記錄,即只會回傳一個流,不會進行計算操作,
  • 終結操作(Terminal operations),實作了計算操作,

中間操作又可以分為:

  • 無狀態(Stateless)操作,元素的處理不受之前元素的影響,
  • 有狀態(Stateful)操作,指該操作只有拿到所有元素之后才能繼續下去,

終結操作又可以分為:

  • 短路(Short-circuiting)操作,指遇到某些符合條件的元素就可以得到最終結果
  • 非短路(Unshort-circuiting)操作,指必須處理完所有元素才能得到最終結果,

操作分類詳情如下圖所示:

原始碼結構

Stream 相關類和介面的繼承關系如下圖所示:

BaseStream

最頂端的介面類,定義了流的基本介面方法,最主要的方法為 spliterator、isParallel,

Stream

最頂端的介面類,定義了流的常用方法,例如 map、filter、sorted、limit、skip、collect 等,

ReferencePipeline

ReferencePipeline 是一個結構類,定義內部類組裝了各種操作流,定義了HeadStatelessOpStatefulOp三個內部類,實作了 BaseStream 與 Stream 的介面方法,

Sink

Sink 介面定義了 Stream 之間的操作行為,包含 begin()end()cancellationRequested()accpt()四個方法,ReferencePipeline最侄訓將整個 Stream 流操作組裝成一個呼叫鏈,而這條呼叫鏈上的各個 Stream 操作的上下關系就是通過 Sink 介面協議來定義實作的,

操作疊加

Stream 的基礎用法就不再敘述了,這里從一段代碼開始,分析 Stream 的作業原理,

@Test
public void testStream() {
    List<String> names = Arrays.asList("kotlin", "java", "go");
    int maxLength = names.stream().filter(name -> name.length() <= 4).map(String::length)
            .max(Comparator.naturalOrder()).orElse(-1);
    System.out.println(maxLength);
}

當使用 Stream 時,主要有 3 部分組成,下面一一講解,

加載資料源

呼叫 names.stream() 方法,會初次加載 ReferencePipeline 的 Head 物件,此時為加載資料源操作,

java.util.Collection#stream

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

StreamSupport 類中的 stream 方法,初始化了一個 ReferencePipeline的 Head 內部類物件,

java.util.stream.StreamSupport#stream(java.util.Spliterator, boolean)

public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
    Objects.requireNonNull(spliterator);
    return new ReferencePipeline.Head<>(spliterator,
                                        StreamOpFlag.fromCharacteristics(spliterator),
                                        parallel);
}

中間操作

接著為 filter(name -> name.length() <= 4).mapToInt(String::length),是中間操作,分為無狀態中間操作 StatelessOp 物件和有狀態操作 StatefulOp 物件,此時的 Stage 并沒有執行,而是通過AbstractPipeline 生成了一個中間操作 Stage 鏈表,

java.util.stream.ReferencePipeline#filter

@Override
public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
    Objects.requireNonNull(predicate);
    return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
                                    StreamOpFlag.NOT_SIZED) {
        @Override
        Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {
            return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
                @Override
                public void begin(long size) {
                    downstream.begin(-1);
                }

                @Override
                public void accept(P_OUT u) {
                    if (predicate.test(u))
                        downstream.accept(u);
                }
            };
        }
    };
}

java.util.stream.ReferencePipeline#map

@Override
@SuppressWarnings("unchecked")
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
    Objects.requireNonNull(mapper);
    return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
                                    StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
        @Override
        Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
            return new Sink.ChainedReference<P_OUT, R>(sink) {
                @Override
                public void accept(P_OUT u) {
                    downstream.accept(mapper.apply(u));
                }
            };
        }
    };
}

可以看到 filter 和 map 方法都回傳了一個新的 StatelessOp 物件,new StatelessOp 將會呼叫父類 AbstractPipeline 的建構式,這個建構式將前后的 Stage 聯系起來,生成一個 Stage 鏈表:

AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, int opFlags) {
    if (previousStage.linkedOrConsumed)
        throw new IllegalStateException(MSG_STREAM_LINKED);
    previousStage.linkedOrConsumed = true;
    previousStage.nextStage = this;

    this.previousStage = previousStage;
    this.sourceOrOpFlags = opFlags & StreamOpFlag.OP_MASK;
    this.combinedFlags = StreamOpFlag.combineOpFlags(opFlags, previousStage.combinedFlags);
    this.sourceStage = previousStage.sourceStage;
    if (opIsStateful())
        sourceStage.sourceAnyStateful = true;
    this.depth = previousStage.depth + 1;
}

終結操作

最后為 max(Comparator.naturalOrder()),是終結操作,會生成一個最終的 Stage,通過這個 Stage 觸發之前的中間操作,從最后一個Stage開始,遞回產生一個Sink鏈,

java.util.stream.ReferencePipeline#max

@Override
public final Optional<P_OUT> max(Comparator<? super P_OUT> comparator) {
    return reduce(BinaryOperator.maxBy(comparator));
}

最終呼叫到 java.util.stream.AbstractPipeline#wrapSink,這個方法會呼叫 opWrapSink 生成一個 Sink 鏈表,對應到本文的例子,就是 filter 和 map 操作,

@Override
@SuppressWarnings("unchecked")
final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {
    Objects.requireNonNull(sink);

    for ( @SuppressWarnings("rawtypes") AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) {
        sink = p.opWrapSink(p.previousStage.combinedFlags, sink);
    }
    return (Sink<P_IN>) sink;
}

在上面 opWrapSink 上斷點除錯,發現最侄訓呼叫到本例中的 filter 和 map 操作,

wrapAndCopyInto 生成 Sink 鏈表后,會通過 copyInfo 方法執行 Sink 鏈表的具體操作,

@Override
final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
    Objects.requireNonNull(wrappedSink);

    if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
        wrappedSink.begin(spliterator.getExactSizeIfKnown());
        spliterator.forEachRemaining(wrappedSink);
        wrappedSink.end();
    }
    else {
        copyIntoWithCancel(wrappedSink, spliterator);
    }
}

上面的核心代碼是:

spliterator.forEachRemaining(wrappedSink);

java.util.Spliterators.ArraySpliterator#forEachRemaining

@Override
public void forEachRemaining(Consumer<? super T> action) {
    Object[] a; int i, hi; // hoist accesses and checks from loop
    if (action == null)
        throw new NullPointerException();
    if ((a = array).length >= (hi = fence) &&
        (i = index) >= 0 && i < (index = hi)) {
        do { action.accept((T)a[i]); } while (++i < hi);
    }
}

斷點除錯,可以發現首先進入了 filter 的 Sink,其中 accept 方法的入參是 list 中的第一個元素“kotlin”(代碼中的 3 個元素是:"kotlin", "java", "go"),filter 的傳入是一個 Lambda 運算式:

filter(name -> name.length() <= 4)

顯然這個第一個元素“kotlin”的 predicate 是不會進入的,

對于第二個元素“java”,predicate.test 會回傳 true(字串“java”的長度<=4),則會進入 map 的 accept 方法,

本次呼叫 accept 方法時,empty 為 false,會將 map 后的結果(int 型別的 4)賦值給 t,

public static <T> TerminalOp<T, Optional<T>>
makeRef(BinaryOperator<T> operator) {
    Objects.requireNonNull(operator);
    class ReducingSink
            implements AccumulatingSink<T, Optional<T>, ReducingSink> {
        private boolean empty;
        private T state;

        public void begin(long size) {
            empty = true;
            state = null;
        }

        @Override
        public void accept(T t) {
            if (empty) {
                empty = false;
                state = t;
            } else {
                state = operator.apply(state, t);
            }
        }

        ……
        }
}

對于第三個元素“go”,也會進入 accept 方法,此時 empty 為 true, map 后的結果(int 型別的 2)會與上次的結果 4 通過自定義的比較器相比較,存入符合結果的值,

public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
    Objects.requireNonNull(comparator);
    return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}

本文代碼中的 max 傳入的比較器為:

max(Comparator.naturalOrder())

至此會回傳 int 型別的 4,

并行處理

上面的例子是串行處理的,如果要改成并行也很簡單,只需要在 stream() 方法后加上 parallel() 就可以了,并行代碼可以寫成:

@Test
public void testStream() {
    List<String> names = Arrays.asList("kotlin", "java", "go");
    int maxLength = names.stream().parallel().filter(name -> name.length() <= 4)
            .map(String::length).max(Comparator.naturalOrder()).orElse(-1);
    System.out.println(maxLength);
}

Stream 的并行處理在執行終結操作之前,跟串行處理的實作是一樣的,而在呼叫終結方法之后,實作的方式就有點不太一樣,會呼叫 TerminalOp 的 evaluateParallel 方法進行并行處理,

final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp) {
    assert getOutputShape() == terminalOp.inputShape();
    if (linkedOrConsumed)
        throw new IllegalStateException(MSG_STREAM_LINKED);
    linkedOrConsumed = true;

    return isParallel()
            ? terminalOp.evaluateParallel(this, sourceSpliterator(terminalOp.getOpFlags()))
            : terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags()));
}

核心是使用了 ForkJoin 框架,對 Stream 處理進行分片,最侄訓呼叫下面的代碼,這里就不展開分析了,

java.util.stream.AbstractTask#compute

@Override
public void compute() {
    Spliterator<P_IN> rs = spliterator, ls; // right, left spliterators
    long sizeEstimate = rs.estimateSize();
    long sizeThreshold = getTargetSize(sizeEstimate);
    boolean forkRight = false;
    @SuppressWarnings("unchecked") K task = (K) this;
    while (sizeEstimate > sizeThreshold && (ls = rs.trySplit()) != null) {
        K leftChild, rightChild, taskToFork;
        task.leftChild  = leftChild = task.makeChild(ls);
        task.rightChild = rightChild = task.makeChild(rs);
        task.setPendingCount(1);
        if (forkRight) {
            forkRight = false;
            rs = ls;
            task = leftChild;
            taskToFork = rightChild;
        }
        else {
            forkRight = true;
            task = rightChild;
            taskToFork = leftChild;
        }
        taskToFork.fork();
        sizeEstimate = rs.estimateSize();
    }
    task.setLocalResult(task.doLeaf());
    task.tryComplete();
}

并行錯誤的使用方法

@Test
public void testParallelWrong() {
    List<Integer> parallelList = new ArrayList<>();
    IntStream.range(0, 1000).boxed().parallel().filter(i -> i % 2 == 1)
            .forEach(parallelList::add);
    System.out.println(parallelList.size());
}

上面的輸出結果會經常小于500,這是因為 parallelList 的型別是 ArrayList,并不是執行緒安全的,在執行 add 操作時,可能正好趕上擴容或者執行緒被占用,會覆寫其他執行緒的賦好的值,

并行正確的使用方法

@Test
public void testParallelRight() {
    List<Integer> parallelList = IntStream.range(0, 1000).boxed().parallel()
            .filter(i -> i % 2 == 1).collect(Collectors.toList());
    System.out.println(parallelList.size());
}

性能

下面的文章參考自:JavaLambdaInternals/8-Stream Performance.md,侵刪,

為保證測驗結果真實可信,我們將JVM運行在-server模式下,測驗資料在GB量級,測驗機器采用常見的商用服務器,配置如下:

OSCentOS 6.7 x86_64
CPUIntel Xeon X5675, 12M Cache 3.06 GHz, 6 Cores 12 Threads
記憶體96GB
JDKjava version 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM

測驗所用代碼在這里,測驗結果匯總.

測驗方法和測驗資料

性能測驗并不是容易的事,Java性能測驗更費勁,因為虛擬機對性能的影響很大,JVM對性能的影響有兩方面:

  1. GC的影響,GC的行為是Java中很不好控制的一塊,為增加確定性,我們手動指定使用CMS收集器,并使用10GB固定大小的堆記憶體,具體到JVM引數就是-XX:+UseConcMarkSweepGC -Xms10G -Xmx10G
  2. JIT(Just-In-Time)即時編譯技術,即時編譯技識訓將熱點代碼在JVM運行的程序中編譯成本地代碼,測驗時我們會先對程式預熱,觸發對測驗函式的即時編譯,相關的JVM引數是-XX:CompileThreshold=10000

Stream并行執行時用到ForkJoinPool.commonPool()得到的執行緒池,為控制并行度我們使用Linux的taskset命令指定JVM可用的核數,

測驗資料由程式隨機生成,為防止一次測驗帶來的抖動,測驗4次求出平均時間作為運行時間,

實驗一 基本型別迭代

測驗內容:找出整型陣列中的最小值,對比for回圈外部迭代和Stream API內部迭代性能,

測驗程式IntTest,測驗結果如下圖:

圖中展示的是for回圈外部迭代耗時為基準的時間比值,分析如下:

  1. 對于基本型別Stream串行迭代的性能開銷明顯高于外部迭代開銷(兩倍);
  2. Stream并行迭代的性能比串行迭代和外部迭代都好,

并行迭代性能跟可利用的核數有關,上圖中的并行迭代使用了全部12個核,為考察使用核數對性能的影響,我們專門測驗了不同核數下的Stream并行迭代效果:

分析,對于基本型別:

  1. 使用Stream并行API在單核情況下性能很差,比Stream串行API的性能還差;
  2. 隨著使用核數的增加,Stream并行效果逐漸變好,比使用for回圈外部迭代的性能還好,

以上兩個測驗說明,對于基本型別的簡單迭代,Stream串行迭代性能更差,但多核情況下Stream迭代時性能較好,

實驗二 物件迭代

再來看物件的迭代效果,

測驗內容:找出字串串列中最小的元素(自然順序),對比for回圈外部迭代和Stream API內部迭代性能,

測驗程式StringTest,測驗結果如下圖:

結果分析如下:

  1. 對于物件型別Stream串行迭代的性能開銷仍然高于外部迭代開銷(1.5倍),但差距沒有基本型別那么大,
  2. Stream并行迭代的性能比串行迭代和外部迭代都好,

再來單獨考察Stream并行迭代效果:

分析,對于物件型別:

  1. 使用Stream并行API在單核情況下性能比for回圈外部迭代差;
  2. 隨著使用核數的增加,Stream并行效果逐漸變好,多核帶來的效果明顯,

以上兩個測驗說明,對于物件型別的簡單迭代,Stream串行迭代性能更差,但多核情況下Stream迭代時性能較好,

實驗三 復雜物件歸約

從實驗一、二的結果來看,Stream串行執行的效果都比外部迭代差(很多),是不是說明Stream真的不行了?先別下結論,我們再來考察一下更復雜的操作,

測驗內容:給定訂單串列,統計每個用戶的總交易額,對比使用外部迭代手動實作和Stream API之間的性能,

我們將訂單簡化為<userName, price, timeStamp>構成的元組,并用Order物件來表示,測驗程式ReductionTest,測驗結果如下圖:

分析,對于復雜的歸約操作:

  1. Stream API的性能普遍好于外部手動迭代,并行Stream效果更佳;

再來考察并行度對并行效果的影響,測驗結果如下:

分析,對于復雜的歸約操作:

  1. 使用Stream并行歸約在單核情況下性能比串行歸約以及手動歸約都要差,簡單說就是最差的;
  2. 隨著使用核數的增加,Stream并行效果逐漸變好,多核帶來的效果明顯,

以上兩個實驗說明,對于復雜的歸約操作,Stream串行歸約效果好于手動歸約,在多核情況下,并行歸約效果更佳,我們有理由相信,對于其他復雜的操作,Stream API也能表現出相似的性能表現,

結論

上述三個實驗的結果可以總結如下:

  1. 對于簡單操作,比如最簡單的遍歷,Stream串行API性能明顯差于顯示迭代,但并行的Stream API能夠發揮多核特性,
  2. 對于復雜操作,Stream串行API性能可以和手動實作的效果匹敵,在并行執行時Stream API效果遠超手動實作,

所以,如果出于性能考慮,1. 對于簡單操作推薦使用外部迭代手動實作,2. 對于復雜操作,推薦使用Stream API, 3. 在多核情況下,推薦使用并行Stream API來發揮多核優勢,4.單核情況下不建議使用并行Stream API,

參考文章

  1. JavaLambdaInternals/6-Stream Pipelines.md
  2. JavaLambdaInternals/8-Stream Performance.md
  3. 極客時間-Java性能調優實戰/06.Stream如何提高遍歷集合效率?

公眾號

coding 筆記、點滴記錄,以后的文章也會同步到公眾號(Coding Insight)中,希望大家關注_

代碼和思維導圖在 GitHub 專案中,歡迎大家 star!

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

標籤:Java

上一篇:dubbo原始碼決議-服務發布

下一篇:2020最新總結大廠Java高頻面試題(含答案決議)

標籤雲
其他(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