Java中的Kafka消費(fèi)者詳解
一、消費(fèi)者工作流程
1.1 總體工作流程

1.2 消費(fèi)者組初始化流程

1.3 消費(fèi)者組詳細(xì)消費(fèi)流程

二、消費(fèi)者消費(fèi)消息方式
pull(拉)模 式:consumer采用從broker中主動(dòng)拉取數(shù)據(jù)。Kafka采用這種方式。
push(推)模式:Kafka沒(méi)有采用這種方式,因?yàn)橛蒪roker決定消息發(fā)送速率,很難適應(yīng)所有消費(fèi)者的 消費(fèi)速率。
例如推送的速度是50m/s,Consumer1、Consumer2就來(lái)不及處理消息。
pull模式不足之處是,如 果Kafka沒(méi)有數(shù)據(jù),消費(fèi)者可能會(huì)陷入循環(huán)中,一直返回空數(shù)據(jù)
2.1 消費(fèi)者組
Consumer Group(CG):消費(fèi)者組,由多個(gè)consumer組成。形成一個(gè)消費(fèi)者組的條件,是所有消費(fèi)者的groupid相同。
消費(fèi)者組內(nèi)每個(gè)消費(fèi)者負(fù)責(zé)消費(fèi)不同分區(qū)的數(shù)據(jù),一個(gè)分區(qū)只能由一個(gè)組內(nèi)消費(fèi)者消費(fèi)。 消費(fèi)者組之間互不影響。所有的消費(fèi)者都屬于某個(gè)消費(fèi)者組,即消費(fèi)者組是邏輯上的一個(gè)訂閱者。

如果向消費(fèi)組中添加更多的消費(fèi)者,超過(guò)主題分區(qū)數(shù)量,則有一部分消費(fèi)者就會(huì)閑置,不會(huì)接收任何消息。
消費(fèi)者組之間互不影響。所有的消費(fèi)者都屬于某個(gè)消費(fèi)者 組,即消費(fèi)者組是邏輯上的一個(gè)訂閱者。
2.2 消費(fèi)一個(gè)主題
// 0 配置
Properties properties = new Properties();
// 連接 bootstrap.servers
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// 反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// 配置消費(fèi)者組id
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test5");
// 1 創(chuàng)建一個(gè)消費(fèi)者 "", "hello"
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 2 訂閱主題 first
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
// 3 消費(fèi)數(shù)據(jù)
while (true){
//1秒消費(fèi)一次
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}2.3 消費(fèi)一個(gè)分區(qū)
需求:創(chuàng)建一個(gè)獨(dú)立消費(fèi)者,消費(fèi) first 主題 0 號(hào)分區(qū)的數(shù)據(jù)。

// 0 配置
Properties properties = new Properties();
// 連接
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// 反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// 組id
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
// 1 創(chuàng)建一個(gè)消費(fèi)者
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 2 訂閱主題對(duì)應(yīng)的分區(qū)
ArrayList<TopicPartition> topicPartitions = new ArrayList<>();
topicPartitions.add(new TopicPartition("first",0));
kafkaConsumer.assign(topicPartitions);
// 3 消費(fèi)數(shù)據(jù)
while (true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}三、分區(qū)的分配以及再平衡
1、一個(gè)consumer group中有多個(gè)consumer組成,一個(gè) topic有多個(gè)partition組成,現(xiàn)在的問(wèn)題是,到底由哪個(gè)consumer來(lái)消費(fèi)哪個(gè) partition的數(shù)據(jù)。
2、Kafka有四種主流的分區(qū)分配策略: Range、RoundRobin、Sticky、CooperativeSticky。 可以通過(guò)配置參數(shù)partition.assignment.strategy,修改分區(qū)的分配策略。Kafka可以同時(shí)使用多個(gè)分區(qū)分配策略。

