主頁 > 後端開發 > fastjson2為什么這么快?

fastjson2為什么這么快?

2023-03-03 10:02:01 後端開發

本文作者從以下三個方面講述了fastjson2 使用了哪些核心技術來提升速度,

1、用「Lambda 生成函式映射」代替「高頻的反射操作」

2、對 String 做零拷貝優化

3、常見型別決議優化
fastjson 是很多企業應用中處理 json 資料的基礎工具,其憑借在易用性、處理速度上的優越性,支撐了很多資料處理場景,fastjson 的作者「高鐵」已更新推出 2.0 版本的 fastjson,即 fastjosn2[1],
圖片
據 “相關資料” [2]顯示,fastjson2 各方面性能均有提升,常規資料序列化相比 1.0 系列提升達到 30%,那么,fastjson2 使用了哪些核心技術來提升速度的呢?筆者總結包含但不限于以下幾個方面:
  • 用「Lambda 生成函式映射」代替「高頻的反射操作」

  • 對 String 做零拷貝優化

  • 常見型別決議優化

 

一、用「 Lambda 生成函式映射」代替「高頻的反射操作」

 

我們來看一段最簡單的反射執行代碼:

public class Bean {
    int id;
    public int getId() {
        return id;
    }
}

Method methodGetId = Bean.class.getMethod("getId");
Bean bean = createInstance();
int value = https://www.cnblogs.com/88223100/p/(Integer) methodGetId.invoke(bean);

上面的反射執行代碼可以被改寫成這樣:


// 將getId()映射為function函式
java.util.function.ToIntFunction<Bean> function = Bean::getId; 
int i = function.applyAsInt(bean);

fastjson2 中的具體實作的要復雜一點,但本質上跟上面一樣,其本質也是生成了一個 function,

//function
java.util.function.ToIntFunction<Bean> function = LambdaMetafactory.metafactory(
        lookup,
        "applyAsInt",
        methodHanlder,
        methodType(ToIntFunction.class),
        lookup.findVirtual(int.class, "getId", methodType(int.class)),
        methodType(int.class)
);
int i = function.applyAsInt(bean);
我們使用反射獲取到的 Method 和 Lambda 函式分別執行 10000 次來看下處理速度差異:

Method invoke elapsed: 25ms
Bean::getId elapsed: 1ms

處理速度相差居然達到 25 倍,使用 Java8 Lambda 為什么能提升這多呢?

答案就是:Lambda 利用 LambdaMetafactory 生成了函式映射代替反射,
下面我們詳細分析下 Java反射 與 Lambda 函式映射 的底層區別,

1、反射執行的底層原理

注:以下只是想表達出反射呼叫本身的繁雜性,大可不必深究這些代碼細節
從代碼角度,我們從 Java 方法反射 Method.invoke 的原始碼入口來深入:

public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException,InvocationTargetException{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    MethodAccessor ma = methodAccessor;// read volatile
    if (ma == null) ma = acquireMethodAccessor();
    return ma.invoke(obj, args);
}

可見,經過簡單的檢查后,呼叫的是MethodAccessor.invoke(),這部分的實際實作:

public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
    if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
        MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
        this.parent.setDelegate(var3);
    }
    return invoke0(this.method, var1, var2);
}

private static native Object invoke0(Method var0, Object var1, Object[] var2);

可見,最終呼叫的是 native 本地方法(本地方法堆疊)的 invoke0(),這部分的實作:


JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)
{
    return JVM_InvokeMethod(env, m, obj, args);
}

可見,呼叫的是 jvm.h 模塊的 JVM_InvokeMethod 方法,這部分的實作:


JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)
{
    return JVM_InvokeMethod(env, m, obj, args);
}
更詳細的細節:https://www.zhihu.com/question/464985077/answer/1940021614

 

2、Lambda生成函式映射的底層原理

具體來講,Bean::getId 這種 Lambda 寫法進過編譯后,會通過 java.lang.invoke.LambdaMetafactory 

呼叫到

