主頁 > 後端開發 > Kafka成長記1:從HelloWorld開始研究Kafka Producer原始碼原理

Kafka成長記1:從HelloWorld開始研究Kafka Producer原始碼原理

2021-10-06 06:10:25 後端開發

file

成長記不會介紹太對一些kafka的基礎知識,如果有需要的話,之后會有專門的《小白起步營》,成長記的默認大家對kafka的一些概念是熟知的、默認也是會基本Kafka的部署的,當然為了照顧一些小白,第一次涉及的知識我會簡單介紹和解釋的,熟悉的人就當回顧吧,簡單的事情重復做有時也是好事,

Kafka成長記會直接從三個方面開始探索,Producer、Broker、Comsumer,程序中,根據場景會使用之前ZK和JDK成長記介紹原始碼分析方法,話不多說,讓我們直接開始第一節的內容吧!

我們之前研究ZK主要是使用的場景法,找到一些核心入口開始分析的,研究Kafka的原始碼時候,我們也可以參考之前的方法,不過這次我們不直接從Broker服務端節點入手,先從Producer開始入手研究,會用到一些新的分析原始碼的思想和方法,

要想分析Kafka Producer的原始碼原理,首先肯定得有一個入口或者下手的地方,很多人使用Kafka肯定都是從一個Demo開始的,自己部署一臺Kafka,之后發送下訊息,之后在自己消費一條訊息,

KafkaProducerHelloWorld

所以我們就從最簡單的一個Kafka Producer的Demo開始,從一個KafkaProducerHelloWorld例子開始Kafka原始碼原理的探索,

HelloWorld的代碼如下:

import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.Properties;

/**
 * @author fanmao
 */
public class KafkaProducerHelloWorld {
	
	public static void main(String[] args) throws Exception {
		//配置Kafka的一些引數
		Properties props = new Properties();
		props.put("bootstrap.servers", "192.168.30.1:9092");

		// 創建一個Producer實體
		KafkaProducer<String, String> producer = new KafkaProducer<>(props);

		// 封裝一條訊息
		ProducerRecord<String, String> record = new ProducerRecord<>(
				"test-topic", "test-key", "test-value");

		// 同步方式發送訊息,會阻塞在這里,直到發送完成
		// producer.send(record).get();

		// 異步方式發送訊息,不阻塞,設定一個監聽回呼函式即可
		producer.send(record, new Callback() {
			@Override
			public void onCompletion(RecordMetadata metadata, Exception exception) {
				if(exception == null) {
					System.out.println("訊息發送成功");
				} else {
					System.out.println("訊息發送例外");
				}
			}
		});

		Thread.sleep(5 * 1000);

		// 退出producer
		producer.close();
	}
	
}

上面的代碼例子,雖然非常簡單,但是也有自己的脈絡,

1)創建KafkaProducer

2)準備訊息ProducerRecord

3)發送訊息producer.send()

簡單畫個圖:

file

這里多說一點,我之前在Zookeeper成長記5提到過原始碼版本的選擇和看原始碼的方式,這里我就不重復說了,直接將選擇后的結果告訴大家,我選擇的是kafka-0.10.0.1版本,

所以客戶端使用的依賴的GAV(Group-ArtifactId-Version) 是 org.apache.kafka-kafka-clients-0.10.0.1,POM如下所示:

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>org.mfm.learn</groupId>
	<artifactId>learn-kafka</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>learn-kafka</name>
	<url>http://maven.apache.org</url>


	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>

	<dependencies>
		<dependency>
		    <groupId>org.apache.kafka</groupId>
		    <artifactId>kafka-clients</artifactId>
		    <version>0.10.0.1</version>
		</dependency>
		<dependency>
		    <groupId>com.alibaba</groupId>
		    <artifactId>fastjson</artifactId>
		    <version>1.2.72</version>
		</dependency>
	</dependencies>

	<build>
	</build>
</project>

KafkaProducer的創建

上面KafkaProducerHelloWorld脈絡既然主要分了三步,那我們一步一步來看,首先就是KafkaProducer的創建,我們來一起看看它初始化什么東西?

