【Python搞定車載自動化測試】——Python實現CAN總線Bootloader刷寫(含Python源碼)

系列文章目錄

【Python搞定車載自動化測試】系列文章目錄匯總

文章目錄

  • 系列文章目錄
  • 💯💯💯 前言💯💯💯
    • 一、環境搭建
      • 1.軟件環境
      • 2.硬件環境
    • 二、目錄結構
    • 三、源碼展示
      • 1.診斷基礎函數方法
      • 2.診斷業務函數方法
      • 3.27服務安全解鎖
      • 4.Bootloader刷寫
      • 5.配置參數
    • 四、測試報告
    • 五、完整源碼鏈接


💯💯💯 前言💯💯💯

在之前的專欄【如何學習CAN總線測試】中介紹了如何通過CAPL語言實現Bootloader刷寫,但CAPL語言依賴于Vector CANoe設備,隨著智能電動汽車行業的發展,目前也普遍使用Python語言實現Bootloader刷寫,本章節主要介紹如何使用Python語言來實現CAN總線UDS Bootloader刷寫。
首先回顧一下Bootloader刷寫基礎知識:

UDS(Unified Diagnostic Services)是一種汽車行業內廣泛使用的診斷標準,用于車輛電子控制單元(ECU)的診斷和編程。當提到“UDS Bootloader刷寫”,通常是指利用UDS協議對車輛ECU中的Bootloader進行更新或重新編程的過程。這一過程允許通過標準化的服務和子功能訪問ECU內部的軟件組件,包括其Bootloader,從而實現安全且可靠的遠程更新能力。以下是UDS Bootloader刷寫的簡要流程:


通信建立:
物理連接:首先,需要通過診斷接口(如OBD-II接口)將車輛與編程工具(或編程工作站)相連。
建立診斷會話:使用UDS的會話管理服務(如Diagnostic Session Control service)建立安全或編程會話。


安全訪問:
為了保護ECU不被非法訪問,刷寫前通常需要通過UDS的安全訪問服務(Security Access service)進行鑒權,這可能涉及密碼或密鑰交換。


刷寫準備:
讀取當前狀態:使用Read Data by Identifier (0x22) 或 Request Seed (0x27) 等服務獲取ECU當前狀態和刷寫條件。
編程模式激活:通過Programming Enable (0x2E) 服務使ECU進入可編程模式。


數據傳輸:
分區擦除:如果需要,先發送請求擦除指定Flash區域的命令(如Request Download (0x34))。
Bootloader傳輸:使用Transfer Data (0x36) 服務將新的Bootloader二進制文件分塊傳輸至ECU。


驗證與激活:
數據校驗:傳輸完成后,使用Request Transfer Exit (0x37) 結束傳輸,并通過Control Module Programming (0x31) 服務執行完整性校驗。
激活新Bootloader:確認無誤后,通過專門的服務命令激活新刷寫的Bootloader,可能需要復位ECU。


會話結束與驗證:
完成刷寫后,退出編程會話,回到正常工作模式,并通過診斷測試驗證ECU及新Bootloader的功能完整性。
整個過程中,嚴格遵循UDS協議的指令和服務,確保操作的可靠性和安全性,防止因錯誤操作導致ECU功能異常。此外,由于涉及到車輛安全和穩定性,此類操作通常由專業技術人員在符合制造商規范的條件下執行。


一、環境搭建

1.軟件環境

Python版本:python3.9
第三方庫:
pip install allure-pytest2.13.5
pip install can-isotp
2.0.4
pip install python-can4.3.1
pip install udsoncan
1.23.0
allure:安裝allure工具并設置環境變量,https://github.com/allure-framework/allure2/releases

2.硬件環境

支持CAN設備硬件接口:
在這里插入圖片描述
https://python-can.readthedocs.io/en/stable/configuration.html


二、目錄結構

在這里插入圖片描述
dll目錄:存放27服務安全解鎖DLL文件(同CANoe中使用的DLL文件)。
public_method目錄:存放公共函數方法和27安全解鎖工具。
test_case目錄:存放python自動化測試用例。
update目錄:存放升級包和效驗文件。
config.py文件:CAN相關配置參數。
run.py文件:運行入口文件。


三、源碼展示

1.診斷基礎函數方法

base_can.py文件主要封裝了CAN初始化、診斷配置、診斷請求、診斷響應基礎方法,源碼如下:

class CanBus:def __init__(self, interface: str = None, channel: int = None, bitrate: int = None, fd: bool = None,data_bitrate: int = None, can_filters: CanFilters = None, *args, **kwargs):self.interface = interfaceself.channel = channelself.bitrate = bitrateself.fd = fdself.data_bitrate = data_bitrateself.can_filters = can_filterstry:self.bus = can.interface.Bus(channel=self.channel, interface=self.interface, app_name="CANoe",bitrate=self.bitrate, fd=self.fd, data_bitrate=self.data_bitrate,can_filters=self.can_filters, *args, **kwargs)except Exception as e:raise Exception("初始化失敗:%s" % e)else:print("初始化成功")def diag_congfig(self, tx: int, rx: int, addressingmode=isotp.AddressingMode.Normal_11bits):"""診斷配置函數:param tx: 診斷請求ID,功能尋址、物理尋址:param rx: 診斷響應ID:return:"""self.isotp_params = {'stmin': 20,  # 流控幀間隔時間'blocksize': 8,  # 流控幀單包大小,0表示不限制'tx_padding': 0,  # 當 notNone表示用于填充發送的消息的字節。'rx_flowcontrol_timeout': 1000,  # 在停止接收和觸發之前等待流控制幀的毫秒數'rx_consecutive_frame_timeout': 1000,  # 在停止接收和觸發 a 之前等待連續幀的毫秒數}try:self.tp_addr = isotp.Address(addressing_mode=addressingmode, txid=tx, rxid=rx)  # 網絡層尋址方案tp_stack = isotp.CanStack(bus=self.bus, address=self.tp_addr, params=self.isotp_params)  # 網絡/傳輸層(IsoTP 協議)self.conn = PythonIsoTpConnection(tp_stack)  # 應用層和傳輸層之間的接口except Exception as e:print("UDS配置失敗:%s" % e)return self.conndef diag_request(self, request_command: str, request_data_log_flag=True):"""診斷請求"""requestPdu = binascii.a2b_hex(request_command.replace(' ', ''))if not self.conn.is_open():self.conn.open()try:self.conn.send(requestPdu)except Exception as e:print("診斷請求失敗:%s" % e)else:req_info = ''request_command = request_command.replace(' ', '')for i in range(len(request_command)):if i >= len(request_command) / 2:breakreq_info += request_command[2 * i:2 * i + 2] + ' 'if request_data_log_flag:print("診斷請求:%s" % req_info)def diag_respond(self, timeout1=1):"""診斷響應"""try:respPdu = self.conn.wait_frame(timeout=timeout1)except Exception as e:print(e)else:if respPdu is None:return Noneresp1 = respPdu.hex().upper()resp2 = ''for i in range(len(resp1)):if i != 0 and i % 2 == 0:resp2 += ' 'resp2 += resp1[i]print("診斷響應:%s" % resp2)return resp2

2.診斷業務函數方法

fun_can.py主要二次封裝UDS診斷函數,包括:27安全解鎖,34服務、36服務、診斷78響應處理、UDS診斷測試、CRC效驗等函數,源碼如下:

import binascii
import os
import subprocess
import timeimport pytestfrom config import Parameter
from public_method.base_can import CanBusclass CanMethod(CanBus):def __init__(self, config):self.interface = config['can']['interface']self.channel = config['can']['channel']self.bitrate = config['can']['bitrate']self.fd = config['can']['canfd']self.data_bitrate = config['can']['data_bitrate']self.addressingmode = config['can']['addressing_mode']self.tx = config['can']['physics_id_default']self.rx = config['can']['response_id_default']CanBus.__init__(self, interface=self.interface, channel=self.channel, bitrate=self.bitrate, fd=self.fd,data_bitrate=self.data_bitrate, )self.diag_congfig(addressingmode=self.addressingmode, tx=self.tx, rx=self.rx)self.sign_nrc78 = 0def __diag_get_seed(self, req_data="27 01"):"""27服務獲取種子并解析"""self.diag_request(req_data)try:uds_res_data = self.conn.specific_wait_frame(timeout=2)while uds_res_data[0] == 0x7F and uds_res_data[2] == 0x78:print("已收到 %d bytes : [%s]" % (len(uds_res_data), binascii.hexlify(uds_res_data)))uds_res_data = self.conn.specific_wait_frame(timeout=3)resp1 = uds_res_data.hex().upper()resp2 = ''for i in range(len(resp1)):if i != 0 and i % 2 == 0:resp2 += ' 'resp2 += resp1[i]print("診斷響應:%s" % resp2)except:print("響應數據失敗")else:seed = []res_seed = resp2.split(' ')[2:]for i in range(len(res_seed)):seed.append(eval('0x' + res_seed[i]))print("seed:%s" % seed)return seeddef get_key_level(self, seed):"""dll_security_unlock.exe工具解鎖語法:dll_security_unlock.exe --dll_path dome.dll --seed [11,22,33,44] --seedsize 4 --level 1 --keysize 4--dll_path DLL路徑--seed 請求種子--seedsize 種子長度--level 安全級別--keysize 秘鑰長度"""seed = str(seed).replace(' ', '')tool = os.path.join(os.path.dirname(__file__), 'dll_security_unlock.exe')cmd = '{} --dll_path {} --seed {}'.format(tool, os.path.join(os.path.dirname(os.path.dirname(__file__)), r'dll\dome.dll'), seed)key = subprocess.getoutput(cmd)return keydef unlock_level(self):"""27安全解鎖"""seed = self.__diag_get_seed(req_data="27 01")if seed is not None:if seed != 0 and len(seed) > 1:key = self.get_key_level(seed)print("key= %s" % key)req_data = "27 02 %s" % keyself.diag_request(req_data)self.uds_respond_0x78()time.sleep(0.1)else:print("seed響應不正確")def diag_send(self, req_data="3E 80"):"""發送診斷請求,不斷言診斷響應"""self.diag_request(req_data)response = self.uds_respond_0x78()time.sleep(0.1)return responsedef diag_send_exp(self, req_data="3E 80", exp=None):"""發送診斷請求,并斷言診斷響應"""self.diag_request(req_data)result = self.__diag_test_response_judge(exp=exp)time.sleep(0.1)return resultdef diag_send_0x34(self, req_data="34 00 44", address="00 00 00 00", size=0, exp=None):"""刷寫時使用,請求傳輸升級包"""print("傳輸包大小= %s" % size)self.diag_request(req_data + address + "{:08X}".format(size))self.__diag_test_response_judge(exp=exp)time.sleep(0.1)def diag_send_0x36(self, req_data="36", trans_size=255, path="", exp=None):"""36服務傳包"""total_size = os.path.getsize(path)print("size = %s" % total_size)with open(path, "rb") as f:file_read = f.read()print("CRC= %s" % "{:02X}".format(binascii.crc32(file_read)))file_byte = []for i in range(len(file_read)):file_byte.append("{:02X}".format(file_read[i]))sequence = 1transmitted_size = 0try:status = Truewhile status:trans_data = ""for i in range(trans_size):if transmitted_size < total_size:trans_data = trans_data + file_byte[transmitted_size]transmitted_size = transmitted_size + 1else:status = Falsebreakprint("data_num=%s" % transmitted_size)self.diag_request(request_command=req_data + "{:02X}".format(sequence) + trans_data,request_data_log_flag=False,)print(req_data + "{:02X}".format(sequence) + "...")self.__diag_test_response_judge(exp=exp)sequence += 1if sequence == 256:sequence = 0finally:print("36傳輸結束")def diag_crc32(self, req_data="31 01 02 02", path="", exp=None):"""刷寫時使用,CRC32校驗"""size = os.path.getsize(path)print("size = %s" % size)with open(path, "rb") as f:file_read = f.read()crc32 = "{:08X}".format(binascii.crc32(file_read))print("crc 32 = %s " % crc32)self.diag_send_exp(req_data=req_data + crc32, exp=exp)def __diag_session_mode(self, session):"""診斷會話模式"""if session == "01":self.diag_send(req_data="10 01")elif session == "03":self.diag_send(req_data="10 03")elif session == "02":self.diag_send(req_data="10 03")self.unlock_level()self.diag_send(req_data="10 02")def uds_respond_0x78(self, timeout1=2):"""78響應處理"""response = self.diag_respond(timeout1=timeout1)if response is not None:try:response2 = response.replace(' ', '')cyc = 0while response2[:2] == '7F' and response2[4:6] == '78':self.sign_nrc78 = 1response = self.diag_respond(timeout1=timeout1)if response is not None:response2 = response.replace(' ', '')cyc += 1if cyc > 20:breakexcept Exception as e:print("異常:%s" % e)return responsedef __diag_test_response_judge(self, exp=None):"""斷言響應結果與預期結果是否一致"""response = self.uds_respond_0x78()response_return = responseif (exp is not None) & (response is not None):exp = exp.replace(" ", "").upper()exp2 = ""for i in range(len(exp)):if i != 0 and i % 2 == 0:exp2 += " "exp2 += exp[i]exp = exp2if len(exp) < len(response):response = response[0: len(exp)]if response == exp:return response_returnelse:print("診斷結果與預期結果不匹配")pytest.fail("診斷結果與預期結果不匹配")def diag_test(self, session="01", req_data=None, exp=None):"""診斷測試:param session: 執行前會話模式,01默認會話,02編程會話,03擴展會話:param req_data:請求數據:param exp:預期結果:return:"""self.__diag_session_mode(session=session)if req_data is not None:self.diag_request(req_data)self.__diag_test_response_judge(exp=exp)uds = CanMethod(Parameter.config)

3.27服務安全解鎖

dll_security_unlock.exe文件可實現DLL安全解鎖,使用方法如下:
語法:
舉例:dll_security_unlock.exe --dll_path dome.dll --seed [11,22,33,44] --seedsize 4 --level 1 --keysize 4
–dll_path DLL路徑
–seed 請求種子
–seedsize 種子長度
–level 安全級別
–keysize 秘鑰長度

4.Bootloader刷寫

test_uds.py主要是自動化測試用例舉例,包括10服務測試、11服務測試、14服務測試、19服務測試、22服務測試、28服務測試、31服務測試、85服務測試等,源碼如下:

import os
import timeimport allurefrom public_method.fun_can import udsclass TestBootloader:@allure.title("Bootloader刷寫")def test_diag_Bootloader(self):print("#####Bootloader刷寫#####")path_driver=r'update\driver.bin'path_app=r"update\001_app.bin"uds.diag_send_exp(req_data='10 03', exp='50 03')time.sleep(0.2)uds.diag_send_exp(req_data='31 01 02 03', exp='71 01 02 03')time.sleep(0.2)uds.diag_send(req_data='85 82')time.sleep(1)uds.diag_send(req_data='28 81 01')time.sleep(1)uds.unlock_level()time.sleep(0.2)uds.diag_send_exp(req_data='10 02', exp='50 02')time.sleep(0.2)uds.diag_send_0x34(req_data='34 00 44 ', address='00 00 00 01 ', size=os.path.getsize(path_driver),exp='74')time.sleep(0.2)uds.diag_send_0x36(req_data='36', path=path_driver, trans_size=1024, exp='76')time.sleep(0.2)uds.diag_send_exp(req_data='37', exp='77')time.sleep(0.2)uds.diag_crc32(req_data='31 01 02 02 ', path=path_driver, exp='71 01 02 02')time.sleep(0.2)uds.diag_send_exp(req_data='31 01 ff 00 44 00 00 00 01 00 00 00 02', exp='71 01 ff 00')time.sleep(0.2)uds.diag_send_0x34(req_data='34 00 44 ', address='00 00 00 01 ', size=os.path.getsize(path_app), exp='74')time.sleep(0.2)uds.diag_send_0x36(req_data='36',path=path_app, trans_size=102400, exp='76')time.sleep(0.2)uds.diag_send_exp(req_data='37', exp='77')time.sleep(0.2)uds.diag_crc32(req_data='31 01 02 02 ', path=path_app, exp='71 01 02 02')time.sleep(1)uds.diag_send_exp(req_data='31 01 ff 01', exp='71 01 ff 01')time.sleep(0.2)uds.diag_send_exp(req_data='11 01', exp='51 01')print('重啟中,等待5分鐘..')time.sleep(300)uds.diag_send(req_data='10 83')time.sleep(1)uds.diag_send(req_data='28 80 01')time.sleep(1)uds.diag_send(req_data='85 81')

5.配置參數

config主要配置CAN和診斷相關的參數:
interface:配置can設備類型(支持python-can三方庫的設備)
channel:通道
bitrate:波特率
addressing_mode:數據比特率
physics_id_default:物理尋址
response_id_default:響應尋址

class Parameter():"""CAN參數配置"""config = {"can": {"interface": "vector","channel": 0,"bitrate": 500000,"data_bitrate": 2000000,"canfd": False,  # 是否canfd"addressing_mode": 0,"physics_id_default": 0x56A,"response_id_default": 0x56B,"function_id_default": 0x56C,}}

四、測試報告

### 2.測試報告

五、完整源碼鏈接

如下載源碼鏈接失效,請將購買專欄截圖和用戶名截圖通過CSDN私信發送給博主,博主更新源碼鏈接:
鏈接:https://pan.baidu.com/s/1EIx0upnVz-ZiudXE9Ki8Bg
提取碼:4kdj

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/15350.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/15350.shtml
英文地址,請注明出處:http://en.pswp.cn/web/15350.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

python 火焰檢測

在日常生活,總是離不開火,有時候我們需要預防火災發生,但是我們又不可能一直盯著,這時候我們就需要一款程序幫我們盯著,一旦發生火災從而告知我們,今天就帶大家編寫這么一款應用。 安裝需要的庫 pip install opencv-python 代碼實現 import cv2 # Library for…

qmt量化教程4----訂閱全推數據

文章鏈接 qmt量化教程4----訂閱全推數據 (qq.com) 上次寫了訂閱單股數據的教程 量化教程3---miniqmt當作第三方庫設置&#xff0c;提供源代碼 全推就主動推送&#xff0c;當行情有變化就會觸發回調函數&#xff0c;推送實時數據&#xff0c;可以理解為數據驅動類型&#xff0…

mysql中使用 mysqldump 實現跨機器備份|數據同步

1.如果同步數據庫&#xff0c;必須先創建數據庫&#xff1a; mysqldump -h 192.168.1.10 --lock-tablesfalse -uroot -proot db_name | mysql -h127.0.0.1 -uroot -proot db_name2.過濾掉不想要的表(沒試過&#xff0c;但是試過轉為sql文件的) mysqldump -h 192.168.1.10 --…

vs2019 c++ 函數的返回值是對象的值傳遞時候,將調用對象的移動構造函數

以前倒沒有注意過這個問題。但編譯器這么處理也符合移動構造的語義。因為本來函數體內的變量也要離開作用域被銷毀回收了。測試如下&#xff1a; 謝謝

實現信號發生控制

1. 信號發生器的基本原理 信號發生器是一種能夠產生特定波形和頻率的電子設備&#xff0c;常用于模擬信號的產生和測試。 信號發生器的基本原理 信號發生器的工作原理基于不同的技術&#xff0c;但最常見的類型包括模擬信號發生器和數字信號發生器&#xff08;DDS&#xff0…

[SCTF2019]babyre

打開看看還是有花指令 解除后首先pass1是解maze&#xff0c;好像又是三維的 x是25&#xff0c;也就是向下跳五層,注意是立體的 得到 passwd1&#xff1a; ddwwxxssxaxwwaasasyywwdd 接著往下看 有一個加密函數IDA逆向常用宏定義_lodword-CSDN博客 unsigned __int64 __fastca…

primeflex樣式庫筆記 Display相關的案例

回顧 寬度設置的基本總結 w-full&#xff1a;表示widtdh&#xff1a;100%&#xff1b;占滿父容器的寬度。 w-screen&#xff1a;表示占滿整個屏幕的寬度。 w-1到w-12&#xff0c;是按百分比劃分寬度&#xff0c;數字越大&#xff0c;占據的比例就越大。 w-1rem到w-30rem&…

Oracle的安裝以及一些相關問題

系列文章目錄 Oracle的安裝以及一些相關問題 文章目錄 系列文章目錄前言一、Oracle的安裝二、常用命令三、誤刪dbf四、PLSQL亂碼五、oracle更換數據庫字符集總結 前言 一段時間沒更新&#xff0c;主要最近一直在找工作&#xff0c;最終還是順著春招找到工作了&#xff0c;現在…

美信時代監控易:堆疊交換機的監控與配置管理策略

隨著企業數字化轉型的加速&#xff0c;網絡架構的複雜性日益提升&#xff0c;堆疊交換機作為高可靠性、靈活擴展性的解決方案&#xff0c;在網絡基礎設施中扮演著至關重要的角色。然而&#xff0c;如何確保堆疊交換機的穩定運行&#xff0c;實現高效監控與配置管理&#xff0c;…

剖析 OceanBase 應對高并發的技術策略

推薦一個AI網站&#xff0c;免費使用豆包AI模型&#xff0c;快去白嫖&#x1f449;海鯨AI 在當今互聯網時代&#xff0c;高并發場景下的數據庫處理能力成為了許多應用的關鍵需求。為了滿足用戶對快速響應和高吞吐量的期望&#xff0c;數據庫系統需要采用一系列技術來優化并發性…

七大經典排序算法——冒泡排序

文章目錄 &#x1f4d1;冒泡排序介紹&#x1f324;?代碼實現&#x1f324;?做個簡單的優化&#x1f324;?復雜度和穩定性分析??結語 &#x1f4d1;冒泡排序介紹 冒泡排序是一種簡單但效率較低的排序算法。它重復地比較相鄰的兩個元素&#xff0c;如果順序不對則交換它們&…

C++ socket epoll IO多路復用

IO多路復用通常用于處理單進程高并發&#xff0c;在Linux中&#xff0c;一切皆文件&#xff0c;一個socket連接會對應一個文件描述符&#xff0c;在監聽多個文件描述符的狀態應用中epoll相對于select和poll效率更高 epoll本質是系統在內核維護了一顆紅黑樹&#xff0c;監聽的文…

Linux中bash腳本怎么表示一個字符串變量

Linux中bash腳本怎么表示一個字符串變量 在Bash腳本中&#xff0c;你可以使用單引號&#xff08;&#xff09;或雙引號&#xff08;"&#xff09;來表示一個字符串變量。以下是兩種方式的示例&#xff1a; 使用單引號&#xff08;&#xff09;&#xff1a; my_variable…

flink 和 clipper搭配使用

Flink是一個用于流處理和批處理的開源框架&#xff0c;可以實時數據處理和分析。 Clipper 是一個用于機器學習模型服務化的開源框架&#xff0c;能夠輕松部署和管理機器學習模型&#xff0c;使模型可以通過統一的接口提供在線推理服務。 flink和clipper搭配使用&#xff1a; …

Leetcode | 5-21| 每日一題

2769. 找出最大的可達成數字 考點: 暴力 數學式子計算 思維 題解 通過式子推導: 第一想法是二分確定區間在區間內進行查找是否符合條件的, 本題最關鍵的便是 條件確定 , 第二種方法: 一般是通過數學公式推導的,這種題目我稱為數學式編程題 代碼 條件判斷式 class Solution { …

需求分析的任務

1 確定對系統的綜合要求 雖然功能需求是對軟件系統的一項基本需求&#xff0c;但卻并不是唯一的需求。通常對軟件系統有下述幾方面的綜合要求。 1&#xff0e;功能需求 這方面的需求指定系統必須提供的服務。通過需求分析應該劃分出系統必須完成的所有功能。 2&#xff0e;性能…

MacBook 怎么玩Windows游戲 蘋果筆記本怎么玩游戲?mac上如何玩windows游戲

傳統上&#xff0c;Mac 不被認為是好的游戲機。然而&#xff0c;蘋果已經開始在 Mac 上的游戲上投入更多精力&#xff0c;特別是自從轉向蘋果芯片以來。這使得 Mac 游戲的本機移植數量和模擬 Windows 游戲的能力都得到了顯著提高。 方法一&#xff1a;Boot Camp 1、Boot Camp是…

SpirngMVC框架學習筆記(一):SpringMVC基本介紹

1 SpringMVC 特點&概述 SpringMVC 從易用性&#xff0c;效率上 比曾經流行的 Struts2 更好 SpringMVC 是 WEB 層框架&#xff0c;接管了 Web 層組件, 比如控制器, 視圖, 視圖解析, 返回給用戶的數據格式, 同時支持 MVC 的開發模式/開發架構SpringMVC 通過注解&#xff0c;…

Java數據結構和算法(B樹)

前言 B樹又叫平衡的多路搜索樹&#xff1b;平衡的意思是又滿足平衡二叉樹的一些性質&#xff0c;左樹大于右樹&#xff1b; 多路意思是&#xff0c;可以多個結點&#xff0c;不再是像二叉樹只有兩個結點&#xff1b; 實現原理 B樹是一種自平衡的搜索樹&#xff0c;通常用于實…

MySQL和MongoDB數據庫的區別

MySQL和MongoDB數據庫的區別 隨著大數據和云計算技術的興起&#xff0c;數據庫的選擇成為開發者和架構師必須面對的重要決策。MySQL和MongoDB作為關系型數據庫和非關系型數據庫的代表&#xff0c;在各自領域都有著廣泛的應用。本文將從多方面詳細比較MySQL和MongoDB&#xff0…