0%

rocketmq源码调试-图文详细

想看rocketmq源码?不知道怎么断点调试?这篇图文详细描述整个调试过程。尽情享受驾驭源码的乐趣吧。

idea拉取rocketmq代码

1
git clone https://gitee.com/apache/rocketmq.git

image-20250117152803367

设置路径

image-20250117152822022

切换分支

image-20250117152933721

刷新项目

image-20250117152854069

配置目录

在根目录下设置config目录

image-20250117153132085

分别配置conf、logs、store目录

image-20250117153248671

拷贝distribution下的三个文件

image-20250117153339132

到conf目录下

image-20250117153416636

修改broker.conf如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH

#nameserver的地址,也可以指定真实ip
namesrvAddr=127.0.0.1:9876
#brokerIp,也可以指定真实ip
brokerIP1=127.0.0.1
#消息存储根路径
storePathRootDir=C:/Users/27338/IdeaProjects/rocketmq/config/store
#commitLog文件的存储路径
storePathCommitLog=C:/Users/27338/IdeaProjects/rocketmq/config/store/commitlog
#consume queue文件的存储路径
storePathConsumeQueue=C:/Users/27338/IdeaProjects/rocketmq/config/store/consumequeue
#消息索引文件的存储路径
storePathIndex=C:/Users/27338/IdeaProjects/rocketmq/config/store/index
#checkpoint文件的存储路径
storeCheckpoint=C:/Users/27338/IdeaProjects/rocketmq/config/store/checkpoint
#abort文件的存储路径
abortFile=C:/Users/27338/IdeaProjects/rocketmq/config/store/abort

修改日志文件的配置

1、将logback_namesrv.xml和logback_broker.xml配置文件中的所有${user.home}都替换为自己的ROCKETMQ_HOME的目录,例如

{user.home}/logs/rocketmqlogs/namesrv_default.log,我会替换为C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/namesrv_default.log;

2、将所有的${brokerLogDir}都替换为broker

logback_namesrv.xml参考文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<configuration>
<appender name="DefaultAppender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/namesrv_default.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/otherdays/namesrv_default.%i.log.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>5</maxIndex>
</rollingPolicy>
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>100MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>

<appender name="RocketmqNamesrvAppender_inner"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/namesrv.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/otherdays/namesrv.%i.log.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>5</maxIndex>
</rollingPolicy>
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>100MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
<appender name="RocketmqNamesrvAppender" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="RocketmqNamesrvAppender_inner"/>
<discardingThreshold>0</discardingThreshold>
</appender>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<append>true</append>
<encoder>
<pattern>%d{yyy-MM-dd HH\:mm\:ss,SSS} %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>

<logger name="RocketmqNamesrv" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqNamesrvAppender"/>
</logger>

<logger name="RocketmqCommon" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqNamesrvAppender"/>
</logger>

<logger name="RocketmqRemoting" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqNamesrvAppender"/>
</logger>

<logger name="RocketmqNamesrvConsole" additivity="false">
<level value="INFO"/>
<appender-ref ref="STDOUT"/>
</logger>

<root>
<level value="INFO"/>
<appender-ref ref="DefaultAppender"/>
</root>
</configuration>

logback_broker.xml参考文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<configuration>
<appender name="DefaultAppender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/broker/broker_default.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/otherdays/broker/broker_default.%i.log.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>100MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>

<appender name="RocketmqBrokerAppender_inner"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/broker/broker.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/otherdays/broker/broker.%i.log.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>20</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>128MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
<appender name="RocketmqBrokerAppender" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="RocketmqBrokerAppender_inner"/>
</appender>

<appender name="RocketmqProtectionAppender_inner"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/broker/protection.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/otherdays/broker/protection.%i.log.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>100MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
<appender name="RocketmqProtectionAppender" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="RocketmqProtectionAppender_inner"/>
</appender>

<appender name="RocketmqWaterMarkAppender_inner"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/broker/watermark.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/otherdays/broker/watermark.%i.log.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>100MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
<appender name="RocketmqWaterMarkAppender" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="RocketmqWaterMarkAppender_inner"/>
</appender>

<appender name="RocketmqStoreAppender_inner"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/broker/store.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/otherdays/broker/store.%i.log.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>128MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
<appender name="RocketmqStoreAppender" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="RocketmqStoreAppender_inner"/>
</appender>

<appender name="RocketmqRemotingAppender_inner"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/broker/remoting.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/otherdays/broker/remoting.%i.log.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>100MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
<appender name="RocketmqRemotingAppender" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="RocketmqRemotingAppender_inner"/>
</appender>

