Android NDK與JNI深度解析

核心概念定義:

  1. NDK (Native Development Kit):

    • 是什么: 一套由 Google 提供的工具集合。
    • 目的: 允許 Android 開發者使用 C 和 C++ 等原生(Native)語言來實現應用程序的部分功能。
    • 包含內容: 交叉編譯器(如 Clang/LLVM)、構建系統(CMake, ndk-build)、標準庫(如 libc++, OpenSSL)、調試工具(ndk-gdb, ndk-stack)、CPU 架構支持庫(ARM, x86, x86-64, MIPS)等。
    • 作用: 將 C/C++ 源代碼編譯、鏈接成 Android 設備上特定 CPU 架構(ARMv7, ARM64, x86, x86-64)可執行的動態鏈接庫(.so 文件)或靜態庫(.a 文件)。
  2. JNI (Java Native Interface):

    • 是什么: Java 平臺定義的一套編程接口規范。
    • 目的: 建立 Java 虛擬機(JVM,在 Android 中是 ART/Dalvik)與運行在同一個進程中的原生代碼(C/C++)之間的橋梁,實現雙向調用。
    • 核心機制: 定義了 Java 代碼如何調用原生函數(Native Methods),以及原生代碼如何訪問和操作 JVM 中的 Java 對象(創建、修改、調用方法、訪問字段)。
    • 關鍵組件: JNIEnv 指針(提供訪問 JVM 環境的函數表)、jclass, jobject, jstring, jint 等類型映射、方法簽名(用于唯一標識 Java 方法)、本地引用/全局引用(管理 Java 對象在原生代碼中的生命周期)。

NDK 和 JNI 的關系:

  • NDK 提供了實現 JNI 的環境和工具。 沒有 NDK,你無法輕松地將 C/C++ 代碼編譯成 Android 可用的庫。
  • JNI 定義了 Java 和 Native 代碼交互的規則。 即使你使用 NDK 編譯了原生代碼,也需要通過 JNI 接口才能在 Java/Kotlin 中調用它,并在原生代碼中操作 Java 對象。
  • 簡單說: NDK 是“工具包”和“編譯環境”,JNI 是“交互協議”和“編程接口”。兩者結合,才能實現 Java/Kotlin 與 C/C++ 在 Android 應用中的協同工作。

