大家好,又见面了,我是你们的朋友全栈君。
Sigar简介
Sigar是Hyperic-hq产品的基础包,是Hyperic HQ主要的数据收集组件。它用来从许多平台收集系统和处理信息。
这些平台包括:Linux, Windows, Solaris, AIX, HP-UX, FreeBSD and Mac OSX。
Sigar有C,C#,Java和Perl API,java版的API为sigar.jar,sigar.jar的底层是用C语言编写的,它通过本地方法来调用操作系统API来获取系统相关数据。
(查看源码,可以发现,各种获取信息的方法都是native的接口,更多原理看这里:java中native关键字的用法 )
Sigar压缩包下载
Hyperic-hq官方网站:http://www.hyperic.com
Sigar.jar下载地址:http://sigar.hyperic.com
备用下载地址:点击下载
Sigar为不同平台提供的不同库文件
这些文件在下载下来的压缩包里
参考官方主页上的配置项。
File | Language | Description | Required |
---|---|---|---|
sigar.jar | Java | Java API | Yes (for Java only) |
log4j.jar | Java | Java logging API | No |
libsigar-x86-linux.so | C | Linux AMD/Intel 32-bit | * |
libsigar-amd64-linux.so | C | Linux AMD/Intel 64-bit | * |
libsigar-ppc-linux.so | C | Linux PowerPC 32-bit | * |
libsigar-ppc64-linux.so | C | Linux PowerPC 64-bit | * |
libsigar-ia64-linux.so | C | Linux Itanium 64-bit | * |
libsigar-s390x-linux.so | C | Linux zSeries 64-bit | * |
sigar-x86-winnt.dll | C | Windows AMD/Intel 32-bit | * |
sigar-amd64-winnt.dll | C | Windows AMD/Intel 64-bit | * |
libsigar-ppc-aix-5.so | C | AIX PowerPC 32-bit | * |
libsigar-ppc64-aix-5.so | C | AIX PowerPC 64-bit | * |
libsigar-pa-hpux-11.sl | C | HP-UX PA-RISC 32-bit | * |
libsigar-ia64-hpux-11.sl | C | HP-UX Itanium 64-bt | * |
libsigar-sparc-solaris.so | C | Solaris Sparc 32-bit | * |
libsigar-sparc64-solaris.so | C | Solaris Sparc 64-bit | * |
libsigar-x86-solaris.so | C | Solaris AMD/Intel 32-bit | * |
libsigar-amd64-solaris.so | C | Solaris AMD/Intel 64-bit | * |
libsigar-universal-macosx.dylib | C | Mac OS X PowerPC/Intel 32-bit | * |
libsigar-universal64-macosx.dylib | C | Mac OS X PowerPC/Intel 64-bit | * |
libsigar-x86-freebsd-5.so | C | FreeBSD 5.x AMD/Intel 32-bit | * |
libsigar-x86-freebsd-6.so | C | FreeBSD 6.x AMD/Intel 64-bit | * |
libsigar-amd64-freebsd-6.so | C | FreeBSD 6.x AMD/Intel 64-bit | * |
Sigar API
Sigar API 提供一个方便的接口来收集系统信息,如:
◆系统内存,页面交换,cpu,平均负载,运行时间,登录信息
◆每个进程占用的内存,cpu,帐号信息,状态,参数,环境,打开的文件
◆文件系统探测和度量
◆网络接口探测,配置信息和度量
◆网络路由和连接表
写代码前的准备
1.按照主页上的说明解压包后将相应的文件copy到java路径。比如windows64位操作系统需要将lib中sigar-amd64-winnt.dll文件拷贝到java SDK目录的bin内。
2.把上面的sigar.jar添加到项目里。
Sigar Java代码使用示例
1、获取CPU信息代码
(1)代码解析
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 | // CPU数量(单位:个) int cpuLength = sigar.getCpuInfoList().length; print(cpuLength); // CPU的总量(单位:HZ)及CPU的相关信息 CpuInfo infos[] = sigar.getCpuInfoList(); for ( int i = 0 ; i < infos.length; i ) { // 不管是单块CPU还是多CPU都适用 CpuInfo info = infos[i]; print( "mhz=" info.getMhz()); // CPU的总量MHz print( "vendor=" info.getVendor()); // 获得CPU的卖主,如:Intel print( "model=" info.getModel()); // 获得CPU的类别,如:Celeron print( "cache size=" info.getCacheSize()); // 缓冲存储器数量 } /** CPU的用户使用量、系统使用剩余量、总的剩余量、总的使用占用量等(单位:100%) **/ // 方式一,主要是针对一块CPU的情况 CpuPerc cpu; try { cpu = sigar.getCpuPerc(); printCpuPerc(cpu); } catch (SigarException e) { e.printStackTrace(); } // 方式二,不管是单块CPU还是多CPU都适用 CpuPerc cpuList[] = null ; try { cpuList = sigar.getCpuPercList(); } catch (SigarException e) { e.printStackTrace(); } for ( int i = 0 ; i < cpuList.length; i ) { // printCpuPerc(cpuList[i]); } |
---|
(2)静态工具类合成
代码语言:javascript复制 /**
* 静态工具类获取cpu的信息
* @throws SigarException
*/
private static void cpu() throws SigarException {
Sigar sigar = new Sigar();
//CPU的总量(单位:HZ)及CPU的相关信息
CpuInfo infos[] = sigar.getCpuInfoList();
CpuPerc cpuList[] = null;
cpuList = sigar.getCpuPercList();
for (int i = 0; i < infos.length; i ) {// 不管是单块CPU还是多CPU都适用
CpuInfo info = infos[i];
System.out.println("第" (i 1) "块CPU信息");
System.out.println("CPU的总量MHz: " info.getMhz());// CPU的总量MHz
System.out.println("CPU生产商: " info.getVendor());// 获得CPU的卖主,如:Intel
System.out.println("CPU类别: " info.getModel());// 获得CPU的类别,如:Celeron
System.out.println("CPU缓存数量: " info.getCacheSize());// 缓冲存储器数量
//当前CPU的用户使用率、系统使用率、当前等待率、当前空闲率、总的使用率
printCpuPerc(cpuList[i]);
}
}
/**
* 静态工具类:获取当前CPU的用户使用率、系统使用率、当前等待率、当前空闲率、总的使用率
* @param cpu:当前CPU
*/
private static void printCpuPerc(CpuPerc cpu) {
System.out.println("CPU用户使用率: " CpuPerc.format(cpu.getUser()));// 用户使用率
System.out.println("CPU系统使用率: " CpuPerc.format(cpu.getSys()));// 系统使用率
System.out.println("CPU当前等待率: " CpuPerc.format(cpu.getWait()));// 当前等待率
System.out.println("CPU当前错误率: " CpuPerc.format(cpu.getNice()));//当前错误率
System.out.println("CPU当前空闲率: " CpuPerc.format(cpu.getIdle()));// 当前空闲率
System.out.println("CPU总的使用率: " CpuPerc.format(cpu.getCombined()));// 总的使用率
}
2、获取内存信息代码
(1)代码解析
代码语言:javascript复制// 物理内存信息
Mem mem = sigar.getMem();
// 内存总量
print("Total = " mem.getTotal() / 1024L / 1024 "M av");
// 当前内存使用量
print("Used = " mem.getUsed() / 1024L / 1024 "M used");
// 当前内存剩余量
print("Free = " mem.getFree() / 1024L / 1024 "M free");
// 系统页面文件交换区信息
Swap swap = sigar.getSwap();
// 交换区总量
print("Total = " swap.getTotal() / 1024L "K av");
// 当前交换区使用量
print("Used = " swap.getUsed() / 1024L "K used");
// 当前交换区剩余量
print("Free = " swap.getFree() / 1024L "K free");
(2)静态工具类合成
代码语言:javascript复制 /**
* 静态工具类:获取内存信息
* @throws SigarException
*/
private static void memory() throws SigarException {
Sigar sigar = new Sigar();
// 物理内存信息
Mem mem = sigar.getMem();
// 内存总量
System.out.println("内存总量: " mem.getTotal() / 1024L "K av");
// 当前内存使用量
System.out.println("当前内存使用量: " mem.getUsed() / 1024L "K used");
// 当前内存剩余量
System.out.println("当前内存剩余量: " mem.getFree() / 1024L "K free");
//系统页面文件交换区信息
Swap swap = sigar.getSwap();
// 交换区总量
System.out.println("交换区总量: " swap.getTotal() / 1024L "K av");
// 当前交换区使用量
System.out.println("当前交换区使用量: " swap.getUsed() / 1024L "K used");
// 当前交换区剩余量
System.out.println("当前交换区剩余量: " swap.getFree() / 1024L "K free");
}
3、获取操作系统信息代码
(1)代码解析
代码语言:javascript复制<span style="white-space: normal; "> </span><span style="white-space: normal; ">// 取到当前操作系统的名称</span>
String hostname = "";
try {
hostname = InetAddress.getLocalHost().getHostName();
} catch (Exception exc) {
try {
hostname = sigar.getNetInfo().getHostName();
} catch (SigarException e) {
hostname = "localhost.unknown";
} finally {
sigar.close();
}
}
print(hostname);
// 取当前操作系统的信息
OperatingSystem OS = OperatingSystem.getInstance();
// 操作系统内核类型如: 386、486、586等x86
print("OS.getArch() = " OS.getArch());
print("OS.getCpuEndian() = " OS.getCpuEndian());//
print("OS.getDataModel() = " OS.getDataModel());//
// 系统描述
print("OS.getDescription() = " OS.getDescription());
print("OS.getMachine() = " OS.getMachine());//
// 操作系统类型
print("OS.getName() = " OS.getName());
print("OS.getPatchLevel() = " OS.getPatchLevel());//
// 操作系统的卖主
print("OS.getVendor() = " OS.getVendor());
// 卖主名称
System.out.println("OS.getVendorCodeName() = " OS.getVendorCodeName());
// 操作系统名称
print("OS.getVendorName() = " OS.getVendorName());
// 操作系统卖主类型
print("OS.getVendorVersion() = " OS.getVendorVersion());
// 操作系统的版本号
print("OS.getVersion() = " OS.getVersion());
// 取当前系统进程表中的用户信息
Who who[] = sigar.getWhoList();
if (who != null && who.length > 0) {
for (int i = 0; i < who.length; i ) {
print("n~~~~~~~~~" String.valueOf(i) "~~~~~~~~~");
Who _who = who[i];
print("getDevice() = " _who.getDevice());
print("getHost() = " _who.getHost());
print("getTime() = " _who.getTime());
// 当前系统进程表中的用户名
print("getUser() = " _who.getUser());
}
}
(2)静态工具类合成
代码语言:javascript复制 /**
* 静态工具类:获取操作系统信息代码
*/
private static void os() {
// 取当前操作系统的信息
OperatingSystem OS = OperatingSystem.getInstance();
// 操作系统内核类型如: 386、486、586等x86
System.out.println("操作系统: " OS.getArch());
System.out.println("操作系统CpuEndian(): " OS.getCpuEndian());//
System.out.println("操作系统DataModel(): " OS.getDataModel());//
// 系统描述
System.out.println("操作系统的描述: " OS.getDescription());
// 操作系统类型
System.out.println("OS.getName(): " OS.getName());
System.out.println("OS.getPatchLevel(): " OS.getPatchLevel());//
// 操作系统的卖主
System.out.println("操作系统的卖主: " OS.getVendor());
// 卖主名称
System.out.println("操作系统的卖主名: " OS.getVendorCodeName());
// 操作系统名称
System.out.println("操作系统名称: " OS.getVendorName());
// 操作系统卖主类型
System.out.println("操作系统卖主类型: " OS.getVendorVersion());
// 操作系统的版本号
System.out.println("操作系统的版本号: " OS.getVersion());
}
4、获取当前系统进程表中的用户信息
(1)代码解析
代码语言:javascript复制// 取当前系统进程表中的用户信息
Who who[] = sigar.getWhoList();
if (who != null && who.length > 0) {
for (int i = 0; i < who.length; i ) {
print("n~~~~~~~~~" String.valueOf(i) "~~~~~~~~~");
Who _who = who[i];
print("getDevice() = " _who.getDevice());
print("getHost() = " _who.getHost());
print("getTime() = " _who.getTime());
// 当前系统进程表中的用户名
print("getUser() = " _who.getUser());
}
}
(2)静态工具类合成
代码语言:javascript复制 /**
* 静态工具类:取当前系统进程表中的用户信息
* @throws SigarException
*/
private static void who() throws SigarException {
Sigar sigar = new Sigar();
Who who[] = sigar.getWhoList();
if (who != null && who.length > 0) {
for (int i = 0; i < who.length; i ) {
System.out.println("当前系统进程表中的用户名" String.valueOf(i));
Who _who = who[i];
System.out.println("用户控制台: " _who.getDevice());
System.out.println("用户host: " _who.getHost());
System.out.println("getTime(): " _who.getTime());
// 当前系统进程表中的用户名
System.out.println("当前系统进程表中的用户名: " _who.getUser());
}
}
}
5、获取磁盘信息代码
(1)代码解析
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 | <span style= "white-space: normal; " > // 取硬盘已有的分区及其详细信息(通过sigar.getFileSystemList()来获得FileSystem列表对象,然后对其进行编历</span> FileSystem fslist[] = sigar.getFileSystemList(); String dir = System.getProperty( "user.home" ); // 当前用户文件夹路径 print(dir " " fslist.length); for ( int i = 0 ; i < fslist.length; i ) { print( "n~~~~~~~~~~" i "~~~~~~~~~~" ); FileSystem fs = fslist[i]; // 分区的盘符名称 print( "fs.getDevName() = " fs.getDevName()); // 分区的盘符名称 print( "fs.getDirName() = " fs.getDirName()); print( "fs.getFlags() = " fs.getFlags()); // // 文件系统类型,比如 FAT32、NTFS print( "fs.getSysTypeName() = " fs.getSysTypeName()); // 文件系统类型名,比如本地硬盘、光驱、网络文件系统等 print( "fs.getTypeName() = " fs.getTypeName()); // 文件系统类型 print( "fs.getType() = " fs.getType()); FileSystemUsage usage = null ; try { usage = sigar.getFileSystemUsage(fs.getDirName()); } catch (SigarException e) { if (fs.getType() == 2 ) throw e; continue ; } switch (fs.getType()) { case 0 : // TYPE_UNKNOWN :未知 break ; case 1 : // TYPE_NONE break ; case 2 : // TYPE_LOCAL_DISK : 本地硬盘 // 文件系统总大小 print( " Total = " usage.getTotal() "KB" ); // 文件系统剩余大小 print( " Free = " usage.getFree() "KB" ); // 文件系统可用大小 print( " Avail = " usage.getAvail() "KB" ); // 文件系统已经使用量 print( " Used = " usage.getUsed() "KB" ); double usePercent = usage.getUsePercent() * 100D; // 文件系统资源的利用率 print( " Usage = " usePercent "%" ); break ; case 3 : // TYPE_NETWORK :网络 break ; case 4 : // TYPE_RAM_DISK :闪存 break ; case 5 : // TYPE_CDROM :光驱 break ; case 6 : // TYPE_SWAP :页面交换 break ; } print( " DiskReads = " usage.getDiskReads()); print( " DiskWrites = " usage.getDiskWrites()); } |
---|
(2)静态工具类合成
代码语言:javascript复制 /**
* 静态工具类:获取磁盘信息
* @throws Exception
*/
private static void file() throws Exception {
Sigar sigar = new Sigar();
//通过sigar.getFileSystemList()来获得FileSystem列表对象,然后对其进行编历
FileSystem fslist[] = sigar.getFileSystemList();
for (int i = 0; i < fslist.length; i ) {
System.out.println("分区的盘符名称" i);
FileSystem fs = fslist[i];
// 分区的盘符名称
System.out.println("盘符名称: " fs.getDevName());
// 分区的盘符名称
System.out.println("盘符路径: " fs.getDirName());
System.out.println("盘符标志: " fs.getFlags());//
// 文件系统类型,比如 FAT32、NTFS
System.out.println("盘符类型: " fs.getSysTypeName());
// 文件系统类型名,比如本地硬盘、光驱、网络文件系统等
System.out.println("盘符类型名: " fs.getTypeName());
// 文件系统类型
System.out.println("盘符文件系统类型: " fs.getType());
FileSystemUsage usage = null;
usage = sigar.getFileSystemUsage(fs.getDirName());
switch (fs.getType()) {
case 0: // TYPE_UNKNOWN :未知
break;
case 1: // TYPE_NONE
break;
case 2: // TYPE_LOCAL_DISK : 本地硬盘
// 文件系统总大小
System.out.println(fs.getDevName() "总大小: " usage.getTotal() "KB");
// 文件系统剩余大小
System.out.println(fs.getDevName() "剩余大小: " usage.getFree() "KB");
// 文件系统可用大小
System.out.println(fs.getDevName() "可用大小: " usage.getAvail() "KB");
// 文件系统已经使用量
System.out.println(fs.getDevName() "已经使用量: " usage.getUsed() "KB");
double usePercent = usage.getUsePercent() * 100D;
// 文件系统资源的利用率
System.out.println(fs.getDevName() "资源的利用率: " usePercent "%");
break;
case 3:// TYPE_NETWORK :网络
break;
case 4:// TYPE_RAM_DISK :闪存
break;
case 5:// TYPE_CDROM :光驱
break;
case 6:// TYPE_SWAP :页面交换
break;
}
System.out.println(fs.getDevName() "读出: " usage.getDiskReads());
System.out.println(fs.getDevName() "写入: " usage.getDiskWrites());
}
return;
}
6、获取System信息代码(从JVM获取)
(1)静态工具类合成
代码语言:javascript复制 /**
* 静态工具类:获取当前(操作系统)信息,从jvm获取
* @throws UnknownHostException
*/
private static void property() throws UnknownHostException {
Runtime r = Runtime.getRuntime();
Properties props = System.getProperties();
InetAddress addr;
addr = InetAddress.getLocalHost();
String ip = addr.getHostAddress();
Map<String, String> map = System.getenv();
String userName = map.get("USERNAME");// 获取用户名
String computerName = map.get("COMPUTERNAME");// 获取计算机名
String userDomain = map.get("USERDOMAIN");// 获取计算机域名
System.out.println("用户名: " userName);
System.out.println("计算机名: " computerName);
System.out.println("计算机域名: " userDomain);
System.out.println("本地ip地址: " ip);
System.out.println("本地主机名: " addr.getHostName());
System.out.println("JVM可以使用的总内存: " r.totalMemory());
System.out.println("JVM可以使用的剩余内存: " r.freeMemory());
System.out.println("JVM可以使用的处理器个数: " r.availableProcessors());
System.out.println("Java的运行环境版本: " props.getProperty("java.version"));
System.out.println("Java的运行环境供应商: " props.getProperty("java.vendor"));
System.out.println("Java供应商的URL: " props.getProperty("java.vendor.url"));
System.out.println("Java的安装路径: " props.getProperty("java.home"));
System.out.println("Java的虚拟机规范版本: " props.getProperty("java.vm.specification.version"));
System.out.println("Java的虚拟机规范供应商: " props.getProperty("java.vm.specification.vendor"));
System.out.println("Java的虚拟机规范名称: " props.getProperty("java.vm.specification.name"));
System.out.println("Java的虚拟机实现版本: " props.getProperty("java.vm.version"));
System.out.println("Java的虚拟机实现供应商: " props.getProperty("java.vm.vendor"));
System.out.println("Java的虚拟机实现名称: " props.getProperty("java.vm.name"));
System.out.println("Java运行时环境规范版本: " props.getProperty("java.specification.version"));
System.out.println("Java运行时环境规范供应商: " props.getProperty("java.specification.vender"));
System.out.println("Java运行时环境规范名称: " props.getProperty("java.specification.name"));
System.out.println("Java的类格式版本号: " props.getProperty("java.class.version"));
System.out.println("Java的类路径: " props.getProperty("java.class.path"));
System.out.println("加载库时搜索的路径列表: " props.getProperty("java.library.path"));
System.out.println("默认的临时文件路径: " props.getProperty("java.io.tmpdir"));
System.out.println("一个或多个扩展目录的路径: " props.getProperty("java.ext.dirs"));
System.out.println("操作系统的名称: " props.getProperty("os.name"));
System.out.println("操作系统的构架: " props.getProperty("os.arch"));
System.out.println("操作系统的版本: " props.getProperty("os.version"));
System.out.println("文件分隔符: " props.getProperty("file.separator"));
System.out.println("路径分隔符: " props.getProperty("path.separator"));
System.out.println("行分隔符: " props.getProperty("line.separator"));
System.out.println("用户的账户名称: " props.getProperty("user.name"));
System.out.println("用户的主目录: " props.getProperty("user.home"));
System.out.println("用户的当前工作目录: " props.getProperty("user.dir"));
}
7、获取网络流量等信息代码
代码语言:javascript复制 /**
* 静态工具类:获取网络流量等信息
* @throws Exception
*/
private static void net() throws Exception {
Sigar sigar = new Sigar();
String ifNames[] = sigar.getNetInterfaceList();
for (int i = 0; i < ifNames.length; i ) {
String name = ifNames[i];
NetInterfaceConfig ifconfig = sigar.getNetInterfaceConfig(name);
System.out.println("网络设备名: " name);// 网络设备名
System.out.println("IP地址: " ifconfig.getAddress());// IP地址
System.out.println("子网掩码: " ifconfig.getNetmask());// 子网掩码
if ((ifconfig.getFlags() & 1L) <= 0L) {
System.out.println("!IFF_UP...skipping getNetInterfaceStat");
continue;
}
NetInterfaceStat ifstat = sigar.getNetInterfaceStat(name);
System.out.println(name "接收的总包裹数:" ifstat.getRxPackets());// 接收的总包裹数
System.out.println(name "发送的总包裹数:" ifstat.getTxPackets());// 发送的总包裹数
System.out.println(name "接收到的总字节数:" ifstat.getRxBytes());// 接收到的总字节数
System.out.println(name "发送的总字节数:" ifstat.getTxBytes());// 发送的总字节数
System.out.println(name "接收到的错误包数:" ifstat.getRxErrors());// 接收到的错误包数
System.out.println(name "发送数据包时的错误数:" ifstat.getTxErrors());// 发送数据包时的错误数
System.out.println(name "接收时丢弃的包数:" ifstat.getRxDropped());// 接收时丢弃的包数
System.out.println(name "发送时丢弃的包数:" ifstat.getTxDropped());// 发送时丢弃的包数
}
}
7、获取以太网信息代码
代码语言:javascript复制 /**
* 静态工具类:获取以太网信息
* @throws SigarException
*/
private static void ethernet() throws SigarException {
Sigar sigar = null;
sigar = new Sigar();
String[] ifaces = sigar.getNetInterfaceList();
for (int i = 0; i < ifaces.length; i ) {
NetInterfaceConfig cfg = sigar.getNetInterfaceConfig(ifaces[i]);
if (NetFlags.LOOPBACK_ADDRESS.equals(cfg.getAddress()) || (cfg.getFlags() & NetFlags.IFF_LOOPBACK) != 0
|| NetFlags.NULL_HWADDR.equals(cfg.getHwaddr())) {
continue;
}
System.out.println(cfg.getName() "IP地址:" cfg.getAddress());// IP地址
System.out.println(cfg.getName() "网关广播地址:" cfg.getBroadcast());// 网关广播地址
System.out.println(cfg.getName() "网卡MAC地址:" cfg.getHwaddr());// 网卡MAC地址
System.out.println(cfg.getName() "子网掩码:" cfg.getNetmask());// 子网掩码
System.out.println(cfg.getName() "网卡描述信息:" cfg.getDescription());// 网卡描述信息
System.out.println(cfg.getName() "网卡类型" cfg.getType());//
}
}
8、获取用户信息代码
代码语言:javascript复制 /**
* 静态工具类:取当前系统进程表中的用户信息
* @throws SigarException
*/
private static void who() throws SigarException {
Sigar sigar = new Sigar();
Who who[] = sigar.getWhoList();
if (who != null && who.length > 0) {
for (int i = 0; i < who.length; i ) {
System.out.println("当前系统进程表中的用户名" String.valueOf(i));
Who _who = who[i];
System.out.println("用户控制台: " _who.getDevice());
System.out.println("用户host: " _who.getHost());
System.out.println("getTime(): " _who.getTime());
// 当前系统进程表中的用户名
System.out.println("当前系统进程表中的用户名: " _who.getUser());
}
}
}
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/107235.html原文链接:https://javaforall.cn