<appender name="RocketmqStoreErrorAppender_inner"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/broker/storeerror.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/otherdays/broker/storeerror.%i.log.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>100MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
<appender name="RocketmqStoreErrorAppender" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="RocketmqStoreErrorAppender_inner"/>
</appender>


<appender name="RocketmqTransactionAppender_inner"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/broker/transaction.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/otherdays/broker/transaction.%i.log.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>100MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
<appender name="RocketmqTransactionAppender" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="RocketmqTransactionAppender_inner"/>
</appender>

<appender name="RocketmqRebalanceLockAppender_inner"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/broker/lock.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/otherdays/broker/lock.%i.log.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>5</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>100MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
<appender name="RocketmqRebalanceLockAppender" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="RocketmqRebalanceLockAppender_inner"/>
</appender>

<appender name="RocketmqFilterAppender_inner"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/broker/filter.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/otherdays/broker/filter.%i.log.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>100MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
<appender name="RocketmqFilterAppender" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="RocketmqFilterAppender_inner"/>
</appender>

<appender name="RocketmqStatsAppender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/broker/stats.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/otherdays/broker/stats.%i.log.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>5</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>100MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} %p - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>

<appender name="RocketmqCommercialAppender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/broker/commercial.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>C:/Users/27338/IdeaProjects/rocketmq/config/logs/rocketmqlogs/otherdays/broker/commercial.%i.log.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>500MB</maxFileSize>
</triggeringPolicy>
</appender>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<append>true</append>
<encoder>
<pattern>%d{yyy-MM-dd HH\:mm\:ss,GMT+8} %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>

<logger name="RocketmqBroker" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqBrokerAppender"/>
</logger>

<logger name="RocketmqProtection" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqProtectionAppender"/>
</logger>

<logger name="RocketmqWaterMark" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqWaterMarkAppender"/>
</logger>

<logger name="RocketmqCommon" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqBrokerAppender"/>
</logger>

<logger name="RocketmqStore" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqStoreAppender"/>
</logger>

<logger name="RocketmqStoreError" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqStoreErrorAppender"/>
</logger>

<logger name="RocketmqTransaction" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqTransactionAppender"/>
</logger>

<logger name="RocketmqRebalanceLock" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqRebalanceLockAppender"/>
</logger>

<logger name="RocketmqRemoting" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqRemotingAppender"/>
</logger>

<logger name="RocketmqStats" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqStatsAppender"/>
</logger>

<logger name="RocketmqCommercial" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqCommercialAppender"/>
</logger>

<logger name="RocketmqFilter" additivity="false">
<level value="INFO"/>
<appender-ref ref="RocketmqFilterAppender"/>
</logger>

<logger name="RocketmqConsole" additivity="false">
<level value="INFO"/>
<appender-ref ref="STDOUT"/>
</logger>

<root>
<level value="INFO"/>
<appender-ref ref="DefaultAppender"/>
</root>
</configuration>

启动namesrv

找到namesrv启动类

image-20250117154040197

尝试启动

image-20250117154150245

提示报错

image-20250117154343788

编辑配置

image-20250117154241402

添加如下环境变量

1
ROCKETMQ_HOME=C:/Users/27338/IdeaProjects/rocketmq/config

image-20250117154331690

再次启动,可以看到启动成功

image-20250117154438933

启动broker

同样设置环境变量

1
ROCKETMQ_HOME=C:/Users/27338/IdeaProjects/rocketmq/config

多了一个参数配置

1
-c C:/Users/27338/IdeaProjects/rocketmq/config/conf/broker.conf

image-20250117154700285

找到启动类,启动

image-20250117154813220

看到启动成功

image-20250117154856711

也可以从启动日志中看

image-20250117154949438

启动rocketmq-dashboard

idea同样拉取rocketmq-dashboard代码

1
git clone https://github.com/apache/rocketmq-dashboard.git

修改application.yml配置文件中的namesrvAddrs配置,改为本地namesrv地址:

image-20250117155059298

启动

image-20250117155121128

看到启动成功

image-20250117155149975

浏览器访问

输入localhost:8080

image-20250117155223236

测试

找到测试代码,修改生产者配置,启动

image-20250117155321270

可以看到消息发送成功

image-20250117155403112

尝试消费

image-20250117155435756

消费成功

image-20250117155457694

控制台可以看到消费情况

image-20250117155540792