JNI 調用深度解析:

  1. Java/Kotlin -> Native 調用流程:

    1. 聲明 Native 方法: 在 Java/Kotlin 類中使用 native 關鍵字聲明方法(只有簽名,沒有實現)。
      public native String stringFromJNI();
      public native void processData(byte[] data, int width, int height);
      
    2. 加載 Native 庫: 在 Java/Kotlin 代碼中(通常在靜態初始化塊)使用 System.loadLibrary("library-name") 加載編譯好的 .so 文件。NDK 會根據約定(lib<name>.so)找到文件。
    3. 實現 Native 函數: 在 C/C++ 代碼中,按照 JNI 規范實現對應的函數。函數名必須遵循特定格式:Java_[包名_下劃線分隔]_[類名]_[方法名]
      #include 
      extern "C" JNIEXPORT jstring JNICALL
      Java_com_example_myapp_MainActivity_stringFromJNI(JNIEnv* env, jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());
      }
      
      • JNIEnv* env: 指向 JNI 函數表的指針,是幾乎所有 JNI 操作的入口點。
      • jobject this: 對于非靜態 native 方法,指向調用該方法的 Java 對象實例(類似于 Java 中的 this)。對于靜態 native 方法,這里是 jclass,指向聲明該方法的類。
      • 參數類型和返回值類型需要使用 JNI 類型(如 jstring, jint, jobject, jbyteArray)。
    4. 構建 Native 庫: 使用 NDK 的構建系統(CMake 是當前推薦)將 C/C++ 代碼編譯鏈接成 .so 文件。
    5. 運行: Java 代碼調用 native 方法,JVM 通過 JNI 接口找到并執行對應的原生函數實現。
  2. Native -> Java/Kotlin 調用流程:

    1. 獲取類引用: 在原生代碼中,使用 env->FindClass("java/lang/String")env->GetObjectClass(jobj) 獲取 jclass
    2. 獲取方法/字段 ID: 使用 env->GetMethodID(jclass, "methodName", "(Signature)ReturnType")env->GetFieldID(...)方法簽名是關鍵且易錯點!
      • 簽名示例:"(I)V" 表示 void method(int), "([B)Ljava/lang/String;" 表示 String method(byte[])
    3. 調用方法/訪問字段:
      • 調用實例方法:env->Call<Type>Method(jobject, methodID, args...) (如 CallVoidMethod, CallIntMethod, CallObjectMethod)。
      • 調用靜態方法:env->CallStatic<Type>Method(jclass, methodID, args...)
      • 獲取/設置實例字段:env->Get<Type>Field(jobject, fieldID), env->Set<Type>Field(jobject, fieldID, value)
      • 獲取/設置靜態字段:env->GetStatic<Type>Field(jclass, fieldID), env->SetStatic<Type>Field(jclass, fieldID, value)
    4. 處理異常: 原生代碼調用 Java 方法可能拋出異常。必須在原生代碼中檢查并處理異常(使用 env->ExceptionCheck()env->ExceptionOccurred()),否則可能導致 JVM 崩潰或未定義行為。處理完異常后通常需要清除(env->ExceptionClear())。
  3. 關鍵機制:

    • 類型映射: JNI 定義了基本類型(jint, jboolean, jdouble 等)和引用類型(jobject, jclass, jstring, jarray, jthrowable)與 Java 類型的對應關系。引用類型需要特殊處理。
    • 引用管理 (至關重要!):
      • 本地引用 (Local References): 由 JNI 函數返回的大部分對象引用(如 NewStringUTF, GetObjectArrayElement, CallObjectMethod)都是本地引用。它們在當前 native 方法執行期間有效,方法返回后會自動釋放。重要: 在長時間運行的原生循環或創建大量對象時,必須使用 env->DeleteLocalRef(localRef) 主動釋放,否則可能耗盡 JVM 的本地引用表,導致 Fatal Error。
      • 全局引用 (Global References): 使用 env->NewGlobalRef(localRef) 創建。它們一直有效,直到顯式調用 env->DeleteGlobalRef(globalRef) 釋放。用于緩存頻繁使用的類、方法 ID 或需要在多個 native 調用間保持活動的對象。
      • 弱全局引用 (Weak Global References): 使用 env->NewWeakGlobalRef(localRef) 創建。不會阻止垃圾回收器回收對象。使用前必須用 env->IsSameObject(weakRef, NULL)env->IsSameObject(weakRef, ...) 檢查對象是否已被回收。
    • 字符串處理: jstring 是 Java String 對象的引用。在原生代碼中使用 GetStringUTFChars 獲取指向 UTF-8 編碼 C 字符串的指針(只讀或可修改),使用完畢后必須調用 ReleaseStringUTFChars 釋放。NewStringUTF 可以從 C 字符串創建 jstring。避免頻繁轉換。
    • 數組處理: 對于原始類型數組(jintArray, jbyteArray),使用 Get<PrimitiveType>ArrayElements 獲取指向底層數組的指針(可能是拷貝或直接指針)。操作完成后必須調用 Release<PrimitiveType>ArrayElements 釋放。使用 GetArrayRegion/SetArrayRegion 可安全地復制部分數組數據,避免獲取整個數組指針的開銷。New<PrimitiveType>Array 創建新數組。
    • 線程: JNIEnv 指針 (env) 是線程相關的。不能將一個線程的 env 傳遞給另一個線程使用。在原生創建的線程(Attached Threads)中訪問 JNI,必須先調用 JNIEnv *env = (JNIEnv*)pthread_getspecific(jni_key); (如果已設置) 或通過 JavaVM* 指針調用 AttachCurrentThread(&env, NULL) 將線程附加到 JVM 來獲取 env。使用完畢后必須調用 DetachCurrentThread()

