Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >CMAKE入门实战

CMAKE入门实战

作者头像
公众号guangcity
发布于 2019-09-20 09:39:09
发布于 2019-09-20 09:39:09
1.6K00
代码可运行
举报
文章被收录于专栏:光城(guangcity)光城(guangcity)
运行总次数:0
代码可运行

0.导语

最近做的项目使用CLION构建,而这个采用CMakeLists.txt管理,因此为了更好的学习,故找到了一篇大牛级别的入门文章,有文章有代码,本文是花了一点时间把这篇文章学习后的重要点记录吧,原作者github地址:https://github.com/wzpan/cmake-demo。

1.单个源文件

CMakeLists.txt 的语法比较简单,由命令、注释和空格组成,其中命令是不区分大小写的。符号 # 后面的内容被认为是注释。命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔。

首先创建一个main.cpp

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

编写第一个CMakeLists.txt:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
cmake_minimum_required(VERSION 3.10)
project(cmakeLearn)
add_executable(main main.cpp)
  1. cmake_minimum_required:指定运行此配置文件所需的 CMake 的最低版本;
  2. project:参数值是 cmakeLearn,该命令表示项目的名称是 cmakeLearn
  3. add_executable:将名为main.cpp的源文件编译成一个名称为 cmakeLearn 的可执行文件。

现在查看一下路径:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
light@city:~/CLionProjects/cmakeLearn$ ls
CMakeLists.txt  main.cpp

输入下列命令进行cmake

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
cmake .

cmake过程

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
light@city:~/CLionProjects/cmakeLearn$ cmake .
-- The C compiler identification is GNU 5.5.0
-- The CXX compiler identification is GNU 5.5.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/light/CLionProjects/cmakeLearn

cmake 后 生成了Makefile

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
light@city:~/CLionProjects/cmakeLearn$ ls
CMakeCache.txt  cmake_install.cmake  main.cpp
CMakeFiles      CMakeLists.txt       Makefile

然后执行make

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
light@city:~/CLionProjects/cmakeLearn$ make
Scanning dependencies of target main
[ 50%] Building CXX object CMakeFiles/main.dir/main.cpp.o
[100%] Linking CXX executable main
[100%] Built target main

执行

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
light@city:~/CLionProjects/cmakeLearn$ ./main
Hello, World!

2.多个源文件

2.1 同一目录,多个源文件

在1中的cmake添加下面这行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)

当本地源文件很多,如果将源文件都加到里面就很烦,所以这里采用aux_source_directory。CMake 会将当前目录所有源文件的文件名赋值给变量 DIR_SRCS ,再指示变量 DIR_SRCS 中的源文件需要编译成一个名称为 Demo 的可执行文件。

这样就避免出现下面这种情况:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 指定生成目标
add_executable(Demo main.cc MathFunctions.cc xx.cc ....cc)

小结:

  • aux_source_directory(. DIR_SRCS)

查找当前目录下的所有源文件,并将名称保存到 DIR_SRCS 变量。

2.2 多个目录,多个源文件

此时目录架构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
./Demo3
    |
    +--- main.cc
    |
    +--- math/
          |
          +--- MathFunctions.cc
          |
          +--- MathFunctions.h

对于这种情况,需要分别在项目根目录 Demo3 和 math 目录里各编写一个 CMakeLists.txt 文件。

为了方便,我们可以先将 math 目录里的文件编译成静态库再由 main 函数调用

这里与上述2.1CMakeLists不同之处是在上面基础上加上了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 添加 math 子目录
add_subdirectory(math)
# 添加链接库
target_link_libraries(Demo MathFunctions)

以此完成:

  • 子目录添加
  • 链接库添加

最后,在子目录下指定链接库名字:

子目录中的 CMakeLists.txt:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)
# 生成链接库
add_library (MathFunctions ${DIR_LIB_SRCS})

小结:

  • add_library(MathFunctions ${DIR_LIB_SRCS})

将 src 目录中的源文件编译为静态链接库。