二分查找通常将待查找区间分为两部分并只取一部分继续查找,可以大大减少查找的复杂度。

求开方

  1. Sqrt(x) (Easy)

题目描述

  给定一个非负整数,求它的开方,向下取整。

输入输出样例

  输入是一个整数,输出一个整数。

1
2
Input: 8
Output: 2

c++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int mySqrt(int a) {
if (a == 0) return 0;
int l = 1, r = a, mid, sqrt;
while (l <= r) {
mid = l + (r - 1) / 2;
sqrt = a / mid;
if (sqrt == mid) {
return mid;
} else if (mid > sqrt) {
r = mid - 1;
} else {
l = mid + 1;
}
}
return r;
}

java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//注意int强转long的用法
class Solution {
public int mySqrt(int x) {
int l = 0;
int r = x;
int ans = -1;
while (l <= r) {
int mid = (l + r) / 2;
if ((long)mid * mid <= x) {
l = mid + 1;
ans = mid;
} else {
r = mid - 1;
}
}
return ans;
}
}

查找区间

  1. Find First and Last Position of Element in Sorted Array (Medium)

题目描述

  给定一个增序的整数数组和一个值,查找该值第一次和最后一次出现的位置。

输入输出样例

  输入是一个数组和一个值,输出为该值第一次出现的位置和最后一次出现的位置(从0开始);如果不存在该值,则两个返回值都设为-1。

1
2
Input: nums = [5,7,7,8,8,10], target = 8
Output: [3, 4]

c++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
vector<int> searchRange(vector<int>& nums, int target) {
if (nums.empty()) return vector<int>{-1, -1};
int lower = lower_bound(nums, target);
int upper = upper_bound(nums, target) - 1;
if (lower == nums.size() ## nums[lower] != target) {
retrun vector<int>{-1, -1};
}
}

int lower_bound(vector<int>& nums, int target) {
int l = 0, r = nums.size(), mid;
while (l < r) {
mid = (l + r) / 2;
if (nums[mid] >= target) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
}

int upper_bound(vector<int>& nums, int target) {
int l = 0, r = nums.size(), mid;
while (l < r) {
mid = (l + r) / 2;
if (nums[mid] > target) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
}

java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//拆解为两个二分查找,注意收敛边界
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = binarySearch(nums, target, true);
int right = binarySearch(nums, target, false);
return new int[]{left, right};
}

public int binarySearch(int[] nums, int target, Boolean leftFlag) {
int result = -1;
if (nums.length <= 0) {
return result;
}
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] == target && leftFlag && (0 == mid || nums[mid - 1] < nums[mid])) {
return mid;
} else if (nums[mid] == target && !leftFlag && (mid == nums.length - 1 || nums[mid + 1] > nums[mid])) {
return mid;
} else if ((leftFlag && nums[mid] >= target) || (!leftFlag && nums[mid] > target)) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return result;
}
}

旋转数组查找数字

  1. Search in Rotated Sorted Array (Medium)

题目描述

  一个原本增序的数组被首尾相连后按某个位置断开(如[1,2,2,3,4,5]->[2,3,4,5,1,2],在第一位和第二位断开),我们称其为旋转数组。给定一个值,判断这个值是否存在于这个为旋转数组中。

输入输出样例

  输入是一个数组和一个值,输出是一个布尔值,表示数组中是否存在于这个旋转数组中。

1
2
Input: nums = [2,5,6,0,0,1,2], target = 0
Output: true

c++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bool search(vector<int>& nums, int target) {
int start = 0, end = nums.size() - 1;
while (start < end) {
mid = (start + end) / 2;
if (nums[mid] == target) {
return true;
}
if (nums[start] == nums[mid]) {
++start;
} else if (nums[mid] <= nums[end]) {
if (target > nums[mid] && target <= nums[end]) {
start = mid + 1;
} else {
end = mid - 1;
}
} else {
if (target >= nums[start] && target < nums[mid]) {
end = mid - 1;
} else {
start = mid + 1;
}
}
}
return flase;
}

java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//注意区分有序空间
class Solution {
public boolean search(int[] nums, int target) {
// nums = [2,5,6,0,0,1,2]
int left = 0;
int right = nums.length - 1;
boolean res = false;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
return true;
} else if (nums[left] < nums[mid]) { //在前面升区间上
if (nums[mid] < target) {
left = mid + 1;//一定对的,因为它右边的大
} else if (nums[left] > target) {//左边的最小值的大于它,可能在右边
left = mid + 1;
} else {//否则一定在左边
right = mid - 1;
}
} else if (nums[left] > nums[mid]) {
// nums = [2,5,6,0,0,1,2]
if (nums[mid] > target) { //在后面升区间上
right = mid - 1;//一定对的,小的在左边
} else if (nums[right] < target) {
right = mid - 1;//最右边的最大值还没达到最大值,在左边
} else {
left = mid + 1;
}
} else {
left++;
}
}
return res;
}
}