應用場景 (為什么使用 NDK/JNI):

  1. 性能關鍵型任務:
    • CPU 密集型計算: 數學運算、物理模擬、復雜算法(加密解密、圖像/視頻編碼解碼、信號處理)。C/C++ 通常比 Java/Kotlin 更快(尤其在利用 SIMD 指令時)。
    • 內存操作密集型任務: 需要精細控制內存布局和訪問模式的任務(如大型矩陣運算、自定義數據結構)。
  2. 重用現有 C/C++ 庫:
    • 跨平臺庫: OpenCV (計算機視覺), FFmpeg (音視頻處理), TensorFlow Lite (機器學習), SQLite (數據庫), Bullet Physics (物理引擎) 等。
    • 遺留代碼: 將公司或社區已有的成熟 C/C++ 代碼集成到 Android 應用中。
  3. 底層硬件訪問和控制:
    • 需要直接操作特定硬件特性或寄存器(雖然 Android 通常通過 HAL 和 Framework API 抽象硬件)。
    • 需要極低延遲的操作(如高精度音頻處理)。
  4. 平臺特定優化: 利用特定 CPU 架構(如 ARM NEON)的指令集進行高度優化。
  5. 安全性考慮 (謹慎使用): 將敏感算法或密鑰存儲在 native 代碼中,增加反編譯難度(但絕非絕對安全,native 代碼也能被逆向)。

優勢:

  • 性能: 對于計算密集型任務,C/C++ 通常能提供顯著的性能優勢。
  • 代碼復用: 重用龐大的、成熟的、跨平臺的 C/C++ 生態系統庫。
  • 硬件訪問: 提供更接近硬件的操作能力(需權限)。
  • 內存控制: 提供更精細的內存管理(但風險也更大)。

劣勢與挑戰:

  1. 復雜性陡增:
    • 構建系統: 需要管理 CMake/ndk-build、Native 依賴、ABI 過濾等,比純 Java/Kotlin 項目復雜得多。
    • JNI 編程模型: 類型轉換、引用管理、異常處理、字符串/數組操作、線程安全都需要開發者非常小心,極易出錯。
    • 調試困難: Native 崩潰日志(如 signal 11 (SIGSEGV))通常不如 Java 異常堆棧清晰。需要 ndk-stack 等工具解析,或使用 LLDB 進行原生調試,配置和使用比 Java 調試復雜。
  2. 開發效率降低: 編寫、調試和維護 JNI 膠水代碼(Glue Code)非常耗時,且容易引入 Bug。
  3. 內存安全風險: C/C++ 缺乏自動內存管理和邊界檢查,容易引發內存泄漏(Memory Leaks)、野指針(Dangling Pointers)、緩沖區溢出(Buffer Overflows)、段錯誤(Segmentation Faults)等嚴重問題,導致應用崩潰甚至安全漏洞。
  4. 跨平臺兼容性問題: 需要為不同的 CPU 架構(armeabi-v7a, arm64-v8a, x86, x86_64)編譯多個 .so 文件,增加 APK 大小。需要處理不同架構下的潛在差異(如字節序、對齊)。
  5. 啟動性能: 加載 .so 庫需要時間,可能影響應用啟動速度。
  6. 維護成本高: 需要同時具備 Java/Kotlin 和 C/C++ 開發能力的團隊,增加了知識要求和維護負擔。