3.自定义编译选项

CMake 允许为项目增加编译选项,从而可以根据用户的环境和需求选择最合适的编译方案。

例如,可以将 MathFunctions 库设为一个可选的库,如果该选项为 ON ,就使用该库定义的数学函数来进行运算。否则就调用标准库中的数学函数库。

本节CMake与2不同如下三块:

(1)加入一个配置头文件,用于处理 CMake 对源码的设置

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 加入一个配置头文件,用于处理 CMake 对源码的设置
configure_file (
  "${PROJECT_SOURCE_DIR}/config.h.in"
  "${PROJECT_BINARY_DIR}/config.h"
  )

configure_file 命令用于加入一个配置头文件 config.h ,这个文件由 CMake 从 config.h.in生成,通过这样的机制,将可以通过预定义一些参数和变量来控制代码的生成。

例如:修改main.cc:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include "config.h"
...
...
...
#ifdef USE_MYMATH
  #include "math/MathFunctions.h"
#else
  #include
#endif

根据 USE_MYMATH 的预定义值来决定是否调用标准库还是 MathFunctions 库。

上修改main.cc中引用了一个 config.h 文件,这个文件预定义了 USE_MYMATH 的值。但我们并不直接编写这个文件,为了方便从 CMakeLists.txt 中导入配置,我们编写一个 config.h.in 文件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#cmakedefine USE_MYMATH

这样 CMake 会自动根据 CMakeLists 配置文件中的设置自动生成 config.h 文件。

这里使用了ccmake进行可视化编译选择,Ubuntu上安装:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sudo apt-get install cmake-curses-gui

运行ccmake .后:

hjkl控制方向移动,enter 键可以修改该选项。修改完成后可以按下 c 选项完成配置,之后再按 g 键确认生成 Makefile 。ccmake 的其他操作可以参考窗口下方给出的指令提示。

下面来看一下config.h文件内容:

USE_MYMATH 为 ON
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define USE_MYMATH
USE_MYMATH 为 OFF
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/* #undef USE_MYMATH */

(2)是否加入 MathFunctions 库

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 是否加入 MathFunctions 库
if (USE_MYMATH)
  include_directories ("${PROJECT_SOURCE_DIR}/math")
  add_subdirectory (math)  
  set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)

option 命令添加了一个 USE_MYMATH 选项,并且默认值为 ON

(3)是否使用自己的 MathFunctions 库

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 是否使用自己的 MathFunctions 库
option (USE_MYMATH
       "Use provided math implementation" ON)

根据 USE_MYMATH 变量的值来决定是否使用我们自己编写的 MathFunctions 库。

4.安装和测试

4.1 安装

之前在编译一些源代码程序的时候,先make后make install,这样会把一些头文件与静态/动态库安装到指定的目录下。

那在CMAKE中同样可以这么做,如下:

首先先在 math/CMakeLists.txt 文件里添加下面两行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 指定 MathFunctions 库的安装路径
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)

指明 MathFunctions 库的安装路径。之后同样修改根目录的 CMakeLists 文件,在末尾添加下面几行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 指定安装路径
install (TARGETS Demo DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/config.h"
         DESTINATION include)

通过上面定制,后安装如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
light@city:~/cmake-demo/Demo5$ sudo make install
[ 50%] Built target MathFunctions
[100%] Built target Demo
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/bin/Demo
-- Installing: /usr/local/include/config.h
-- Installing: /usr/local/lib/libMathFunctions.a
-- Installing: /usr/local/include/MathFunctions.h

据此完成了安装工作。

4.2 测试

CMake 提供了一个称为 CTest 的测试工具。我们要做的只是在项目根目录的 CMakeLists 文件中调用一系列的 add_test 命令。

启动测试:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 启用测试
enable_testing()

(1)无帮助信息测试

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 测试程序是否成功运行
add_test (test_run Demo 5 2)