第一章 序章

关于LeetCode

LeetCode主要是用在面试中,在有限的时间内考验小镇做题家的算法能力

关于作者

万和晧是读研时的舍友,互相监督刷题卷一下嘿嘿

编程语言

万擅长用java, 晧用c++各写一遍

要查看 Docker 容器中的存储卷或挂载点是如何映射到主机的,可以通过以下步骤来检查:

1. 使用 docker inspect 命令

docker inspect 命令可以查看容器的详细配置信息,其中包括挂载的卷(volumes)和绑定挂载(bind mounts)。

具体命令如下:

1
docker inspect <container_name_or_id>

这个命令将返回一个包含容器所有信息的 JSON 文件。你可以从其中找到有关存储挂载的部分。

2. 查找卷和绑定挂载信息

docker inspect 返回的结果中,查找以下字段:

  • "Mounts": 列出所有卷和绑定挂载信息。

示例输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"Mounts": [
{
"Type": "bind",
"Source": "/host/path",
"Destination": "/container/path",
"Mode": "rw",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "volume",
"Name": "my_volume",
"Source": "/var/lib/docker/volumes/my_volume/_data",
"Destination": "/container/volume",
"Driver": "local",
"Mode": "rw",
"RW": true,
"Propagation": ""
}
]

在这个例子中:

  • 绑定挂载(bind mount):
    • 容器内部的 /container/path 挂载到了主机的 /host/path
  • 卷(volume):
    • 容器内部的 /container/volume 使用了 Docker 卷 my_volume,存储在主机的 /var/lib/docker/volumes/my_volume/_data 目录下。

3. 过滤查看挂载点

如果你只想查看挂载点信息,可以使用 docker inspect--format 选项来过滤信息。比如只显示挂载点信息:

1
docker inspect --format='{{json .Mounts}}' <container_name_or_id>

这样可以直接输出容器的挂载信息,简化结果。

总结

通过 docker inspect 命令,你可以清楚地看到容器中的存储卷或者挂载的目录对应主机上的哪个路径。

根据时间复杂度的不同,大致分为3类:

  1. 时间复杂度O(n^2)的排序算法: 冒泡,选择,插入,希尔排序
  2. 时间复杂度O(nlogn)的排序算法: 快速,归并,堆排序
  3. 时间复杂度为线性的排序算法: 计数,桶排序,基数排序

快速排序(Quicksort)

c++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void quicksort(vector<int> &nums, int l ,int r) {
if (l + 1 >= r) {
return;
}
int first = l, last = r - 1, key = nums[first];
while (fisrt < last) {
while (fist < last && nums[last] >= key) {
--last;
}
nums[first] = nums[last];
while (fist < last && nums[first] <= key) {
++first;
}
nums[last] = nums[first];
}
nums[first] = key;
quicksort(nums, l, first);
quicksort(nums, first + 1, r);
}

java:

1
@houwanwan

window使用visual studio community 2017编译安装openjdk18,并使用clion断点调试openjdk18源码-图文详细

想看JDK源码?还想在window环境下调试?那么请看向这篇文章吧。如果能在window下断点调试openjdk源码那将会非常方便,本文就将整个调试过程图文记录下来。

源码下载

1
git clone https://github.com/openjdk/jdk.git

切换到openjdk18分支

1
git checkout jdk-18+9

安装cygwin

下载地址

https://www.cygwin.com/install.html

从互联网安装

image-20241230095828413

配置根目录

image-20241230095857917

直接连接

image-20241230095922989

选择阿里云镜像

image-20241230095940366

选择安装包

image-20241230095959846

配置环境变量

搜索环境变量

image-20250117103931985

点击环境变量

image-20250117103952617

双击Path

image-20250117104109079

配置环境变量

image-20250117104228485

安装visual studio 2017

下载地址

https://aka.ms/vs/15/release/vs_community.exe

选择C++桌面开发

image-20241230095601656

只选英语

image-20241230095617418

配置安装位置

注意路径不要有空格

image-20241230095642351