性能考量 (并非萬能藥):

  • JNI 調用開銷: 每次 Java -> Native 或 Native -> Java 的調用本身就有一定的開銷(參數/返回值轉換、邊界檢查、可能的線程狀態切換)。避免在緊密循環中進行大量細粒度的 JNI 調用!
  • 數據傳輸開銷: 在 Java 堆和 Native 堆之間傳遞大量數據(如大數組、字符串)可能涉及復制操作,成本高昂。盡量在 Native 側處理完整的數據塊,減少跨邊界的數據傳遞次數和量。
  • 內存布局: 利用 C/C++ 對內存布局的精細控制(如結構體緊密排列、避免指針間接訪問)可以提升緩存友好性,這是 Java 對象難以企及的。
  • SIMD 指令: C/C++ 編譯器更容易生成或開發者更容易顯式使用 SIMD (如 NEON, SSE) 指令進行數據并行計算,大幅提升向量運算性能。
  • 權衡: 在決定使用 Native 之前,務必進行嚴格的性能分析和基準測試 (Benchmarking)。很多情況下,優化良好的 Java/Kotlin 代碼(特別是利用 ART 優化和現代 API)可能已經足夠快,且避免了 JNI 的復雜性和開銷。只有當 Native 帶來的性能提升顯著超過其引入的開銷和復雜性成本時,才應使用。

最佳實踐:

  1. 最小化 JNI 邊界: 設計時盡量讓跨 JNI 邊界的調用次數少、每次傳遞的數據量大。在 Native 側完成盡可能多的工作。
  2. 謹慎管理引用:
    • 嚴格釋放:NewGlobalRef, NewWeakGlobalRef, New<Type>Array, Get<Type>ArrayElements, GetStringUTFChars 等函數創建的非本地引用或獲取的資源,使用完畢后必須調用對應的 ReleaseDelete 函數。
    • 緩存 ID: 頻繁使用的 jclass, jmethodID, jfieldID 應該在初始化時(如 JNI_OnLoad)查找并緩存為全局引用(jclass)或直接緩存 ID(jmethodID/jfieldID 本身是普通值,不需要作為全局引用管理,但保存它們的 jclass 需要是全局引用)。
  3. 正確處理異常: 在 Native 代碼中調用 JNI 函數后,如果該函數可能拋出 Java 異常,必須檢查異常(env->ExceptionCheck())并妥善處理(清除、返回錯誤碼或拋出 Native 異常)。不要讓異常懸而未決。
  4. 高效處理字符串和數組:
    • 優先使用 GetStringRegion/GetStringUTFRegionGetArrayRegion/SetArrayRegion 進行部分復制,避免獲取整個數組指針。
    • 如果必須獲取指針,盡早 Release
    • 避免在 JNI 邊界頻繁傳遞和轉換字符串。
  5. 線程安全:
    • 不要在未附加的線程中使用 JNIEnv。使用 AttachCurrentThread/DetachCurrentThread
    • 注意全局共享數據的同步(使用 Mutex 等)。
  6. 健壯的錯誤處理: Native 代碼應有清晰的錯誤返回機制,并通過 JNI 將錯誤信息(或異常)傳遞回 Java 層。
  7. 使用現代構建系統 (CMake): 優先使用 CMake 而非已廢棄的 ndk-build (Android.mk/Application.mk)。CMake 更強大、更通用、與現代 IDE 集成更好。
  8. 利用 C++ 特性: 使用 RAII (Resource Acquisition Is Initialization) 模式管理資源(如使用 std::unique_ptr 配合自定義 Deleter 管理 JNI 本地引用或數組指針),利用 C++ 標準庫(std::vector, std::string)簡化開發,但要處理好與 JNI 類型的轉換。
  9. ABI 管理:build.gradle 中使用 ndk.abiFilters 明確指定需要支持的 ABI,避免打包不需要的庫增大 APK 體積。
  10. 詳盡日志: 在 Native 代碼中添加詳細的日志(使用 __android_log_print),方便調試。注意日志級別和性能。
  11. 內存分析: 使用 Address Sanitizer (ASan) 和 Valgrind (較舊) 等工具檢測 Native 內存錯誤。
  12. 安全編碼: 特別注意緩沖區溢出、格式化字符串漏洞等常見 C/C++ 安全問題。

