如何用Java寫一個整理Java方法調用關系網絡的程序

????????大家好,我是猿碼叔叔,一位 Java 語言工作者,也是一位算法學習剛入門的小學生。很久沒有為大家帶來干貨了。

????????最近遇到了一個問題,大致是這樣的:如果給你一個 java 方法,如何找到有哪些菜單在使用。我的第一想法是,這不很簡單嗎?!使用 IDEA 自帶的右鍵 Find Usage 功能,一步一步往上溯源最終找到 Controller 中的方法,找到 requestMapping 的映射路徑,然后去數據庫一查便知。? ? ?

目錄

一、問題真的這么簡單嗎?我們先分析一下這種問題的出現場景

二、有沒有更快的解決方案?

三、如何提取 Java 對象中的方法以及方法中的被調用方法

? ? ? ? IO 流讀取 .java 文件

? ? ? ? JDK 編譯后的 .class 字節碼文件

四、解讀 -javap 命令反編譯后的內容

五、用代碼解析出類的方法與被調方法

六、讓方法與被調方法的關系可視化


一、問題真的這么簡單嗎?我們先分析一下這種問題的出現場景

????????我所在的這個項目是一個接近15年的老項目,使用的還是 SSM 框架。前后端沒有做到分離開來,耦合度極高。所以剛才那個問題的目的大致就是要將部分方法拆分出來,降低耦合度,使得后期的維護更加方便,亦或是擴展起來更加容易。?

? ? ? ? 這種項目模塊或方法之間耦合度高的問題,大多出現在老項目中。而仍然使用老項目的企業中國企居多。成本與安全也是阻礙老項目得到升級的兩大關鍵問題。隨著AI的興起,這種問題的徹底解決或許能夠看到一些希望,但是否有大模型專注于解決這種問題仍然需要考慮到成本和價值問題了。

二、有沒有更快的解決方案?

? ? ? ? 除了剛才使用 IDEA 的 Find?Usage 右鍵功能。我們或許可以調用 IDEA 的 API 也就是 Find Usage 功能,然后將項目中的所有方法串聯成一個 N 叉樹。對于?Controller 中的方法可以放在 Root 節點的下一層節點中。

? ? ? ? 但,IDEA 工具真的會給你提供這個 API 嗎?答案是否定的,至少我搜索了很多相關內容,也沒有得到一個準確的結果。或許有相關的開源組件提供這種方法溯源菜單的功能,但也都不盡如人意。

? ? ? ? 那我們能否自己寫一個這樣的程序呢?

三、如何提取 Java 對象中的方法以及方法中的被調用方法

? ? ? ? 這個程序實現起來其實很簡單。我們只需要使用 IO 流去讀取 .java 文件或者反射取出 class 中的 declaredMethods 即可。前者更開放,也更有挑戰性。后者除了能取到聲明方法以外,方法中的被調用方法反射做不到這一點。

? ? ? ? IO 流讀取 .java 文件

? ? ? ? IO 流我們使用 BufferedReader 一行一行地讀取 .java 文件中的內容,然后根據方法的特征解析出方法與被調用方法即可。聽起來是不是很簡單,怎么寫代碼?

? ? ? ? 考慮到 Java 中代碼的多變性,比如換行、注釋、內部類、靜態代碼塊、字段等等,這些都是需要我們用算法來處理的。但這么搞下去,真的可以自己寫一個 JDK 了。如果你肯堅持和足夠動腦,也不是不可能實現。

? ? ? ? JDK 編譯后的 .class 字節碼文件

? ? ? ? 如果你動手能力強,你會發現剛才說的一部分要處理的內容,jdk 可以幫你解決。比如注釋。在項目編譯后的 target 目錄下,原來的 .java 文件會被編譯成 .class 文件,這些文件中原有的注釋內容100%都不會被保留。此時,我們可以考慮去讀取 .class 文件來進一步實現我們的計劃。