安装成功后看到编译工具链

image-20241230101803133

安装前导boot jdk

一般是编译版本的前一个版本(N-1版本),例如本次编译openjdk18,那么前导版本就是openjdk17

下载地址

https://jdk.java.net/archive/

image-20250117104830526

解压

image-20241230100602912

源码编译

进入cygwin命令终端

image-20241230100125425

进入jdk源码目录

1
cd /cygdrive/d/opensource/jdk/

image-20250117104911873

执行配置命令

1
./configure --with-target-bits=64 --with-debug-level=slowdebug ZIP_DEBUGINFO_FILES=0 --with-jvm-variants=server --with-tools-dir='/cygdrive/f/soft/MicrosoftVisualStudio/2017/Community/VC/Auxiliary/Build' --with-boot-jdk=/cygdrive/d/conf/jdk-17.0.1 --disable-warnings-as-errors

image-20250117103334778

看到执行成功

image-20250117103607193

生成镜像

1
make images CONF=windows-x86_64-server-slowdebug

image-20250117103826770

看到生成成功

image-20250117104302041

测试

进入生成执行命令目录

1
cd build/windows-x86_64-server-slowdebug/

image-20250117104339524

进入jdk目录

1
cd jdk

image-20250117104357199

进入bin目录

image-20250117104420582

打印版本号

可以看到编译成功

image-20250117104437338

安装调试环境

执行命令

1
make vscode-project CONF=windows-x86_64-server-slowdebug

image-20250117105332790

可以看到执行成功

image-20250117110643349

可以看到生成了对应的compile_commands.json文件

image-20250117110712789

安装clion

下载地址

https://www.jetbrains.com/zh-cn/clion/download/#section=windows

使用clion调试openjdk18

打开项目

image-20250117105812634

选择compile_commands.json,点击ok

image-20250117105848111

打开作为一个项目

image-20250117105922309

打开如下

image-20250117110013072

点击设置

image-20250117110031682

配置visual studio工具链

image-20250117110105568

配置编译目标,点击三个点

image-20250117110201441

新增一个

image-20250117110235999

配置如下内容

image-20250117110247942

在配置一个清理策略

image-20250117110311477

分别选中两个,点击右下角的Apply,然后点击OK

image-20250117110339889

点击编辑配置

image-20250117110456283

配置自定义编译应用,选择编译的java,并执行java -version命令

点击右下角Apply,然后点击OK

image-20250117110516879

点击运行

image-20250117110755551

可以看到正在编译

image-20250117110850617

生成输出结果

image-20250117111017246

点击目录切换

image-20250117111101239

指向jdk的根目录

image-20250117111113646

可以看到已经切换成功

image-20250117111148149

找到jdk运行主类,并设置断点

image-20250117111233262

点击调试

image-20250117111300381

恭喜,已经进入断点调试模式

image-20250117111338700

继续执行单步调试

image-20250117111725828

可以看到执行到了打印版本号代码

image-20250117111807051

且已经输出到了控制台

image-20250117111926036

参考文章

https://openjdk.org/groups/build/doc/building.html

https://jvm-insider.mygraphql.com/zh-cn/latest/appendix-lab-env/build-jdk/build-slow-debug-jdk.html

https://blog.csdn.net/ciqingloveless/article/details/81950308

https://mayflygame.com/2022/05/04/OpenJDK%E8%B0%83%E8%AF%95/

https://juejin.cn/post/7325791449665257524

https://blog.csdn.net/qq_43460743/article/details/129202296

centos7中java使用JNI访问jvm的naive方法举例

​ java使用的naive方法,底层是调用的c语言实现的,JNI就是java调用底层c语言的桥梁,下面举个例子。

java项目中创建JNIDemo类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HelloJNI {
static {
System.loadLibrary("jnidemo"); // Load native library at runtime
// jnidemo.dll (Windows) or libjnidemo.so (Unixes)
}

// Declare a native method sayHello() that receives nothing and returns void
private native void sayHello();

// Test Driver
public static void main(String[] args) {
new HelloJNI().sayHello(); // invoke the native method
}
}

编译

1
javac HelloJNI.java

生成HelloJNI.class文件

image-20241217110241595

生成头文件

1
javah HelloJNI

生成如下HelloJNI.h文件