現代發展趨勢與替代方案:

  1. Java (Kotlin) 性能提升: ART 運行時的持續優化(AOT, JIT, Profile-Guided Optimization - PGO)、硬件性能提升、更好的 Java/Kotlin API(如 java.util.concurrent, java.nio)使得許多以前需要 Native 的任務現在可以在 Managed 層高效完成。
  2. Renderscript 的棄用: Google 已棄用 Renderscript(一種用于并行計算的高級框架),開發者轉向 Vulkan(圖形計算)或直接使用 NDK 進行高性能計算。
  3. Vulkan: 用于高性能 3D 圖形和并行計算的現代跨平臺 API。通過 NDK 提供。在圖形和計算密集型任務上是 OpenGL ES 的強大替代品。
  4. Android Jetpack 組件:
    • CameraX: 簡化相機開發,底層可能使用 Native,但暴露的是 Java/Kotlin API。
    • Media3/ExoPlayer: 強大的媒體播放庫,內部使用 Native 編解碼器,但提供 Java/Kotlin API。
    • TensorFlow Lite: 雖然核心是 C++,但提供了易于使用的 Java/Kotlin API。
  5. 機器學習: ML Kit 和 TensorFlow Lite 提供了高層級的 Java/Kotlin API,隱藏了底層 Native 實現的復雜性。
  6. 跨平臺框架: Flutter (Dart), React Native (JavaScript), Kotlin Multiplatform Mobile (KMM) 等試圖提供跨平臺解決方案,它們內部可能使用 Native,但開發者主要使用高級語言。KMM 特別允許在 Android 和 iOS 間共享 Kotlin 業務邏輯(包括可能調用平臺特定的 Native 代碼)。
  7. WebAssembly (Wasm): 一種新興的二進制指令格式,有望在未來提供一種更安全、更跨平臺的 Native 代碼執行方式(通過瀏覽器引擎或獨立運行時),但目前(Android API 33+ 支持有限)在 Android NDK 中的集成度和成熟度還遠不如直接使用 JNI。

結論:

Android NDK 和 JNI 是連接 Java/Kotlin 世界與 C/C++ 原生世界的強大但復雜的橋梁。它們對于性能極致要求、重用龐大 C/C++ 生態、底層硬件交互等場景是不可或缺的工具。然而,其引入的開發復雜性、調試難度、內存安全風險和維護成本是巨大的。

決策建議:

  1. 優先考慮純 Java/Kotlin 解決方案。 現代 Android 運行時的性能已經非常優秀。
  2. 嚴格評估性能需求。 只有在經過充分 Profiling 證明 Managed 層確實是瓶頸,且預計 Native 能帶來顯著收益時,才考慮使用。
  3. 優先尋找封裝好的 Java/Kotlin 庫或 Jetpack 組件。 許多底層使用 Native 的高性能庫(如 CameraX, Media3, TFLite)已經提供了優秀的 Java/Kotlin API,無需直接面對 JNI。
  4. 如果必須使用 NDK/JNI:
    • 務必深刻理解 JNI 規范,特別是引用管理、異常處理和線程安全。
    • 遵循最佳實踐,最小化 JNI 邊界,謹慎管理資源。
    • 使用現代工具鏈(CMake)和調試工具(LLDB, ASan)。
    • 進行嚴格的測試(功能、性能、穩定性、內存泄漏、多線程)和代碼審查。
    • 清晰隔離 Native 模塊,設計好與 Java 層的接口。

NDK/JNI 是一把雙刃劍。用得好,可以解鎖 Android 應用的性能極限和復用強大生態;用不好,則會引入無盡的崩潰、內存問題和維護噩夢。務必謹慎評估,理性選擇。

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

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

相關文章

Golang各版本特性

1. Go各版本特性 | FeelingLife 2. https://chatgpt.com/share/68808f58-ae5c-800a-8153-5358098f301b 3.https://tonybai.com/2024/11/14/go-map-use-swiss-table/

HTML 轉 Word API 接口

HTML 轉 Word API 接口 支持網頁轉 Word&#xff0c;高效轉換為 Word&#xff0c;提供永久鏈接。 1. 產品功能 超高性能轉換效率&#xff1b;支持將傳遞的 HTML 轉換為 Word&#xff0c;支持 HTML 中的 CSS 格式在 Word 文檔中的呈現&#xff1b;支持傳遞網站的 URL&#xff…