這里問大家一個問題,這種構造方法的原始碼原理,一般分析的結果用什么方法會比較好?

沒錯,組件圖或者原始碼脈絡圖分析最容易理解了,我們只需要有個大致印象就行,方法有了,一般又會用什么思想呢?連蒙帶猜、看看注釋,猜測組件的作用,是不是?

好了讓我們來試試吧!

new KafkaProducer的代碼如下:

    /**
     * A producer is instantiated by providing a set of key-value pairs as configuration. Valid configuration strings
     * are documented <a href="http://kafka.apache.org/documentation.html#producerconfigs">here</a>.
     * @param properties   The producer configs
     */
    public KafkaProducer(Properties properties) {
        this(new ProducerConfig(properties), null, null);
    }

建構式中呼叫了一個多載的建構式,我們不著急往下看,先看下注釋,大體可以知道,這個建構式,入參是可以通過Properties設定一些引數,之后肯定是講這個引數轉換成了ProducerConfig物件進行封裝,肯定有一定的轉換方法,你還記得Zookeeper成長記中是不是也有類似的操作,封裝了一個QuorumPeerConfig物件,其實分析多了很多原始碼,你就逐漸有經驗了,更好的能駕輕就熟的分析任何一個原始碼原理了,這才是我想要讓大家學會的,而不是它如何決議,封裝成配置物件的,

我們接著分析,那么接下里就是兩條路了,看下多載的構造方法或者是 ProducerConfig是如何決議的,如下:

file

Kafka Producer 生產者的配置如何決議的?

這一節,我們就先來看看ProducerConfig是如何決議組態檔的,new ProducerConfig()的代碼如下:

/*
 * NOTE: DO NOT CHANGE EITHER CONFIG STRINGS OR THEIR JAVA VARIABLE NAMES AS THESE ARE PART OF THE PUBLIC API AND
 * CHANGE WILL BREAK USER CODE.
 * 注意:請勿更改任何配置字串或它們的JAVA變數名,因為它們是公共API的一部分,更改將破壞用戶代碼,
 */
private static final ConfigDef CONFIG;

ProducerConfig(Map<?, ?> props) {
    super(CONFIG, props);
}

這個建構式的脈絡,呼叫了一個super,竟然有一個父類,看起來比Zookeeper的配置決議封裝的多一些,不是簡單的一個QuorumPeerConfig,

而且有一個靜態變數 ConfigDef CONFIG,你肯定想知道它是個什么東西,

我們可以看下ConfigDef這個類的原始碼脈絡,看看能不能看出來什么:

file

看著就是有一堆define方法、validate方法,關鍵幾個變數,比如一個Map configKeys啥的,好像感覺是放key-value配置的

比如key=bootstrap.servers , value192.168.30.:9092的,

實在猜不到,我們可以再看看ConfigDef這個類的注釋,

/**
/**
 * This class is used for specifying the set of expected configurations. For each configuration, you can specify
 * the name, the type, the default value, the documentation, the group information, the order in the group,
 * the width of the configuration value and the name suitable for display in the UI.
 * 此類用于指定期望的配置集,對于每種配置,您可以指定名稱,型別,默認值,檔案,組資訊,組中的順序,配置值的寬度和適合在UI中顯示的名稱,
 *
 * You can provide special validation logic used for single configuration validation by overriding {@link Validator}.
 * 您可以通過覆寫{@link Validator}來提供用于單個配置驗證的特殊驗證邏輯,
 *
 * Moreover, you can specify the dependents of a configuration. The valid values and visibility of a configuration
 * may change according to the values of other configurations. You can override {@link Recommender} to get valid
 * values and set visibility of a configuration given the current configuration values.
 * 此外,您可以指定配置的從屬,配置的有效值和可見性可能會根據其他配置的值而改變,您可以覆寫{@link Recommender}來獲得有效值,
 * 并在給定當前配置值的情況下設定配置的可見性,

 * 省略其他...
 
 * This class can be used standalone or in combination with {@link AbstractConfig} which provides some additional
 * functionality for accessing configs.
 * 此類可以單獨使用,也可以與{@link AbstractConfig}結合使用,從而提供一些附加功能訪問配置的功能, 
 */