image-20241217110252668

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloJNI
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloJNI_sayHello
(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

使用clion创建C++项目

image-20241217103709937

拷贝HelloJNI.h到clion项目中

image-20241217105224914

拷贝jni.h到clion项目中

  • windows去本地jdk安装目中找<jdk安装目录>/include/jni.h
  • linux去本地jdk安装目录找<jdk安装目录>/include/jni.h

image-20241217104353147

image-20241217104500463

拷贝jni_md.h到clion项目中

  • windows去本地jdk安装目中找<jdk安装目录>/include/win32/jni_md.h
  • linux去本地jdk安装目录找<jdk安装目录>/include/linux/jni_md.h

image-20241217105018310

image-20241217105110117

创建HelloJNI.cpp文件,实现helloJni()方法

1
2
3
4
5
6
7
8
#include "jni.h"
#include <stdio.h>
#include "HelloJNI.h"

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject){
printf("Hello World!\n");
return;
}

image-20241217105339221

HelloJNI.cpp文件添加到CMakeList.txt

image-20241217104637296

编译

看到编译成功

image-20241217105404074

将libjnidemo.so拷贝到测试文件夹下

image-20241217105535022

执行测试案例

1
java -Djava.library.path=./ HelloJNI

看到执行成功

image-20241217110108704

centos7源码编译openjdk11,并使用Clion调试openjdk11源码

源码下载

1
git clone https://github.com/openjdk/jdk.git

切换到openjdk11分支

1
git checkout jdk-11+24

执行验证

1
git status

image-20241216105615647

编译工具安装

1
sudo yum groupinstall "Development Tools"

准备openjdk10作为N-1版本启动引导

https://jdk.java.net/archive/

image-20241216104133390

解压

image-20241216105101198

执行安装脚本

1
bash configure --with-boot-jdk=/home/hou/jdk-10.0.1

提示报错1

image-20241216103154765

checking for X11/extensions/shape.h… configure: error: Could not find all X11 headers (shape.h Xrender.h XTest.h Intrinsic.h). You might be able to fix this by running ‘sudo yum install libXtst-devel libXt-devel libXrender-devel libXi-devel’.

执行安装

1
sudo yum install libXtst-devel libXt-devel libXrender-devel libXi-devel

再次执行安装脚本

1
bash configure --with-boot-jdk=/home/hou/jdk-10.0.1

提示报错2

image-20241216103340413

checking cups/ppd.h presence… configure: error: Could not find cups! You might be able to fix this by running ‘sudo yum install cups-devel’.

执行安装

1
sudo yum install cups-devel

再次执行安装脚本

1
bash configure --with-boot-jdk=/home/hou/jdk-10.0.1

提示报错3

image-20241216103434766

configure: error: Could not find fontconfig! You might be able to fix this by running ‘sudo yum install fontconfig-devel’.

执行安装

1
sudo yum install fontconfig-devel

再次执行安装脚本

1
bash configure --with-boot-jdk=/home/hou/jdk-10.0.1

提示报错4

image-20241216103527628

configure: error: Could not find alsa! You might be able to fix this by running ‘sudo yum install alsa-lib-devel’.

执行安装

1
sudo yum install alsa-lib-devel

再次执行安装脚本

1
bash configure --with-boot-jdk=/home/hou/jdk-10.0.1

提示构建成功

image-20241216103621422

执行make,生成镜像

1
make images

image-20241216105004336

创建成功

image-20241216105537054

验证安装

1
./build/*/images/jdk/bin/java -version

image-20241216105149079

执行成功

启用调试模式

1
bash configure --with-boot-jdk=/home/hou/jdk-10.0.1 --disable-warnings-as-errors --with-debug-level=slowdebug --with-jvm-variants=server
1
2
3
# disable-warnings-as-errors选项是禁止把warning 当成error
# --with-debug-level=slowdebug。用来设置编译的级别,可选值为release、fastdebug、slowde-bug,越往后进行的优化措施就越少,带的调试信息就越多。默认值为release。slowdebug 含有最丰富的调试信息,没有这些信息,很多执行可能被优化掉,我们单步执行时,可能看不到一些变量的值。所以最好指定slowdebug 为编译级别。
# with-jvm-variants 编译特定模式的HotSpot虚拟机,可选值:server、client、minimal、core、zero、custom
1
make images

编译成功

image-20241216141151988

执行测试

1
./build/*/images/jdk/bin/java -version

image-20241216141240447

下载Clion导入源码项目

设置自定义编译目标

image-20241216160339380

配置编译命令

image-20241216160411712

配置clean命令

image-20241216160438438

分别配置对应的选项

image-20241216160533177

编辑配置

image-20241216160557945

选中编译的java命令,并执行version