Lucid Search: 極簡、隱私友好的問答式搜索引擎技術解析

Lucid Search: 極簡、隱私友好的問答式搜索引擎技術解析 產品定位與價值主張 Lucid Search 是一款革命性的問答式搜索引擎&#xff0c;其核心價值在于&#xff1a; 極簡體驗&#xff1a;無賬戶、無廣告、前端完全靜態隱私保護&#xff1a;不寫入 Cookie、不記錄 IP、無追蹤即…

卷積神經網絡:模型評估標準

一、分類模型評價指標在模型評估中&#xff0c;有多個標準用于衡量模型的性能&#xff0c;這些標準包括準確率&#xff08;Accuracy&#xff09;、精確率&#xff08;Precision&#xff09;、召回率&#xff08;Recall&#xff09;、F1 分數&#xff08;F1-Score&#xff09;等…

【前端工程化】前端開發中想做好發布管理可以從哪些方面著手?

在企業級后臺系統中&#xff0c;發布管理是整個開發流程的最終環節&#xff0c;也是最為關鍵的一環。它不僅涉及代碼構建完成后的部署操作&#xff0c;還包括版本控制、灰度發布、回滾機制等保障系統穩定性的措施。 本文主要圍繞發布流程設計、版本控制、部署方式、灰度策略和回…

替分布式=成本下降50% !

在數字化轉型的浪潮中&#xff0c;數據庫作為醫療信息系統的“心臟”&#xff0c;其穩定性與效率直接關乎醫療服務的質量。2024年10月30日&#xff0c;綿陽市第三人民醫院集成平臺的CDR數據庫成功從分布式數據庫Citus切換為國產集中式數據庫KingbaseES&#xff0c;并穩定運行至…

【Linux系統編程】基礎指令

基礎指令1. adduser指令&&passwd指令2. userdel指令3. pwd指令4. ls指令5. cd指令6. tree指令7. touch指令8. mkdir指令9. rmdir指令&&rm指令10. man指令11. cp指令12. mv指令13. cat指令14. more指令15. less指令16. head指令17. tail指令18. date指令19. cal…

區塊鏈之以太坊Hardhat開發框架——部署在windows為例

Hardhat 提供了一個靈活且易于使用的開發環境&#xff0c;可以輕松地編寫、測試和部署智能合約。Hardhat還內置了Hardhat 網絡&#xff08;Hardhat Node&#xff09;&#xff0c;它是為開發而設計的本地以太坊網絡。 下面是hardhat的官方文檔 https://hardhat.org/hardhat-ru…

Ubuntu 1804 編譯ffmpeg qsv MediaSDK libva 遇到的問題記錄

之前都是 用的xeon服務器的cpu 不支持intel QSV 硬件加速 最近把自己的 14年買的pc機裝上了ubuntu 1804 然后準備開啟ffmpeg qsv 硬件加速功能 CPU i3-4170 內存DDR3 16G 硬盤機械盤500G 主板ASUS B85M-G首先安裝vainfo工具apt install vainfo裝完提示如下出錯了 網上說是…

Elasticsearch(ES)介紹和安裝

目錄 一、Elasticsearch(ES)介紹 1.為什么需要單獨的搜索服務 2.全文檢索 3.Elasticsearch簡介 1.Elasticsearch的特點 2.應用場景 3.ElasticSearch數據的存儲和搜索原理 二、Elasticsearch(ES)安裝 1、拉取鏡像 2、創建目錄并給目錄賦權 3、創建并編輯配置文件 4、…

html結構解析

<!DOCTYPE html>&#xff1a;聲明為 HTML5 文檔 <html lang"zh-CN">&#xff1a;根元素&#xff0c;指定頁面語言為中文 <meta charset"UTF-8">&#xff1a;設置字符編碼&#xff0c;確保中文正常顯示 <meta name"viewport"…