java.lang.invoke.InnerClassLambdaMetafactory#spinInnerClass

最終實作是呼叫 JDK 自帶的位元組碼庫 jdk.internal.org.objectweb.asm 動態生成一個內部類,上層 call 內部類的方法執行呼叫,
所以 Lambda 生成函式映射的方式,核心消耗就在于生成函式映射,那生成函式映射的效率究竟如何呢?
我們和反射獲取 Method 做個對比,Benchmark 結論:
Benchmark 
Mode
Cnt
Score
Error
Units
genMethod(反射獲取方法)
avgt(平均耗時)
5
0.125
0.015
us/op
genLambda(生成方法的函式映射)
avgt
5
51.880
40.040
us/op
從資料來看,生成函式映射的耗時遠高于反射獲取 Method,那為我們不禁要問,既然生成函式映射的性能遠低于反射獲取方法,那為什么最終用生成函式的方式的執行速度比反射要快?
答案就在于——函式復用,將一個固定簽名的函式快取起來,下次呼叫就可以省去函式創建的程序,
比如 fastjson2 直接將常用函式的初始化快取放在 static 代碼塊,這就將函式創建的消耗就被前置到類加載階段,在資料處理階段的耗時進一步降低,

 

3、對比分析 & 結論

從原理上來說,反射方式,在獲取 Method 階段消耗較少,但 invoke 階段則是每次都用都呼叫本地方法執行,先是在 jvm 層面多出一些檢查,而后轉到 JNI 本地庫,除了有額外的 jvm 堆疊與本地方法堆疊的 context 交換 ,還多出一系列 C 體系額外操作,在性能上自然是不如 Lambda 函式映射;
Lambda 生成函式映射的方式,在生成代理類的程序中有部分開銷,這部分開銷可以通過快取機制大量減少,而后的呼叫則全部屬于 Java 范疇內的堆疊呼叫(即拿到代理類后,呼叫效率和原生方法呼叫幾乎一致),

二、對 String 做零拷貝優化

1、何為零拷貝

零拷貝[3]是老生常談的問題,Kafka 和 Netty 等都用到了零拷貝的知識,這里簡單介紹一下其概念以便生疏的讀者理解上流暢,
零拷貝:是指計算機執行IO操作時,CPU不需要將資料從一個存盤區域復制到另一個存盤區域,進而減少背景關系切換以及CPU的拷貝時間,它是一種IO操作優化技術,
JDK8 中的 String 是如何拷貝的?
圖片
為了實作字串是不可變的特性,JDK 在構造 String 構造字串的時候,會有拷貝的程序,比如上圖是 JDK8 的 String 的一個建構式的實作,其在堆記憶體中重新開辟了一塊記憶體區域,
如果要提升構造字串的開銷,就要避免這樣的拷貝,即零拷貝,

2、fastjson2 中如何實作 0 拷貝

在 JDK8 中,String 有一個建構式是不做拷貝的:
圖片
但這個方法不是 public,不能直接訪問到,可以反射執行,也可以使用 LambdaMetafactory 創建函式映射來呼叫,前面有介紹這個技巧,
生成的函式映射可以快取起來復用,而這個構造方法的簽名是固定不變的,這意味著,只需要生成一次,后續所有需要初始化 String 的時候都可以復用,

 

3、fastjson2 中的應用

將 LocalDate 格式化為 “yyyy-MM-dd” 的 String 原始碼(注:針對 JDK8 的實作,此處對原始碼精簡整理以方便閱讀):
static BiFunction<char[], Boolean, String>  STRING_CREATOR_JDK8;
static {
    //為上述String的0拷貝構造方法創建一個映射函式
    CallSite callSite = LambdaMetafactory.metafactory(caller, "apply", methodType(BiFunction.class), methodType(Object.class, Object.class, Object.class), handle, methodType(String.class, char[].class, boolean.class));
    STRING_CREATOR_JDK8 = (BiFunction<char[], Boolean, String>) callSite.getTarget().invokeExact();
}