| 參數(shù)名稱 | 描述 |
| heartbeat.interval.ms | Kafka 消費(fèi)者和 coordinator 之間的心跳時(shí)間,默認(rèn) 3s。該條目的值必須小于 session.timeout.ms,也不應(yīng)該高于session.timeout.ms 的 1/3。 |
| session.timeout.ms | Kafka 消費(fèi)者和 coordinator 之間連接超時(shí)時(shí)間,默認(rèn) 45s。超過(guò)該值,該消費(fèi)者被移除,消費(fèi)者組執(zhí)行再平衡。 |
| max.poll.interval.ms | 消費(fèi)者處理消息的最大時(shí)長(zhǎng),默認(rèn)是 5 分鐘。超過(guò)該值,該消費(fèi)者被移除,消費(fèi)者組執(zhí)行再平衡。 |
| partition.assignment.strategy | 消 費(fèi) 者 分 區(qū) 分 配 策 略 , 默 認(rèn) 策 略 是 Range + CooperativeSticky。Kafka 可以同時(shí)使用多個(gè)分區(qū)分配策略。可 以 選 擇 的 策 略 包 括 : Range 、 RoundRobin 、 Sticky 、CooperativeSticky |
3.1 分區(qū)分配策略之Range
- 默認(rèn)策略是Range + CooperativeSticky。

- 再平衡案例
(1)停止掉 0 號(hào)消費(fèi)者,快速重新發(fā)送消息觀看結(jié)果(45s 以內(nèi),越快越好)。 1 號(hào)消費(fèi)者:消費(fèi)到 3、4 號(hào)分區(qū)數(shù)據(jù)。 2 號(hào)消費(fèi)者:消費(fèi)到 5、6 號(hào)分區(qū)數(shù)據(jù)。 0 號(hào)消費(fèi)者的任務(wù)會(huì)整體被分配到 1 號(hào)消費(fèi)者或者 2 號(hào)消費(fèi)者。 說(shuō)明:0 號(hào)消費(fèi)者掛掉后,消費(fèi)者組需要按照超時(shí)時(shí)間 45s 來(lái)判斷它是否退出,所以需要等待,時(shí)間到了 45s 后,判斷它真的退出就會(huì)把任務(wù)分配給其他 broker 執(zhí)行。
(2)再次重新發(fā)送消息觀看結(jié)果(45s 以后)。 1 號(hào)消費(fèi)者:消費(fèi)到 0、1、2、3 號(hào)分區(qū)數(shù)據(jù)。 2 號(hào)消費(fèi)者:消費(fèi)到 4、5、6 號(hào)分區(qū)數(shù)據(jù)。說(shuō)明:消費(fèi)者 0 已經(jīng)被踢出消費(fèi)者組,所以重新按照 range 方式分配。
3.2 分區(qū)分配策略之RoundRobin
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,"org.apache.kafka.clients.consumer.RoundRobinAssignor");