通過上面的話,你應該就不難看出它的功能了,簡單的說就是封裝了key-value的配置,可以設定和校驗key-value,可以單獨使用用于訪問配置

知道了這個靜態變數的作用后,你點擊到ProducerConfig的super,進入父類的建構式:

public AbstractConfig(ConfigDef definition, Map<?, ?> originals, boolean doLog) {
    /* check that all the keys are really strings */
    for (Object key : originals.keySet())
        if (!(key instanceof String))
            throw new ConfigException(key.toString(), originals.get(key), "Key must be a string.");
    this.originals = (Map<String, ?>) originals;
    this.values = definition.parse(this.originals);
    this.used = Collections.synchronizedSet(new HashSet<String>());
    if (doLog)
        logAll();
}

上面的代碼核心脈絡就一句話definition.parse(this.originals); 也就是執行了ConfigDef的parrse方法,

到這里,你想都不用想,這個方法就是轉換 Properties為ProducerConfig配置的方法了,如下圖所示:

file

那么接下來簡單看下parse方法吧,代碼如下:

private final Map<String, ConfigKey> configKeys = new HashMap<>();

public Map<String, Object> parse(Map<?, ?> props) {
        // Check all configurations are defined
        List<String> undefinedConfigKeys = undefinedDependentConfigs();
        if (!undefinedConfigKeys.isEmpty()) {
            String joined = Utils.join(undefinedConfigKeys, ",");
            throw new ConfigException("Some configurations in are referred in the dependents, but not defined: " + joined);
        }
        // parse all known keys
        Map<String, Object> values = new HashMap<>();
        for (ConfigKey key : configKeys.values()) {
            Object value;
            // props map contains setting - assign ConfigKey value
            if (props.containsKey(key.name)) {
                value = https://www.cnblogs.com/fanmao/archive/2021/10/05/parseType(key.name, props.get(key.name), key.type);
                // props map doesn't contain setting, the key is required because no default value specified - its an error
            } else if (key.defaultValue =https://www.cnblogs.com/fanmao/archive/2021/10/05/= NO_DEFAULT_VALUE) {
                throw new ConfigException("Missing required configuration \"" + key.name + "\" which has no default value.");
            } else {
                // otherwise assign setting its default value
                value = https://www.cnblogs.com/fanmao/archive/2021/10/05/key.defaultValue;
            }
            if (key.validator != null) {
                key.validator.ensureValid(key.name, value);
            }
            values.put(key.name, value);
        }
        return values;
 }

這段代碼直接看上去有點懵,沒關系,還是直接看的核心脈絡,

核心脈絡是一個for回圈,主要遍歷了Map<String, ConfigKey> configKey這個map,核心邏輯如下:

1)首先通過parseType確認value的型別, 之后根據ConfigKey定義的配置名稱,也就是key

2)最后將準備好的key-value配置,放入Map<String, Object> values中回傳給了AbstractConfig

這里我們就知道了最終我們配置的Producer引數,就會放入到AbstractConfig的一個Map<String,Object>中,而且Object說明配置的value是區分整數、字串之類的,比如

Properties props = new Properties();
props.put("bootstrap.servers", "192.168.30.:9092");

就會如下圖所示:

file

其實就是決議的Properties的整個程序了,你會發現其實也沒有多復雜,就是稍微比Zookeeper封裝的復雜點,

不過如果你細心的話,這里就有一個問題了, 上面parase方法的for回圈,回圈的Map<String, ConfigKey> configKey 是什么時候初始化的呢?

我們可以倒回去看看,

private static final ConfigDef CONFIG;

ProducerConfig(Map<?, ?> props) {
    super(CONFIG, props);
}