? ? ? ? 當拿到 .class 文件的數據時,我傻眼了。讀取到的流數據并非我們眼睛看到的數據那樣,而是二進制的字節碼內容,要想解析這些數據,我們得學會解讀這些內容。當然現在有很多工具可以反編譯字節碼文件。為了不重復造輪子,我去網上找到了如下代碼,可以在 java 代碼中執行反編譯命令,將指定目錄下的 .class 文件反編譯成我們能讀懂的內容。

    private void decodeClassFile(File clazzFile) {String absPath = clazzFile.getAbsolutePath();try {String command = "javap -c " + absPath;Process process = runtime.exec(command);// 讀取命令執行結果BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));StringBuilder sb = new StringBuilder();String line;while ((line = reader.readLine()) != null) {sb.append(line).append("\n");}// 等待命令執行完成process.waitFor();// 獲取退出值process.exitValue();reader.close();absPath = absPath.substring(absPath.indexOf("cn"));absPath = absPath.replace('\\', '.');absPath = absPath.replace(".class", ".txt");// 將反編譯后的內容寫入到指定的文件內writeDecodedClazzFileDown(sb.toString(), absPath);} catch (IOException | InterruptedException e) {e.printStackTrace();}}

四、解讀 -javap 命令反編譯后的內容

? ? ? ? 打開反編譯后的文件,你會發現方法的內容區域會有數字序列號,這些序列號其實是執行順序的一個標識。序列號右側的 “//” 后面會跟隨描述當前行的內容類別。如果是方法,// 會寫著? “Method”,如果是接口方法則是“InterfaceMethod”,如果是字段定義則是“Field”。對于其他的描述,讀者有興趣也可以去研究一下。緊隨這些描述后的內容就是 java 源代碼中的真實內容了。

   46: invokevirtual #27                 // Method cn/com/xxx/xxx/pojo/dev/Admin.getUserList:()Ljava/util/List;97: invokeinterface #7,  3            // InterfaceMethod org/springframework/ui/Model.addAttribute:(Ljava/lang/String;Ljava/lang/Object;)Lorg/springframework/ui/Model;

? ? ? ? 編號46,是一個普通方法;編號97是一個接口方法。46 的 Method 后面我給他分為 3 個部分。

1、方法名與方法所在的包路徑。

2、() 中的內容代表參數信息。這些參數信息只包含包路徑與類型,沒有參數名稱。

3、括號后面的內容是返回值信息。

? ? ? ? 我們看到,參數內容與返回值區域路徑首字符多了一個 “L” 字符。這個代表對象類型區別于 Java 自己的 8 大基本類型。?這 8 個類型的指令如下:

dataType.put('[', "[]");  // 代表數組
dataType.put('I', "int");
dataType.put('J', "long");
dataType.put('F', "float");
dataType.put('D', "double");
dataType.put('Z', "boolean");
dataType.put('B', "byte");
dataType.put('C', "char");
dataType.put('S', "short");

? ? ? ? 對于其他更多的指令,讀者可以去咨詢 AI。比如文心一言或者GPT。

五、用代碼解析出類的方法與被調方法

? ? ? ? 解析方法與被調方法前我們需要注意幾點:

  • Service 方法與 ServiceImpl 實現類的方法轉換。在 Service 方法中的方法體是沒有內容的,其內容會在他的實現類對應的方法體里。這時候你應該知道我要強調的是什么了。
  • 方法與被調方法的參數信息在反編譯文件里的內容是不同的,比如 double 類型與 int 類型同時存在時,你看到的是 “DI”,如果前者是數組,你看到的是“[DI”。所以為了在拿到被調方法時能夠準確找到下一個節點,我們必須對這些內容進行還原
  • 如上一條所說,我們解析出方法與被調方法的目的是能夠準確的通過方法找到有哪些被調方法,拿到被調方法,能夠準確找到被調方法中的被調方法。這是一個 N-ary 樹的遍歷思想,直到找不到為止。
  • 當前類的自有方法互相調用,需要拼接其包路徑。這是為了統一管理。
  • 解析后的內容,在存放時應當有一個便于處理的格式。比如一級方法名前面沒有空格,而二級的被調方法則應當在前面加上四個“-”字符,這樣可以明確他們之間的關系。

? ? ? 下面是解析代碼:

public class ProjectMethodCallingTreeGraph {private final static char[] METHOD_PREFIX = {'M', 'e', 't', 'h', 'o', 'd'};private final static char[] INTERFACE_METHOD_PREFIX = {'I', 'n', 't', 'e', 'r', 'f'};private final static String TARGET_ROOT_PATH = "D:\\WORK\\xxx\\pro-info\\xxx\\xxx-graph";private static Map<Character, String> dataType = new HashMap<>();static {dataType.put('[', "[]");dataType.put('I', "int");dataType.put('J', "long");dataType.put('F', "float");dataType.put('D', "double");dataType.put('Z', "boolean");dataType.put('B', "byte");dataType.put('C', "char");dataType.put('S', "short");}public static void main(String[] args) {String path = "D:\\WORK\\xx\\pro-info\\xxx\\xxxx";ProjectMethodCallingTreeGraph p = new ProjectMethodCallingTreeGraph();p.readDecodedClazzFile(path);}private void readDecodedClazzFile(String path) {File file = new File(path);for (File f : file.listFiles()) {String method = null;String fName = f.getName().substring(0, f.getName().length() - 3);boolean mapperOrService = f.getName().endsWith("Service.txt") || f.getName().endsWith("Mapper.txt");LinkedHashSet<String> callMethods = new LinkedHashSet<>();try (BufferedReader br = new BufferedReader(new FileReader(f))) {String line;StringBuilder sb = new StringBuilder();while ((line = br.readLine()) != null) {char[] cs = line.toCharArray();String res = findMethodLine(cs, callMethods, mapperOrService);if (res != null) {if (method != null && method.length() > 2) {sb.append("----").append(fName).append(method).append("\n");append(sb, callMethods, fName);callMethods.clear();}method = res;}}writeDown(f.getName(), sb.toString());} catch (Exception e) {System.out.println(e.getMessage());}}}private void append(StringBuilder sb, LinkedHashSet<String> callMethods, String fName) {for (String m : callMethods) {sb.append("--------");if (localMethod(m)) {sb.append(fName);}sb.append(m).append("\n");}}private boolean localMethod(String str) {int n = str.length(), leftParenthesis = -1;for (int i = 0; i < n; ++i) {if (leftParenthesis == -1 && str.charAt(i) == '.') {return false;}if (leftParenthesis == -1 && str.charAt(i) == '(') {leftParenthesis = i;}}return true;}private void writeDown(String fname, String content) {try (BufferedWriter bw = new BufferedWriter(new FileWriter(TARGET_ROOT_PATH + "\\" + fname))) {bw.write(content);} catch (Exception e) {e.fillInStackTrace();}}private String findMethodLine(char[] cs, LinkedHashSet<String> calledMethods, boolean mapperOrService) {int x = 0, n = cs.length;while (x < n && cs[x] == ' ') {++x;}return x == 2 ? getSpecialCharIndex(cs, mapperOrService) : (x > 4 ? findCalledMethods(cs, x, calledMethods) : null);}private String findCalledMethods(char[] cs, int x, LinkedHashSet<String> calledMethods) {// interfaceMethodStringBuilder sb = new StringBuilder();int n = cs.length;boolean canAppend = false, inParenthesis = false, simpleDataTypePrior = false;String typeMask = "";for (; x < n; ++x) {if (cs[x] == '/' && cs[x - 1] == '/') {if (cs[x + 2] == 'M' && compare2Arrays(cs, x + 2, METHOD_PREFIX)) {x += 8;canAppend = true;} else if (cs[x + 2] == 'I' && compare2Arrays(cs, x + 2, INTERFACE_METHOD_PREFIX)) {canAppend = true;x += 17;} else {return null;}continue;}if (cs[x] == '[' || (x + 1 < n && cs[x + 1] == ')' && cs[x] == ';')) {continue;}if (canAppend && cs[x] != ':') {if (cs[x] == '/') {sb.append('.');} else if (cs[x] == ';') {sb.append(typeMask).append(", ");typeMask = "";simpleDataTypePrior = false;} else {if (inParenthesis && cs[x - 1] == '[') {typeMask = "[]";}if (cs[x] == 'L' && (cs[x - 1] == '(' || cs[x - 1] == '[' || cs[x - 1] == ';')) {continue;}if ((cs[x - 1] == '(' || cs[x - 1] == ';' || simpleDataTypePrior || cs[x - 1] == '[') && dataType.containsKey(cs[x])) {simpleDataTypePrior = true;sb.append(dataType.get(cs[x]));if (cs[x - 1] == '[') {sb.append("[]");}if ((x + 1 < n && cs[x + 1] != ')') || (x + 1 < n && cs[x + 1] == ';' && cs[x + 2] != ')')) {sb.append(", ");}} else {sb.append(cs[x]);}}if (cs[x] == '(') {inParenthesis = true;}}if (cs[x] == ')') {break;}}if (sb.length() > 0) {calledMethods.add(sb.toString());}return null;}private boolean compare2Arrays(char[] a, int x, char[] b) {return Arrays.equals(a, x, x + b.length, b, 0, b.length);}private String getSpecialCharIndex(char[] cs, boolean mapperOrService) {int pre = 0, cnt = 0, leftParenthesis = -1;StringBuilder sb = new StringBuilder();for (int i = 2; i < cs.length; ++i) {if (leftParenthesis != -1) {sb.append(cs[i]);}if (leftParenthesis == -1 && cs[i] == ' ') {pre = i;++cnt;}if (leftParenthesis == -1 && cs[i] == '(') {leftParenthesis = i;i = pre;}if (cs[i] == ')') {break;}}return cnt > 1 ? sb.toString() : null;}
}

配置好 .class 文件的路徑以及寫入的目標路徑后,執行代碼,等待幾秒后,就可以去看看寫入的方法與被調方法信息了。

六、讓方法與被調方法的關系可視化

? ? ? ? 為了更直觀的表達各方法之間的調用關系。我們可以為此創建一個 web 頁面,來展現這些方法與方法之間的調用關系。由于時間有限,目前只能向讀者提供方法與方法之間的調用關系,后續會豐富功能,并向大家展示。

  • web 頁面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>方法調用關系圖</title><style>html {background: orange;}.container {margin: 0;padding: 0;text-align: center;flex-direction: column;}.list-box {margin-top: 10px;height: 80px;overflow-y: hidden;border: solid 5px lightgray;overflow-x: scroll;}.list-item {list-style: none;padding: 2px 3px;}ul {display: flex;}li > button:hover {background: black;color: white;}button {background: white;color: rgba(0, 0, 0, 0.6);padding: 4px 6px;border: none;cursor: pointer;border-radius: 3px;}.clicked_current {color: white;background: black;}</style>
</head>
<body>
<div class="container">
</div>
</body>
<script type="text/javascript">const XMLRequest = new XMLHttpRequest();let level = 0;window.onload = function () {const url = "http://localhost/methodGraph";request(url, 'get', false);XMLRequest.onreadystatechange = function () {if (XMLRequest.readyState === XMLHttpRequest.DONE && (XMLRequest.status === 200 || XMLRequest.status === 304)) {renderElements(JSON.parse(XMLRequest.responseText));}}XMLRequest.send(null);}function request(url, method, async) {XMLRequest.open(method, url, async);XMLRequest.setRequestHeader('Content-Type', 'application/json');}let curLevel = -1;function renderElements(arr) {const parentDom = document.querySelector(".container");const frag = document.createDocumentFragment();for (const item of arr) {const li = document.createElement("li");li.classList.add("list-item");const btn = document.createElement("button");btn.onclick = function () {curLevel = parseInt(this.parentNode.parentNode.classList[0].substring(5));const docs = document.querySelectorAll(".clicked_current");for (const doc of docs) {doc.classList.remove("clicked_current");}btn.classList.add("clicked_current");search(item);}btn.title = item;btn.textContent = getNameFromLongString(item);li.appendChild(btn);frag.append(li);}if (curLevel === level - 1) {if (arr.length > 0) {const div = document.createElement("div");div.classList.add("list-box");const ul = document.createElement("ul");ul.classList.add(`level${level++}`)ul.appendChild(frag);div.appendChild(ul);parentDom.appendChild(div);}} else {let rem = curLevel + 2;if (arr.length > 0) {const ulExist = document.querySelector(`.level${curLevel + 1}`);ulExist.innerHTML = "";ulExist.appendChild(frag);rem++;}while (parentDom.childNodes.length > rem) {const last = parentDom.childNodes.length;parentDom.removeChild(parentDom.childNodes[last - 1]);level--;}}}function getNameFromLongString(longName) {if (level === 0) {return longName.substring(longName.lastIndexOf('.') + 1);}longName = longName.substring(0, longName.indexOf('('));return longName.substring(longName.lastIndexOf('.') + 1);}function search(name) {const url = `http://localhost/findByName?name=${name}`;request(url, 'get', false);XMLRequest.onreadystatechange = function () {if (XMLRequest.readyState === XMLHttpRequest.DONE && (XMLRequest.status === 200 || XMLRequest.status === 304)) {renderElements(JSON.parse(XMLRequest.responseText));}}XMLRequest.send(null);}
</script>
</html>
  • controller
    @RequestMapping("/findByName")@ResponseBodypublic List<String> findByName(@RequestParam(name = "name", defaultValue = "unknown") String name) {return projectInformationService.findByClazzName(name);}
  • 實現類
package com.example.develper.demos.service;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.*;@Service
public class ProjectInformationServiceImpl implements ProjectInformationService{Map<String, Map<String, List<String>>> name2Clazz;// 在 properties 中配置之前保存的那個目錄里@Value("${methodConnection.analyze.target.path}")private String methodConnectionPath;private void initialized() {if (name2Clazz != null) { return; }if (methodConnectionPath == null) {throw new IllegalArgumentException("請配置已經解析好的方法關系網絡文檔路徑!");}name2Clazz = new HashMap<>();File file = new File(methodConnectionPath);if (!file.exists()) {throw new IllegalArgumentException("請配置正確的文檔路徑!");}loadsClazzInfo(file);}private void loadsClazzInfo(File file) {File[] files = file.listFiles();for (File f : files) {String name = f.getName().substring(0, f.getName().length() - 4);Map<String, List<String>> method2CalledMethods = new HashMap<>();name2Clazz.put(name, method2CalledMethods);try (BufferedReader br = new BufferedReader(new FileReader(f))) {String line;String methodName = null;while ((line = br.readLine()) != null) {String[] ret = countPlaceholder(line);if ("4".equals(ret[0])) {methodName = ret[1];method2CalledMethods.put(methodName, new ArrayList<>());} else if (methodName != null) {method2CalledMethods.get(methodName).add(ret[1]);}}} catch (Exception e) {e.fillInStackTrace();}}}private String[] countPlaceholder(String line) {int x = 0, cnt = 0, n = line.length();StringBuilder sb = new StringBuilder();while (x < n) {if (line.charAt(x) == '-') {++cnt;} else {sb.append(line.charAt(x));}++x;}String[] ret = new String[2];ret[0] = Integer.toString(cnt);ret[1] = sb.toString();return ret;}@Overridepublic List<String> loadAllControllers() {initialized();List<String> ret = new ArrayList<>();for (String name : name2Clazz.keySet()) {if (name.endsWith("Controller")) {ret.add(name);}}return ret;}@Overridepublic List<String> findByClazzName(String name) {// cn.com.xx.xx.common.Base58.encode(if (name == null || name.trim().isEmpty()) {return new ArrayList<>();}if (name.endsWith("Controller")) {return new ArrayList<>(name2Clazz.getOrDefault(name, new HashMap<>()).keySet());}char[] cs = name.toCharArray();StringBuilder prefix = new StringBuilder();StringBuilder suffix = new StringBuilder();int i = 0;while (i < cs.length && cs[i] != '(') {if (cs[i] == '.') {prefix.append(prefix.length() > 0 ? '.' : "").append(suffix);suffix.setLength(0);} else {suffix.append(cs[i]);}++i;}while (i < cs.length) {suffix.append(cs[i++]);}char[] service = {'S', 'e', 'r', 'v', 'i', 'c', 'e'};int x = prefix.length() - 1, k = service.length - 1;while (k >= 0 && prefix.charAt(x) == service[k]) {--x; --k;}String clazzName = prefix.toString();if (k == -1) {x = prefix.length();while (prefix.charAt(x - 1) != '.') {--x;}prefix.insert(x, "impl.");clazzName = prefix.append("Impl").toString();name = prefix.append(".").append(suffix).toString();}Map<String, List<String>> clazz2Methods = name2Clazz.getOrDefault(clazzName, new HashMap<>());return clazz2Methods.getOrDefault(name, new ArrayList<>());}
}

七、結語

? ? ? ? 創作不易,期待讀者的支持。web 頁面效果不是很理想,后續會持續更新。畢竟這個功能給我平時的工作幫助挺大的。況且先前說的根據方法找菜單的功能并沒有完全實現,但就目前的方向來看,一定是正確的。

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

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

相關文章

線程中如何有效避免死鎖問題

1. 理解死鎖形成的原因 互斥條件&#xff1a;一個資源每次只能被一個線程使用。 請求與保持條件&#xff1a;線程因請求資源而阻塞時&#xff0c;對已獲得的資源保持不放。 不剝奪條件&#xff1a;線程已獲得的資源&#xff0c;在末使用完之前&#xff0c;不能強行剝奪。 循環…

c++ primer plus 第15章友,異常和其他:15.1.3 其他友元關系

c primer plus 第15章友&#xff0c;異常和其他&#xff1a;15.1.3 其他友元關系 提示&#xff1a;這里可以添加系列文章的所有文章的目錄&#xff0c;目錄需要自己手動添加 15.1.3 其他友元關系 提示&#xff1a;寫完文章后&#xff0c;目錄可以自動生成&#xff0c;如何生成可…

整潔架構SOLID-單一職責原則(SRP)

文章目錄 定義案例分析重復的假象代碼合并解決方案 小結 定義 SRP是SOLID五大設計原則中最容易被誤解的一個。也許是名字的原因&#xff0c;很多程序員根據SRP這個名字想當然地認為這個原則就是指&#xff1a;每個模塊都應該只做一件事。 在歷史上&#xff0c;我們曾經這樣描…

科研繪圖系列:R語言雙側條形圖(bar Plot)

介紹 雙側條形圖上的每個條形代表一個特定的細菌屬,條形的高度表示該屬的LDA得分的對數值,顏色用來區分不同的分類群或組別,它具有以下優點: 可視化差異:條形圖可以直觀地展示不同細菌屬在得分上的差異。強調重要性:較高的條形表示某些特征在區分不同組別中具有重要作用…

# Sharding-JDBC從入門到精通(6)-- Sharding-JDBC 水平分庫 和 垂直分庫。

Sharding-JDBC從入門到精通&#xff08;6&#xff09;-- Sharding-JDBC 水平分庫 和 垂直分庫。 一、Sharding-JDBC 水平分庫-分片策略配置 1、分庫策略定義方式如下 # 分庫策略&#xff0c;如何將一個邏輯表映射到多個數據源 spring.shardingsphere.sharding.tables.<邏…

第33集《大乘起信論》

《大乘起信論》和尚尼慈悲&#xff0c;諸位法師、諸位居士&#xff0c;阿彌陀佛&#xff01;&#xff08;阿彌陀佛&#xff01;&#xff09;請大家打開《講義》第七十四頁&#xff0c;子二、釋觀。 本論的特色&#xff0c;一言以蔽之就是文簡意賅、辭約理富&#xff0c;就是說…

VUE2拖拽組件:vue-draggable-resizable-gorkys

vue-draggable-resizable-gorkys組件基于vue-draggable-resizable進行二次開發, 用于可調整大小和可拖動元素的組件并支持沖突檢測、元素吸附、元素對齊、輔助線 安裝: npm install --save vue-draggable-resizable-gorkys 全局引用: import Vue from vue import vdr fro…

嵌入式linux面試1

1. linux 1.1. Window系統和Linux系統的區別 linux區分大小寫windows在dos&#xff08;磁盤操作系統&#xff09;界面命令下不區分大小寫&#xff1b; 1.2. 文件格式區分 windows用擴展名區分文件&#xff1b;如.exe代表執行文件&#xff0c;.txt代表文本文件&#xff0c;.…

運用Python與Keras框架打造深度學習圖像分類應用:詳盡步驟與代碼實例解析

引言 隨著深度學習技術的飛速發展&#xff0c;其在圖像識別和分類領域的應用日益廣泛。在這一背景下&#xff0c;Python因其豐富的數據科學庫和強大的生態系統而成為首選編程語言之一。在本文中&#xff0c;我們將深入探討如何使用Python和其中的Keras深度學習框架來完成一個實…

手動將dingtalk-sdk-java jar包打入maven本地倉庫

有時候,中央鏡像庫不一定有自己需要的jar包,這時候我們就需要用到該方法,將jar打入maven本地倉庫,然后項目中,正常使用maven的引入規則。 mvn install:install-file -Dmaven.repo.local=D:\software\maven\apache-maven-3.6.3-bin\apache-maven-3.6.3\repo -DgroupId=ding…

學習筆記——交通安全分析11

目錄 前言 當天學習筆記整理 4信控交叉口交通安全分析 結束語 前言 #隨著上一輪SPSS學習完成之后&#xff0c;本人又開始了新教材《交通安全分析》的學習 #整理過程不易&#xff0c;喜歡UP就點個免費的關注趴 #本期內容接上一期10筆記 #最近確實太懶了&#xff0c;接受…

跨越數據邊界:域適應在目標檢測中的革新作用

標題&#xff1a;跨越數據邊界&#xff1a;域適應在目標檢測中的革新作用 在機器學習和計算機視覺領域&#xff0c;尤其是目標檢測任務中&#xff0c;域適應&#xff08;Domain Adaptation&#xff09;是一種關鍵技術&#xff0c;它解決了模型在不同數據分布上的泛化問題。當訓…

C語言字節對齊技術在嵌入式、網絡與操作系統中的應用與優化

第一部分&#xff1a;嵌入式系統中的字節對齊 嵌入式系統通常對性能和資源有著嚴格的要求。在這些系統中&#xff0c;字節對齊的正確使用可以顯著提高數據訪問速度&#xff0c;減少內存占用&#xff0c;并提高系統的整體效率。 一、嵌入式系統中的字節對齊挑戰 嵌入式系統中…

Caffeinated for Mac v2.0.6 Mac防休眠應用 兼容 M1/M2/M3

Caffeinated 可以防止您的 Mac 進入休眠狀態、屏幕變暗或者啟動屏幕保護。 應用介紹 您的屏幕是否總是在您不希望的時候變暗&#xff1f;那么Caffeinated就是您解決這個大麻煩的最好工具啦。Caffeinated是在Caffeine這個非常便捷、有用的工具的基礎上開發而來的。Caffeinated…

215. 數組中的第K個最大元素(中等)

215. 數組中的第K個最大元素 1. 題目描述2.詳細題解3.代碼實現3.1 Python3.2 Java 1. 題目描述 題目中轉&#xff1a;215. 數組中的第K個最大元素 2.詳細題解 快速排序算法在每一輪排序中&#xff0c;隨機選擇一個數字 x x x&#xff0c;根據與 x x x的大小關系將要排序的數…

PMP–知識卡片--PDCA循環

記憶 PDCA&#xff1a;計劃執行檢查調整&#xff0c;計劃觀察動作&#xff1b;plan do check action 定義 PDCA循環的含義是將質量管理分為四個過程&#xff0c;即計劃&#xff08;Plan&#xff09;、執行&#xff08;Do&#xff09;、檢查&#xff08;Check&#xff09;、處…

C++開發調試工具:GDB調試,windebug調試,adb調試

我們在C開發過程中時常避免不了要調試追蹤&#xff0c;一下介紹最主流的三種調試工具&#xff1a; 一.GDB調試 1.coredump文件&#xff1a; coredump文件是程序異常時系統產生的錯誤日志文件&#xff0c;即核心轉儲文件&#xff1b; 編譯一個debug程序&#xff0c;必須是debu…

使用 OpenCV 和 Python 進行車道檢測和物體檢測(YOLO)

本項目旨在開發一個集車道檢測與物體檢測功能于一體的智能視覺分析系統&#xff0c;利用先進的計算機視覺技術和深度學習模型&#xff0c;實現實時的道路場景理解和目標識別。系統主要依托OpenCV這一強大的計算機視覺庫&#xff0c;以及Python作為編程語言&#xff0c;融合了車…

MySQL索引教程(01):創建索引

文章目錄 MySQL 創建索引索引介紹MySQL CREATE INDEX 語法MySQL 索引類型MySQL CREATE INDEX 實例結論 MySQL 創建索引 對于一個具有大量數據行的表&#xff0c;如果你根據某個查詢條件檢索數據時很慢&#xff0c;可能是因為你沒有在檢索條件相關的列上創建索引。 索引類似于…

FPC生產工藝全流程詳解

FPC生產制作繁瑣而且難度較大&#xff0c;與普通PCB比較&#xff0c;FPC單位面積電路的造價高很多&#xff0c;但是&#xff0c;由于FPC優異的柔性、輕薄和可靠性等特性&#xff0c;給眾多領域的設備和產品提供了更廣泛的實現空間和新的設計方案&#xff0c;比如沉金板在電子、…