static String formatYYYYMMDD(LocalDate date) {
    int year = date.getYear();
    int month = date.getMonthValue();
    int dayOfMonth = date.getDayOfMonth();
    int y0 = year / 1000 + '0';
    int y1 = (year / 100) % 10 + '0';
    int y2 = (year / 10) % 10 + '0';
    int y3 = year % 10 + '0';
    int m0 = month / 10 + '0';
    int m1 = month % 10 + '0';
    int d0 = dayOfMonth / 10 + '0';
    int d1 = dayOfMonth % 10 + '0';

    //char array
    char[] chars = new char[10];
    chars[0] = (char) y1;
    chars[1] = (char) y2;
    chars[2] = (char) y3;
    chars[3] = (char) y4;
    chars[4] = '-';
    chars[5] = (char) m0;
    chars[6] = (char) m1;
    chars[7] = '-';
    chars[8] = (char) d0;
    chars[9] = (char) d1;

    //執行「lambda函式映射」構造String
    String str = STRING_CREATOR_JDK8.apply(chars, Boolean.TRUE);
    return str;
}

在 JDK8 的實作中,先拼接好格式中每一個 char 字符,然后通過零拷貝的方式構造字串物件,這樣就實作了快速格式化 LocalDate 到 String,這樣的實作遠比使用 SimpleDateFormat 之類要快,這種實體化 String 的方式在fatsjson2 中的 JSONReader、JSONWritter 隨處可見,

 

三、常見型別決議優化

fastjson2 里針對各種型別的優化處理很多,不能一一列舉,這里僅以 Date 型別舉例,我們前面舉例了將 Date 格式化為 String,這次我們反過來,將 String 轉換為 Date —— 如何快速將字串決議成日期?以下給出幾種實作方式,隨后我們來做個對比,

1、使用SimpleDateFormat

SimpleDateFormat 是我們使用最廣泛、最容易想到的方式,需要注意的是 SimpleDateFormat 不是執行緒安全的,并發場景下要 sync 同步處理,
static final ThreadLocal<SimpleDateFormat> formatThreadLocal = ThreadLocal.withInitial(
        () -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
);

// get format from ThreadLocal
SimpleDateFormat format = formatThreadLocal.get();
format.parse(str);

 

2、使用java.time.DateTimeFormatter

JDK8 提供了 java.time API,吸收了 joda-time[4]的部分精華,功能更強大,性能也更好,同時,DateTimeFormatter 是執行緒安全的,

static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

// use formatter parse Date
LocalDateTime ldt = LocalDateTime.parse(str, formatter);
ZoneOffset offset = DEFAULT_ZONE_ID.getRules().getOffset(ldt);
long millis = ldt.toInstant(offset).toEpochMilli();
Date date = new Date(millis);

這種方法比使用 SimpleDateFormat 組合 ThreadLocal 代碼更簡潔,速度也大約要快 50%,

圖片
圖片源自github

3、針對固定格式和固定時區優化