還記得呼叫父類方法前,這個ConfigDef是子類傳遞給父類的,這個變數又是一個靜態的,要想初始化,肯定是有一段靜態初始化代碼在ProducerConfig中的,你可以找到如下的代碼:

    /** <code>retries</code> */
    public static final String RETRIES_CONFIG = "retries";

    private static final String RETRIES_DOC = "Setting a value greater than zero will cause the client to resend any record whose send fails with a potentially transient error."
                                              + " Note that this retry is no different than if the client resent the record upon receiving the error."
                                              + " Allowing retries without setting <code>" + MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION + "</code> to 1 will potentially change the"
                                              + " ordering of records because if two batches are sent to a single partition, and the first fails and is retried but the second"
                                              + " succeeds, then the records in the second batch may appear first.";
static {
        CONFIG = new ConfigDef()
            .define(BOOTSTRAP_SERVERS_CONFIG, Type.LIST, Importance.HIGH, CommonClientConfigs.BOOSTRAP_SERVERS_DOC)
			.define(BUFFER_MEMORY_CONFIG, Type.LONG, 32 * 1024 * 1024L, atLeast(0L), Importance.HIGH, BUFFER_MEMORY_DOC)
			.define(RETRIES_CONFIG, Type.INT, 0, between(0, Integer.MAX_VALUE), Importance.HIGH, RETRIES_DOC)
			.define(ACKS_CONFIG,
					Type.STRING,
					"1",
					in("all", "-1", "0", "1"),
					Importance.HIGH,
					ACKS_DOC)
			.define(COMPRESSION_TYPE_CONFIG, Type.STRING, "none", Importance.HIGH, COMPRESSION_TYPE_DOC)
			.define(BATCH_SIZE_CONFIG, Type.INT, 16384, atLeast(0), Importance.MEDIUM, BATCH_SIZE_DOC)
			.define(TIMEOUT_CONFIG, Type.INT, 30 * 1000, atLeast(0), Importance.MEDIUM, TIMEOUT_DOC)
			.define(LINGER_MS_CONFIG, Type.LONG, 0, atLeast(0L), Importance.MEDIUM, LINGER_MS_DOC)
			.define(CLIENT_ID_CONFIG, Type.STRING, "", Importance.MEDIUM, CommonClientConfigs.CLIENT_ID_DOC)
			.define(SEND_BUFFER_CONFIG, Type.INT, 128 * 1024, atLeast(0), Importance.MEDIUM, 
			// 省略其他define
			.withClientSslSupport()
			.withClientSaslSupport();

    }

這個靜態方法的其實就是呼叫了define方法,初始化了Producer各個配置名稱、默認值還有檔案說明,最終封裝成一個map,value是ConfigKey,初始化了ConfigDef,

private final Map<String, ConfigKey> configKeys = new HashMap<>();
public static class ConfigKey {
    public final String name;
    public final Type type;
    public final String documentation;
    public final Object defaultValue;
    public final Validator validator;
    public final Importance importance;
    public final String group;
    public final int orderInGroup;
    public final Width width;
    public final String displayName;
    public final List<String> dependents;
    public final Recommender recommender;
}

這個程序雖然沒什么,但是重點就來了,默認值也就說KafkaProducer的配置,默認值都是在這里初始化的,如果你想知道Producer的默認值,就可以看這里了,

這些引數之前公眾號的《Kafka入門系列》中都有詳細的介紹,我這里介紹了估計你也記不住,之后我們分析原始碼的時候你在慢慢理解吧,下面我摘錄了一些核心配置,供大家回憶下:

Producer核心引數:

metadata.max.age.ms 默認每隔5分鐘 會重繪下元資料

max.request.size 每個請求的最大大小(1mb)

buffer.memory 緩沖區的記憶體大小(32mb)

max.block.ms 緩沖區填滿之后或元資料拉取最大阻塞時間(60s)

request.timeout.ms 請求超時時間(30s)

batch.size 每個batch的大小默認(16kb)

linger.ms 默認為0,不延遲發送,

可以配置為10ms,10ms內還沒有湊成1個batch發送出去,必須立即發送出去

......

小結

好了今天我們就先分析到這里,下一節我們繼續分析Producer 的創建,通過組件圖和流程圖的方式看看配置決議之后,執行的多載建構式又做了那些事情呢?

本文由博客群發一文多發等運營工具平臺 OpenWrite 發布

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

標籤:其他

上一篇:Java秒殺系統二:Service層

下一篇:刷題的狂歡-----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