面試150 最大子數組和

思路 貪心法&#xff1a;設定最小標志result為float(‘-inf’),遍歷一次數組元素進行求和&#xff0c;如果當前元素大于result&#xff0c;則更新result的值&#xff0c;如果sum小于0&#xff0c;則重新置0進行計算&#xff0c;最后返回result class Solution:def maxSubArray(…

MyBatis動態SQL實戰:告別硬編碼,擁抱智能SQL生成

MyBatis動態SQL實戰&#xff1a;告別硬編碼&#xff0c;擁抱智能SQL生成在電商平臺的用戶管理模塊中&#xff0c;需要面對多種不同的用戶查詢組合條件。當使用傳統的硬編碼SQL方式時&#xff0c;代碼膨脹到了2000多行&#xff0c;維護成本極高。而引入MyBatis動態SQL后&#xf…

Web前端開發:JavaScript遍歷方法詳解與對比

1. 傳統 for 循環const arr [10, 20, 30]; for (let i 0; i < arr.length; i) {console.log(索引 ${i}: 值 ${arr[i]}); } // 輸出&#xff1a; // 索引 0: 值 10 // 索引 1: 值 20 // 索引 2: 值 30特點&#xff1a;最基礎的循環&#xff0c;可通過索引精準控制適用場景&…

Python 爬蟲(一):爬蟲偽裝

目錄 1 簡介2 偽裝策略 2.1 Request Headers 問題2.2 IP 限制問題 3 總結 1 簡介 對于一些有一定規模或盈利性質比較強的網站&#xff0c;幾乎都會做一些防爬措施&#xff0c;防爬措施一般來說有兩種&#xff1a;一種是做身份驗證&#xff0c;直接把蟲子擋在了門口&#xff…

TODAY()-WEEKDAY(TODAY(),2)+1

這個Excel公式 TODAY()-WEEKDAY(TODAY(),2)1 用于計算 當前周的周一日期。下面詳細解釋它的邏輯和用法&#xff1a;公式解析TODAY()返回當前日期&#xff08;例如今天是2023年12月20日&#xff0c;則 TODAY() 2023/12/20&#xff09;。WEEKDAY(TODAY(), 2)計算當前日期是星期幾…

Fast Frequency Estimation Algorithm by Least Squares Phase Unwrapping

I. 引言 單個含噪正弦信號的頻率估計是一個研究已久的問題&#xff0c;并有多種應用[1]。在高斯白噪聲假設下&#xff0c;最大似然(ML)頻率估計器是Rife和Boorstyn [2]中提出的周期圖估計器&#xff0c;其中傅里葉變換用于搜索周期圖的最大值。周期圖估計器被廣泛認為是單頻估計…

C語言常見的預定符號常量

C語言常見的預定符號常量C 語言提供了豐富的預定義符號常量&#xff0c;分布在不同頭文件中&#xff0c;用于獲取編譯信息、數值范圍、浮點特性等關鍵信息。以下是常見預定義符號常量的分類總結&#xff1a;一、預定義宏&#xff08;編譯時信息&#xff09;由編譯器自動定義&am…

【2025】使用vue構建一個漂亮的天氣卡片

1. 核心框架&#xff1a;Vue Vue 以其輕量、易用、響應式數據綁定的特點&#xff0c;非常適合快速構建這類小型界面組件。即使是直接通過 CDN 引入&#xff0c;也能高效開發&#xff0c;降低項目復雜度&#xff0c;無需搭建完整工程化環境 。 2. 網絡請求&#xff1a;Axios 用于…

Ruby 命令行選項詳解

Ruby 命令行選項詳解 引言 Ruby 是一種廣泛使用的編程語言,它以其簡潔、優雅和強大的功能而聞名。在 Ruby 的使用過程中,命令行界面(CLI)提供了豐富的選項,可以幫助開發者更高效地與 Ruby 環境交互。本文將詳細解析 Ruby 命令行選項,旨在幫助開發者更好地利用這些工具。…