- 再平衡案例
(1)停止掉 0 號(hào)消費(fèi)者,快速重新發(fā)送消息觀看結(jié)果(45s 以內(nèi),越快越好)。 1 號(hào)消費(fèi)者:消費(fèi)到 2、5 號(hào)分區(qū)數(shù)據(jù) 2 號(hào)消費(fèi)者:消費(fèi)到 4、1 號(hào)分區(qū)數(shù)據(jù)0 號(hào)消費(fèi)者的任務(wù)會(huì)按照 RoundRobin 的方式,把數(shù)據(jù)輪詢分成 0 、6 和 3 號(hào)分區(qū)數(shù)據(jù), 分別由 1 號(hào)消費(fèi)者或者 2 號(hào)消費(fèi)者消費(fèi)。說(shuō)明:0 號(hào)消費(fèi)者掛掉后,消費(fèi)者組需要按照超時(shí)時(shí)間 45s 來(lái)判斷它是否退出,所以需 要等待,時(shí)間到了 45s 后,判斷它真的退出就會(huì)把任務(wù)分配給其他 broker 執(zhí)行。
(2)再次重新發(fā)送消息觀看結(jié)果(45s 以后)。 1 號(hào)消費(fèi)者:消費(fèi)到 0、2、4、6 號(hào)分區(qū)數(shù)據(jù)2 號(hào)消費(fèi)者:消費(fèi)到 1、3、5 號(hào)分區(qū)數(shù)據(jù)說(shuō)明:消費(fèi)者 0 已經(jīng)被踢出消費(fèi)者組,所以重新按照 RoundRobin 方式分配
3.3 Sticky 以及再平衡
粘性分區(qū)定義:可以理解為分配的結(jié)果帶有“粘性的”。即在執(zhí)行一次新的分配之前,考慮上一次分配的結(jié)果,盡量少的調(diào)整分配的變動(dòng),可以節(jié)省大量的開(kāi)銷。
粘性分區(qū)是 Kafka 從 0.11.x 版本開(kāi)始引入這種分配策略,首先會(huì)盡量均衡的放置分區(qū)到消費(fèi)者上面,在出現(xiàn)同一消費(fèi)者組內(nèi)消費(fèi)者出現(xiàn)問(wèn)題的時(shí)候,會(huì)盡量保持原有分配的分區(qū)不變化
再平衡案例
(1)停止掉 0 號(hào)消費(fèi)者,快速重新發(fā)送消息觀看結(jié)果(45s 以內(nèi),越快越好)。 1 號(hào)消費(fèi)者:消費(fèi)到 2、5、3 號(hào)分區(qū)數(shù)據(jù)。 2 號(hào)消費(fèi)者:消費(fèi)到 4、6 號(hào)分區(qū)數(shù)據(jù)。 0 號(hào)消費(fèi)者的任務(wù)會(huì)按照粘性規(guī)則,盡可能均衡的隨機(jī)分成 0 和 1 號(hào)分區(qū)數(shù)據(jù),分別由 1 號(hào)消費(fèi)者或者 2 號(hào)消費(fèi)者消費(fèi)。說(shuō)明:0 號(hào)消費(fèi)者掛掉后,消費(fèi)者組需要按照超時(shí)時(shí)間 45s 來(lái)判斷它是否退出,所以需要等待,時(shí)間到了 45s 后,判斷它真的退出就會(huì)把任務(wù)分配給其他 broker 執(zhí)行。
(2)再次重新發(fā)送消息觀看結(jié)果(45s 以后)。 1 號(hào)消費(fèi)者:消費(fèi)到 2、3、5 號(hào)分區(qū)數(shù)據(jù)。 2 號(hào)消費(fèi)者:消費(fèi)到 0、1、4、6 號(hào)分區(qū)數(shù)據(jù)。說(shuō)明:消費(fèi)者 0 已經(jīng)被踢出消費(fèi)者組,所以重新按照粘性方式分配。
四、offset 位移

__consumer_offsets 主題里面采用 key 和 value 的方式存儲(chǔ)數(shù)據(jù)。key 是 group.id+topic+分區(qū)號(hào),value 就是當(dāng)前 offset 的值。
每隔一段時(shí)間,kafka 內(nèi)部會(huì)對(duì)這個(gè) topic 進(jìn)行compact,也就是每個(gè) group.id+topic+分區(qū)號(hào)就保留最新數(shù)據(jù)。
4.1 自動(dòng)提交 offset
為了使我們能夠?qū)W⒂谧约旱臉I(yè)務(wù)邏輯,Kafka提供了自動(dòng)提交offset的功能。5s
| 參數(shù)名稱 | 描述 |
| enable.auto.commit | 默認(rèn)值為 true,消費(fèi)者會(huì)自動(dòng)周期性地向服務(wù)器提交偏移量。 |
| auto.commit.interval.ms | 自動(dòng)提交offset的時(shí)間間隔,默認(rèn)是5s,如果設(shè)置了 enable.auto.commit 的值為 true, 則該值定義了消費(fèi)者偏移量向 Kafka 提交的頻率,默認(rèn) 5s。 |