image-20241216160614096

启动运行

image-20241216160704850

启动调试

打断点在java.c上

image-20241216160738145

执行调试

image-20241216160802545

点击继续运行

image-20241216160827103

可以看到运行成功

image-20241216160908592

可以看到报错1

image-20241216161447221

由于HotSpot JVM内部使用了SEGV等信号来实现一些功能(如NullPointerExceptionsafepoints等),所以调试过程中,GDB可能会误报Signal: SIGSEGV (Segmentation fault). 解决办法是,在用户目录下创建.gdbinit,让GDB捕获SEGV等信号:

1
vim ~/.gdbinit

将以下内容追加到文件中并保存:

1
2
handle SIGSEGV pass noprint nostop 
handle SIGBUS pass noprint nostop

重新运行就没有相关报错了

image-20241216161715500

能在 Switch 中使用 String 吗?且分析原理

是的,从 Java 7 开始,switch 语句可以使用 String 类型。在使用 String 作为 switch 的表达式时,Java 会根据每个 case 中的字符串计算其哈希值来做判断。

原理详细分析

在 Java 中,switch 语句从 Java 7 开始支持 String 类型。其工作原理与使用整数类型的 switch 语句不同,因为字符串是对象,字符串比较涉及到内存地址和内容的比较,不能直接作为枚举的比较项。因此,Java 在编译期间会对 Stringswitch 做一系列的转换和优化。以下是更详细的分解:

1. 编译器的转换过程

switch 使用 String 作为输入时,编译器会将它转换为使用 hashCode 方法和 equals 方法的组合。具体步骤如下:

  • 第一步:Java 编译器将 switch 语句的 String 表达式转换为 String.hashCode() 值进行处理。hashCode 返回的是一个 int 值,因此转换后的 switch 语句类似于 int 类型的 switch 语句。
  • 第二步:编译器为 switch 语句生成一个“跳转表”(即使用 hashCode 匹配的映射表),将每个 case 标签的 String 表达式计算出 hashCode 并进行相应的映射。这一表格帮助在执行时快速找到匹配项。

2. 运行时工作机制

当代码执行到 switch 语句时,会先对传入的 String 调用 hashCode 方法并将其与 case 语句的 hashCode 逐一对比:

  • 如果 hashCode 匹配,进入下一步的字符串验证,比较输入字符串和 case 中的字符串是否相同(使用 equals 方法)。
  • 如果 hashCode 匹配,但 equals 不匹配,说明产生了 哈希冲突,跳过此 case,继续寻找下一个匹配的 case 标签。
  • 如果没有任何匹配项,则执行 default 代码块(如果有 default)。

3. 示例及工作原理

以如下代码为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SwitchWithStringExample {
public static void main(String[] args) {
String day = "MONDAY";

switch (day) {
case "MONDAY":
System.out.println("Start of the work week!");
break;
case "FRIDAY":
System.out.println("End of the work week!");
break;
default:
System.out.println("Midweek day");
break;
}
}
}

编译后(伪代码):

编译后的代码大致等效于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int hash = day.hashCode();
switch (hash) {
case 744853702: // hash code for "MONDAY"
if (day.equals("MONDAY")) {
System.out.println("Start of the work week!");
break;
}
case -1402213467: // hash code for "FRIDAY"
if (day.equals("FRIDAY")) {
System.out.println("End of the work week!");
break;
}
default:
System.out.println("Midweek day");
break;
}

解释

  • day.hashCode() 计算的整数值会用于 switch 语句的分支选择。
  • 在每个 case 中,首先通过 hashCode 值判断是否有可能匹配。如果 hashCode 相同(744853702 对应 “MONDAY”,-1402213467 对应 “FRIDAY”),进一步使用 equals 比较字符串内容,以确认 day 确实为该 case 语句指定的字符串。
  • default 匹配 hashCodeequals 都未匹配的情况,表示该 switch 中无对应的 case 分支。

4. 优势与限制

  • 性能优化:相比一组连续的 if-else 字符串对比,switch 通过先 hashCode 匹配再 equals 校验的方式提高了速度,减少了直接调用 equals 的次数。
  • 哈希冲突:由于 hashCode 是一种散列算法,不同字符串可能具有相同的 hashCode。在这种情况下,编译后的代码会在 hashCode 相等后再调用 equals,确保得到的结果准确无误。
  • null 值限制switch 不允许 null 作为输入值,传入 null 会导致 NullPointerException。这是因为 null 没有 hashCode 值,无法进行哈希比较。