我們在日常處理 Date 資料時,在國內最常見的格式就是 "yyyy-MM-dd HH:mm:ss",默認的時區為東 8 區,在 java.time 中的 ZonedId 是 "Asia/Shanghai"(而不是 Asia/Beijing),而東 8 區在1992年之后,不在使用夏令時,固定的 zoneOffset 是 +8,根據這個情況,我們可以針對性做優化,如下(為方便理解,以下為原始碼的簡化版,去掉了影響閱讀的邊界處理等邏輯):
public static Date parseYYYYMMDDHHMMSS19(String str) {
    char y0 = str.charAt(0);
    char y1 = str.charAt(1);
    char y2 = str.charAt(2);
    char y3 = str.charAt(3);
    char m0 = str.charAt(4);
    char m1 = str.charAt(5);
    ...
    char s1 = str.charAt(18);

    int year = (y0 - '0') * 1000 + (y1 - '0') * 100 + (y2 - '0') * 10 + (y3 - '0');
    int month = (m0 - '0') * 10 + (m1 - '0');
    int dom = (d0 - '0') * 10 + (d1 - '0');
    int hour = (h0 - '0') * 10 + (h1 - '0');
    int minute = (i0 - '0') * 10 + (i1 - '0');
    int second = (s0 - '0') * 10 + (s1 - '0');

    //換算成毫秒
    long millis;
    if (year >= 1992 && (DEFAULT_ZONE_ID == SHANGHAI_ZONE_ID || DEFAULT_ZONE_ID.getRules() == IOUtils.SHANGHAI_ZONE_RULES)) {

        final int DAYS_PER_CYCLE = 146097;
        final long DAYS_0000_TO_1970 = (DAYS_PER_CYCLE * 5L) - (30L * 365L + 7L);

        long y = year;
        long m = month;

        long epochDay;
        {
            long total = 0;
            total += 365 * y;
            total += (y + 3) / 4 - (y + 99) / 100 + (y + 399) / 400;
            total += ((367 * m - 362) / 12);
            total += dom - 1;
            if (m > 2) {
                total--;
                boolean leapYear = (year & 3) == 0 && ((year % 100) != 0 || (year % 400) == 0);
                if (!leapYear) {
                    total--;
                }
            }
            epochDay = total - DAYS_0000_TO_1970;
        }
        long seconds = epochDay * 86400
                + hour * 3600
                + minute * 60
                + second
                - SHANGHAI_ZONE_OFFSET_TOTAL_SECONDS;

        millis = seconds * 1000L;
    } else {
        LocalDate localDate = LocalDate.of(year, month, dom);
        LocalTime localTime = LocalTime.of(hour, minute, second, 0);
        LocalDateTime ldt = LocalDateTime.of(localDate, localTime);
        ZoneOffset offset = DEFAULT_ZONE_ID.getRules().getOffset(ldt);
        millis = ldt.toEpochSecond(offset) * 1000;
    }

    return new Date(millis);
}

 

核心邏輯就是根據位數,直接開始計算給定的時間字串,相對于參照的原點時間(1970-1-1 0點)過去了多少毫秒,這個優化,避免了parse Number的開銷,精簡了大量 Partten 的處理,處理流程非常高效,

 

4、性能測驗 & 結論

benchmark[5]:
Benchmark 
Mode
Cnt
Score
Error
Units
DateParse.simpleDateFormatParse
avgt(平均耗時)
5
11.540
4.170
us/ms
DateParse.dateTimeFormatterParse
avgt
5
7.594
0.200
us/ms
DateParse.parseYYYYMMDDHHMMSS19
avgt
5
0.425
0.098
us/ms

JMH測驗顯示:方法 3 的耗時遠低于其他方式,方法 3 這種針對性的型別決議優化可以使用在重度使用日期決議的優化場景,比如資料批量匯入決議日期,大資料場景的 UDF 日期決議等,

One more thing

fastjson 系列相比同類 json 處理工具,雖然在安全性、魯棒性等方面還可以提升,但其最大優勢——處理速度,卻使其他同類競品望塵莫及,我們也可以在日常業務處理中,學習其精華部分,運用其中的技術亮點,優化業務處理速度,提升用戶體驗,
參考鏈接:

[1]https://github.com/alibaba/fastjson2

[2]https://github.com/alibaba/fastjson2/wiki/fastjson_benchmark

[3]https://so.csdn.net/so/search?q=零拷貝&spm=1001.2101.3001.7020

[4]https://www.joda.org/joda-time/

[5]https://github.com/alibaba/fastjson2/blob/main/benchmark/src/main/java/com/alibaba/fastjson2/benchmark/DateParse.java

 

作者|嚴彬源(泰文)

本文來自博客園,作者:古道輕風,轉載請注明原文鏈接:https://www.cnblogs.com/88223100/p/Why-is-fastjson2-so-fast.html

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

標籤:Java

上一篇:《分布式技術原理與演算法決議》學習筆記Day28

下一篇:全域視角看技術-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