// 自動(dòng)提交 properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,true); // 提交時(shí)間間隔 properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,1000);
4.2 手動(dòng)提交offset
雖然自動(dòng)提交offset十分簡(jiǎn)單便利,但由于其是基于時(shí)間提交的,開(kāi)發(fā)人員難以把握offset提交的時(shí)機(jī)。因 此Kafka還提供了手動(dòng)提交offset的API。
手動(dòng)提交offset的方法有兩種:分別是commitSync(同步提交)和commitAsync(異步提交)。兩者的相同點(diǎn)是,都會(huì)將本次提交的一批數(shù)據(jù)最高的偏移量提交;不同點(diǎn)是,同步提交阻塞當(dāng)前線程,一直到提交成功,并且會(huì)自動(dòng)失敗重試(由不可控因素導(dǎo)致,也會(huì)出現(xiàn)提交失?。欢惒教峤粍t沒(méi)有失敗重試機(jī)制,故有可能提交失敗。
commitSync(同步提交):必須等待offset提交完畢,再去消費(fèi)下一批數(shù)據(jù)。 commitAsync(異步提交) :發(fā)送完提交offset請(qǐng)求后,就開(kāi)始消費(fèi)下一批數(shù)據(jù)了。

- 同步提交 offset
由于同步提交 offset 有失敗重試機(jī)制,故更加可靠,但是由于一直等待提交結(jié)果,提交的效率比較低。以下為同步提交 offset 的示例。
// 0 配置
Properties properties = new Properties();
// 連接 bootstrap.servers
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// 反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// 配置消費(fèi)者組id
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
// 手動(dòng)提交
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,false);
// 1 創(chuàng)建一個(gè)消費(fèi)者 "", "hello"
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 2 訂閱主題 first
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
// 3 消費(fèi)數(shù)據(jù)
while (true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
// 同步提交 offset
kafkaConsumer.commitSync();
}- 異步提交 offset
雖然同步提交 offset 更可靠一些,但是由于其會(huì)阻塞當(dāng)前線程,直到提交成功。因此吞吐量會(huì)受到很大的影響。因此更多的情況下,會(huì)選用異步提交 offset 的方式。
// 0 配置
Properties properties = new Properties();
// 連接 bootstrap.servers
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// 反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// 配置消費(fèi)者組id
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
// 手動(dòng)提交
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,false);
// 1 創(chuàng)建一個(gè)消費(fèi)者 "", "hello"
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 2 訂閱主題 first
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
// 3 消費(fèi)數(shù)據(jù)
while (true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
// 同步提交 offset
kafkaConsumer.commitAsync();
}4.3 指定 Offset 消費(fèi)
auto.offset.reset = earliest | latest | none 默認(rèn)是 latest。 當(dāng) Kafka 中沒(méi)有初始偏移量(消費(fèi)者組第一次消費(fèi))或服務(wù)器上不再存在當(dāng)前偏移量時(shí)(例如該數(shù)據(jù)已被刪除),該怎么辦?
(1)earliest:自動(dòng)將偏移量重置為最早的偏移量,–from-beginning。
(2)latest(默認(rèn)值):自動(dòng)將偏移量重置為最新偏移量。
(3)none:如果未找到消費(fèi)者組的先前偏移量,則向消費(fèi)者拋出異常。

// 0 配置信息
Properties properties = new Properties();
// 連接
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// 反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// 組id
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test3");
// 1 創(chuàng)建消費(fèi)者
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 2 訂閱主題
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
// 指定位置進(jìn)行消費(fèi)
Set<TopicPartition> assignment = kafkaConsumer.assignment();
// 保證分區(qū)分配方案已經(jīng)制定完畢
while (assignment.size() == 0){
kafkaConsumer.poll(Duration.ofSeconds(1));
assignment = kafkaConsumer.assignment();
}
// 指定消費(fèi)的offset
for (TopicPartition topicPartition : assignment) {
kafkaConsumer.seek(topicPartition,600);
}
// 3 消費(fèi)數(shù)據(jù)
while (true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}4.4 指定時(shí)間消費(fèi)
需求:在生產(chǎn)環(huán)境中,會(huì)遇到最近消費(fèi)的幾個(gè)小時(shí)數(shù)據(jù)異常,想重新按照時(shí)間消費(fèi)。例如要求按照時(shí)間消費(fèi)前一天的數(shù)據(jù),怎么處理?
// 0 配置信息
Properties properties = new Properties();
// 連接
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// 反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// 組id
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test3");
// 1 創(chuàng)建消費(fèi)者
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 2 訂閱主題
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
// 指定位置進(jìn)行消費(fèi)
Set<TopicPartition> assignment = kafkaConsumer.assignment();
// 保證分區(qū)分配方案已經(jīng)制定完畢
while (assignment.size() == 0){
kafkaConsumer.poll(Duration.ofSeconds(1));
assignment = kafkaConsumer.assignment();
}
// 希望把時(shí)間轉(zhuǎn)換為對(duì)應(yīng)的offset
HashMap<TopicPartition, Long> topicPartitionLongHashMap = new HashMap<>();
// 封裝對(duì)應(yīng)集合
for (TopicPartition topicPartition : assignment) {
topicPartitionLongHashMap.put(topicPartition,System.currentTimeMillis() - 1 * 24 * 3600 * 1000);
}
Map<TopicPartition, OffsetAndTimestamp> topicPartitionOffsetAndTimestampMap = kafkaConsumer.offsetsForTimes(topicPartitionLongHashMap);
// 指定消費(fèi)的offset
for (TopicPartition topicPartition : assignment) {
OffsetAndTimestamp offsetAndTimestamp = topicPartitionOffsetAndTimestampMap.get(topicPartition);
kafkaConsumer.seek(topicPartition,offsetAndTimestamp.offset());
}
// 3 消費(fèi)數(shù)據(jù)
while (true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}五、消費(fèi)者事務(wù)
重復(fù)消費(fèi):已經(jīng)消費(fèi)了數(shù)據(jù),但是 offset 沒(méi)提交。
漏消費(fèi):先提交 offset 后消費(fèi),有可能會(huì)造成數(shù)據(jù)的漏消費(fèi)。