5. 总结

  • switch 使用 String 时,编译器会先通过 hashCode 匹配,再用 equals 方法校验。
  • String switch 适合用在较小的字符串集内,以减少哈希冲突。
  • 相比 if-else 的优势:这种处理方式减少了重复字符串比较(直接调用 equals),提升了性能。

为什么 3 * 0.1 == 0.3 在 Java 中会返回 false?(详细原理)

要详细解释 3 * 0.1 == 0.3 返回 false 的原因,涉及到计算机如何存储浮点数以及它们在底层的表示原理。让我们逐步解释这一点。

1. 浮点数的表示

计算机使用 IEEE 754 标准来存储浮点数。这个标准将浮点数分为三部分:

  • 符号位:决定数值是正数还是负数。
  • 指数位:用来表示数值的规模,即类似于科学计数法中的指数部分。
  • 尾数位(或称为有效数字部分):表示数值的精度。

2. 为什么 0.1 无法精确表示?

计算机使用二进制来表示数字,而某些十进制的数(例如 0.1)在二进制系统中是无法精确表示的,就像某些分数在十进制系统中是无限循环小数一样(例如,1/3 = 0.333…)。

二进制中的 0.1:

0.1 的二进制表示是一个无限循环的小数:

1
0.1 十进制 = 0.000110011001100110011001100110011...(二进制)

由于浮点数只能存储有限的位数(双精度浮点数一般是 53 位有效位),计算机会对这个值进行截断或四舍五入。因此,0.1 并不是精确存储的,而是一个近似值

类似地,0.3 也不能被精确表示为二进制浮点数。

3. 乘法操作的误差

当我们执行 3 * 0.1 时,计算机内部实际计算的是 3 * 0.000110011001100110011001... 这个近似的二进制值。由于浮点数表示的有限精度,这个结果不会精确等于 0.3,而是一个非常接近 0.3 的值。

具体的结果可能类似于:

1
3 * 0.1 ≈ 0.30000000000000004

因此,3 * 0.1 == 0.3 进行比较时,计算机发现两者在底层二进制的表示并不完全相同,最终导致返回 false

4. 精度损失的示例

我们可以通过输出 3 * 0.1 来查看实际结果:

1
2
3
4
5
6
public class FloatTest {
public static void main(String[] args) {
double result = 3 * 0.1;
System.out.println(result); // 输出:0.30000000000000004
}
}

5. 为什么会有这种误差?

误差的根源在于浮点数的有限位数表示。浮点数的尾数部分只能表示有限的位数,而像 0.1 这样的小数在二进制系统中需要无限位数才能精确表示。因为尾数的限制,浮点数只能存储某个数字的近似值,从而导致误差在数学运算(如乘法)中进一步累积。

6. 如何处理这种误差?

由于浮点数无法精确表示某些小数,直接比较浮点数的相等性可能导致不准确的结果。在实际编程中,我们可以使用以下方法来避免这种问题:

1. 使用误差范围(容差)

为了比较两个浮点数是否“近似相等”,我们可以设置一个容差值(例如 0.000001),只要两个浮点数的差值小于这个容差,就认为它们是相等的。

1
2
3
4
5
6
7
8
9
10
public class FloatTest {
public static void main(String[] args) {
double result = 3 * 0.1;
if (Math.abs(result - 0.3) < 0.000001) {
System.out.println("True");
} else {
System.out.println("False");
}
}
}

2. 使用 BigDecimal 进行精确计算

BigDecimal 提供了高精度的数值运算,并可以避免由于浮点数精度导致的问题。对于涉及到财务计算等需要高精度的场景,BigDecimal 是更好的选择。

1
2
3
4
5
6
7
8
9
import java.math.BigDecimal;

public class BigDecimalTest {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = a.multiply(new BigDecimal("3"));
System.out.println(b.equals(new BigDecimal("0.3"))); // 输出:true
}
}

注意,这里使用了字符串作为 BigDecimal 的输入,避免了浮点数近似表示的问题。

7. 总结

  • 浮点数表示的精度问题:0.1 和 0.3 不能精确表示为二进制浮点数,计算时产生误差。
  • 误差来源:有限的位数导致在表示和运算过程中累积误差。
  • 解决方案:通过误差范围比较,或者使用 BigDecimal 进行精确计算。

这种现象不仅仅是 Java 中存在,在其他使用 IEEE 754 标准的编程语言中(如 Python、C++)也会遇到同样的问题。