(2)有帮助信息测试

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 测试 210 次方
add_test (test_2_10 Demo 2 10)
set_tests_properties (test_2_10)
 PROPERTIES PASS_REGULAR_EXPRESSION "is 1024")

其中 PASS_REGULAR_EXPRESSION 用来测试输出是否包含后面跟着的字符串。

5.支持gdb

让 CMake 支持 gdb 的设置也很容易,只需要指定 Debug 模式下开启 -g 选项:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

6.添加版本号

在编写项目中给项目添加版本号:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
set (Demo_VERSION_MAJOR 1)
set (Demo_VERSION_MINOR 0)

分别指定当前的项目的主版本号和副版本号。

如果想在代码中获取信息,可以修改config.h.in文件,添加两个预定义变量:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// the configured options and settings for Tutorial
#define Demo_VERSION_MAJOR @Demo_VERSION_MAJOR@
#define Demo_VERSION_MINOR @Demo_VERSION_MINOR@

主程序调用,打印:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
printf("%s Version %d.%d\n",
        argv[0],
        Demo_VERSION_MAJOR,
        Demo_VERSION_MINOR);

7.生成安装包

如何配置生成各种平台上的安装包,包括二进制安装包和源码安装包。为了完成这个任务,我们需要用到 CPack ,它同样也是由 CMake 提供的一个工具,专门用于打包。

我们做如下三个工作:

  1. 导入 InstallRequiredSystemLibraries 模块,以便之后导入 CPack 模块;
  2. 设置一些 CPack 相关变量,包括版权信息和版本信息,其中版本信息用了上一节定义的版本号;
  3. 导入 CPack 模块。