思考:怎么能做到既不漏消費(fèi)也不重復(fù)消費(fèi)呢?詳看消費(fèi)者事務(wù)。
如果想完成Consumer端的精準(zhǔn)一次性消費(fèi),那么需要Kafka消費(fèi)端將消費(fèi)過(guò)程和提交offset過(guò)程做原子綁定。此時(shí)我們需要將Kafka的offset保存到支持事務(wù)的自定義介質(zhì)(比 如MySQL)。

數(shù)據(jù)積壓(消費(fèi)者如何提高吞吐量)

| 參數(shù)名稱 | 描述 |
| fetch.max.bytes | 默認(rèn) Default: 52428800(50 m)。消費(fèi)者獲取服務(wù)器端一批消息最大的字節(jié)數(shù)。如果服務(wù)器端一批次的數(shù)據(jù)大于該值(50m)仍然可以拉取回來(lái)這批數(shù)據(jù),因此,這不是一個(gè)絕對(duì)最大值。一批次的大小受 message.max.bytes (broker config)or max.message.bytes (topic config)影響。 |
| max.poll.records | 一次 poll 拉取數(shù)據(jù)返回消息的最大條數(shù),默認(rèn)是 500 條 |
到此這篇關(guān)于Java中的Kafka消費(fèi)者詳解的文章就介紹到這了,更多相關(guān)Kafka消費(fèi)者內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IntelliJ IDEA查看方法說(shuō)明文檔的圖解
今天小編就為大家分享一篇關(guān)于IntelliJ IDEA查看方法說(shuō)明文檔的圖解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-10-10
Java中使用數(shù)組實(shí)現(xiàn)棧數(shù)據(jù)結(jié)構(gòu)實(shí)例
這篇文章主要介紹了Java中使用數(shù)組實(shí)現(xiàn)棧數(shù)據(jù)結(jié)構(gòu)實(shí)例,本文先是講解了實(shí)現(xiàn)棧至少應(yīng)該包括以下幾個(gè)方法等知識(shí),然后給出代碼實(shí)例,需要的朋友可以參考下2015-01-01
IDEA2020.1同步系統(tǒng)設(shè)置到GitHub的方法
這篇文章主要介紹了IDEA2020.1同步系統(tǒng)設(shè)置到GitHub的方法,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
Java高并發(fā)BlockingQueue重要的實(shí)現(xiàn)類詳解
這篇文章主要給大家介紹了關(guān)于Java高并發(fā)BlockingQueue重要的實(shí)現(xiàn)類的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
Redis 集成Spring的示例代碼(spring-data-redis)
本篇文章主要介紹了Redis 集成Spring的示例代碼(spring-data-redis) ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09
Spring MVC請(qǐng)求參數(shù)的傳遞方式
Spring MVC是一種基于Model-View-Controller(MVC)設(shè)計(jì)模式的輕量級(jí)Web框架,用于Java應(yīng)用程序的開(kāi)發(fā),在處理HTTP請(qǐng)求時(shí),Spring MVC會(huì)涉及到請(qǐng)求參數(shù)的傳遞,本文給大家介紹了Spring MVC請(qǐng)求參數(shù)的傳遞方式,需要的朋友可以參考下2024-10-10