对应CMakeLists.txt如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 构建一个 CPack 安装包
include (InstallRequiredSystemLibraries)
set (CPACK_RESOURCE_FILE_LICENSE
  "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set (CPACK_PACKAGE_VERSION_MAJOR "${Demo_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${Demo_VERSION_MINOR}")
include (CPack)

下面就是如何使用:

输入cpack .,也可以指定二进制与源码安装包:

  • 生成二进制安装包:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
cpack -C CPackConfig.cmake
  • 生成源码安装包
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
cpack -C CPackSourceConfig.cmake

cpack安装:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
light@city:~/cmake-demo/Demo8$ cpack -CPackConfig.cmake
CPack: Create package using STGZ
CPack: Install projects
CPack: - Run preinstall target for: Demo8
CPack: - Install project: Demo8
CPack: Create package
CPack: - package: /home/light/cmake-demo/Demo8/Demo8-1.0.1-Linux.sh generated.
CPack: Create package using TGZ
CPack: Install projects
CPack: - Run preinstall target for: Demo8
CPack: - Install project: Demo8
CPack: Create package
CPack: - package: /home/light/cmake-demo/Demo8/Demo8-1.0.1-Linux.tar.gz generated.
CPack: Create package using TZ
CPack: Install projects
CPack: - Run preinstall target for: Demo8
CPack: - Install project: Demo8
CPack: Create package
CPack: - package: /home/light/cmake-demo/Demo8/Demo8-1.0.1-Linux.tar.Z generated.

此时会在本地目录下创建3个不同格式的二进制包文件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
light@city:~/cmake-demo/Demo8$ ls Demo8*
Demo8-1.0.1-Linux.sh  Demo8-1.0.1-Linux.tar.gz  Demo8-1.0.1-Linux.tar.Z

随便选择一个安装,例如sh:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sh Demo8-1.0.1-Linux.sh

使用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
light@city:~/cmake-demo/Demo8$ ./Demo8-1.0.1-Linux/bin/Demo 5 2
Now we use our own Math library.
5 ^ 2 is 25
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 光城 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
ConcurrentHashMap1.8 – 扩容详解「建议收藏」
ConcurrenHashMap 在扩容过程中主要使用 sizeCtl 和 transferIndex 这两个属性来协调多线程之间的并发操作,并且在扩容过程中大部分数据依旧可以做到访问不阻塞,具体是如何实现的,请继续 。
全栈程序员站长
2022/09/12
8390
ConcurrentHashMap1.8 – 扩容详解「建议收藏」
【原创】Java并发编程系列27 | ConcurrentHashMap(下)
上一篇详细分析了HashMap源码,介绍了HashMap的数据结构以及并发编程中HashMap的问题,这篇就来看下ConcurrentHashMap。因为ConcurrentHashMap与HashMap结构是一样的,本文将重点介绍ConcurrentHashMap在并发编程中如何保证线程安全:
java进阶架构师
2020/07/02
5260
【原创】Java并发编程系列27 | ConcurrentHashMap(下)
【死磕Java并发】-----J.U.C之Java并发容器:ConcurrentHashMap
此篇博客所有源码均来自JDK 1.8 HashMap是我们用得非常频繁的一个集合,但是由于它是非线程安全的,在多线程环境下,put操作是有可能产生死循环的,导致CPU利用率接近100%。为了解决该问题,提供了Hashtable和Collections.synchronizedMap(hashMap)两种解决方案,但是这两种方案都是对读写加锁,独占式,一个线程在读时其他线程必须等待,吞吐量较低,性能较为低下。故而Doug Lea大神给我们提供了高性能的线程安全HashMap:ConcurrentHashMap
用户1655470
2018/04/26
1.1K0
【死磕Java并发】-----J.U.C之Java并发容器:ConcurrentHashMap
Java并发容器--ConcurrentHashMap
  1、不安全:大家都知道HashMap不是线程安全的,在多线程环境下,对HashMap进行put操作会导致死循环。是因为多线程会导致Entry链表形成环形数据结构,这样Entry的next节点将永远不为空,就会产生死循环获取Entry。具体内容见HashMap随笔。
在周末
2019/09/11
4730
ConcurrentHashMap源码(一)
整体流程跟HashMap比较类似,大致是以下几步: (1)如果桶数组未初始化,则初始化; (2)如果待插入元素所在的桶为空,则尝试把此元素直接插入到桶的第一个位置; (3)如果正在扩容,则当前线程一起加入到扩容的过程中; (4)如果待插入的元素所在的桶不为空且不在迁移元素,则锁住这个桶(分段锁); (5)如果当前桶中元素以链表方式存储,则在链表中寻找该元素或者插入元素; (6)如果当前桶中元素以红黑树方式存储,则在红黑树中寻找该元素或者插入元素; (7)如果元素存在,则返回旧值; (8)如果元素不存在,整个Map的元素个数加1,并检查是否需要扩容; 添加元素操作中使用的锁主要有(自旋锁 + CAS + synchronized + 分段锁)。 为什么使用synchronized而不是ReentrantLock? 因为synchronized已经得到了极大地优化,在特定情况下并不比ReentrantLock差。
Java阿呆
2020/11/04
4050
为并发而生的 ConcurrentHashMap(Java 8)
HashMap 是我们日常最常见的一种容器,它以键值对的形式完成对数据的存储,但众所周知,它在高并发的情境下是不安全的。尤其是在 jdk 1.8 之前,rehash 的过程中采用头插法转移结点,高并发
Single
2018/01/04
2.5K0
为并发而生的 ConcurrentHashMap(Java 8)
并发容器和框架之ConcurrentHashMap
了解HashMap的人都知道HashMap是线程不安全的(多线程下的put方法达到一定大小,引发rehash,导致闭链,最终占满CPU),同时线程安全的HashTable效率又令人望而却步(每个方法都进行同步,效率低下),所以在这种情境下为并发而生的ConcurrentHashMap就应运而生! 接下来我们按照以下顺序揭开ConcurrentHashMap的面纱: JDK1.6,1.7的ConcurrentHashMap JDK1.8的ConcurrentHashMap 1.1 ConcurrentHas
MindMrWang
2018/04/16
1.1K0
并发容器和框架之ConcurrentHashMap
爆肝ConcurrentHashMap
特别说明:除第一小节以外,其他均都是以JDK 1.8的ConcurrentHashMap进行分析,本文信息量略大,每一份坚持都是值得被尊重的,希望你可以坚持读完这篇文章,也希望这篇文章对各位读者朋友有所帮助。
shysh95
2021/05/11
1.2K0
ConcurrentHashMap源码学习
既然有了HashMap为什么还会出现ConcurrentHashMap?同时ConcurrentHashMap具有什么优势?ConcurrentHashMap与HashMap、HashTable有什么区别?ConcurrentHashMap中的sizeCtl有几种值,这些值代表的是什么状态?ConcurrentHashMap使用了哪些锁?DEFAULT_CONCURRENCY_LEVEL表示什么,有什么用?
路行的亚洲
2020/07/17
5470
深度解析ConcurrentHashMap1.8源码分析
想必大家对HashMap数据结构并不陌生,JDK1.7采用的是数组+链表的方式,JDK1.8采用的是数组+链表+红黑树的方式。虽然JDK1.8对于HashMap有了很大的改进,提高了存取效率,但是线程安全的问题不可忽视,所以就有了线程安全的解决方案,比如在方法上加synchronized同步锁的HashTable,或者并发包中的ConcurrentHashMap线程安全类,本文就来和大家一起探讨一下关于ConcurrentHashMap的源码,版本是JDK1.8,下面让我们正式开始吧。
烂猪皮
2019/12/16
7360
深度解析ConcurrentHashMap1.8源码分析
JDK1.8的ConcurrentHashMap源码解析
在思考这个问题之前,我们可以思考:如果不用ConcurrentHashMap的话,有哪些其他的容器供我们选择呢?并且它们的缺陷是什么?
烂猪皮
2023/09/04
1870
JDK1.8的ConcurrentHashMap源码解析
jdk1.8中ConcurrentHashMap的实现原理
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fjse51/article/details/55260493
提莫队长
2019/06/11
3K0
Java8 ConcurrentHashMap源码解析
JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本。
黑洞代码
2021/02/09
3760
ConcurrentHashMap源码分析
本文基于jdk1.8对concurrentHashMap的源码进行分析,以put()方法为入口对concurrentHashMap的扩容机制,size计算方式等代码进行分析
Java进阶之路
2022/08/03
4140
ConcurrentHashMap源码分析
JUC学习笔记(二)—ConcurentHashMap
Unsafe:是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。 常见方法:
Monica2333
2020/06/19
5670
Java的ConcurrentHashMap
ConcurrentHashMap是Java中的一个线程安全且高效的HashMap实现。
用户3467126
2019/07/25
5800
Java的ConcurrentHashMap
Java集合源码解析-ConcurrentHashMap(JDK8)
Java 7为实现并发访问,引入了Segment这一结构,实现了分段锁,理论上最大并发度与Segment个数相等。
JavaEdge
2021/10/18
2550
详解ConCurrentHashMap源码(jdk1.8)
ConCurrentHashMap是一个支持高并发集合,常用的集合之一,在jdk1.8中ConCurrentHashMap的结构和操作和HashMap都很类似:
用户10384376
2023/02/26
2820
详解ConCurrentHashMap源码(jdk1.8)
一文吃透ConcurrentHashMap的前世与今生
hello,everyone。上篇博客中介绍了HashMap,本文给大家带来ConcurrentHashMap。
柏炎
2022/08/23
2820
一文吃透ConcurrentHashMap的前世与今生
ConcurrentHashMap 1.7和1.8区别
ConcurrentHashMap 与HashMap和Hashtable 最大的不同在于:put和 get 两次Hash到达指定的HashEntry,第一次hash到达Segment,第二次到达Segment里面的Entry,然后在遍历entry链表
全栈程序员站长
2022/09/12
6660
ConcurrentHashMap 1.7和1.8区别
推荐阅读
相关推荐
ConcurrentHashMap1.8 – 扩容详解「建议收藏」
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验