文章目录
- 系统信息显示及磁盘空间统计
- Ⅰ 项目模块
- (1)项目总体模块框图
- Ⅱ CPU占有率小记
- Ⅲ 磁盘统计小记
系统信息显示及磁盘空间统计
项目目标
- 统计磁盘某个文件目录下子目录、子文件所占空间的大小
- 通过曲线图实时展示CPU 占有率
项目使用技术栈与平台
- 所用技术:javaSE/javafx
- 平台与环境:windows 10/jdk1.8/idea
项目功能
- 显示CPU 占有率,OS 版本,内存大小,操作系统架构等系统信息
- 文件目录扫描
项目展示:
(1)显示CPU 占有率
(2) 磁盘空间统计
Ⅰ 项目模块
- 项目总体模块框图
- 创建主程序
- fxml 描述UI
- CPU 占有率Tab 页设计
- 磁盘扫描Tab 页设计
- Controller 模块
- 系统资源获取模块
- 文件目录扫描模块
(1)项目总体模块框图
OS Monitor 项目结构分为两部分:UI 部分和逻辑部分。
UI 部分:主程序、磁盘空间扫描Tab 页、CPU 占有率Tab 页、Controller。UI 部分是通过javafx来实现。
逻辑部分:系统资源获取、文件目录扫描。系统资源是通过OperatingSystemMXBean 来获取。
(2)JavaFX
javaFX 是JAVA 的GUI (图形用户界面)工具箱,javafx能够使用布局文件(XML)设计GUI 并使用CSS 设置样式。JavaFX 还将2D 和3D 图形、图表,以及音频、视频和嵌入式Web应用程序集成到一个GUI工具包中。
javafx 的核心构架:
javafx 控件:
javafx 中的控件是一个树形结构,每个节点都是一个控件,所有控件都是从Node 派生下来的,所以每个控件都可以叫做Node。
JavaFX编程指南:http://tutorials.jenkov.com/javafx/index.html(3)创建项目:
打开Idea,选择菜单"file" -> “new” -> “project” 。
(4)创建主程序
将项目自动生成的主程序Main替换成我们的OSMonitorApplication。
public class OSMonitorApplication extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
// 1. 加载.fxml 文件
FXMLLoader loader = new
FXMLLoader(getClass().getClassLoader().getResource("os_monitor_tab1.fxml"));
Parent root = loader.load();
// 2. 创建一个场景对象
Scene scene = new Scene(root, 800, 600);
// 3. 给舞台对象设置标题
primaryStage.setTitle("OS Monitor");
// 4. 给舞台对象stage 设置场景对象scene
primaryStage.setScene(scene);
// 5. 展示舞台
primaryStage.show();
}
}
加载.fxml 文件:
//getResource从resource下获取资源,类加载器
FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource("os_monitor_tab.fxml"));
真正的加载:
Parent root = loader.load();
注意:
- 所有javafx 主程序必须派生自Application 类
- 子类必须override 父类的start 方法
创建一个资源文件夹resource,并将其标记为资源路径:
(5)fxml 描述UI
我们要用自己设计的UI 替换工程自动生成的.fxml 文件(UI的设计部分主要由SceneBuilder来完成)。UI 的主题框架是通过fxml 来描述。UI 交互比较简单,只有两个tab 页。fxml 中所有元素名称都是Javafx 中的类名或者是类的属性名,元素的属性是javafx 中类的属性。
UI 主框架包含两个tab 页,用到的控件是<TabPane>
和<Tab>
。<TabPane>
代表tab页所在的容器面板,代表一个个的tab 页。
<?xml version="1.0" encoding="UTF-8"?>
// 导入类路径
<?import javafx.geometry.Insets?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.171"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.haska.ui.OSMonitorController">
<children>
<TabPane tabClosingPolicy="UNAVAILABLE">
<tabs>
<Tab onSelectionChanged="#handleCPUSelectionChanged" text="CPU 占有
率">
<content>
...
</content>
</Tab>
<Tab text="磁盘空间统计">
<content>
...
</content>
</Tab>
</tabs>
</TabPane>
</children>
</VBox>
(6)CPU 占有率Tab 页设计
CPU 占有率= CPU 执行程序时间/ 统计周期时间。比如,每100ms 统计一次占用率,如果CPU 执行程序用了90ms,那么CPU 占用率是90/100 = 90%。CPU 执行程序时间包括:用户程序执行时间和内核空间执行时间。CPU 占有率Tab 页主要是通过<LineChart>
控件绘制曲线图,x 轴和y 轴都用<NumberAxis>
控件。通过<Tab>
控件的onSelectionChanged
属性设置监听事件的方法:handleCPUSelectionChanged。
<Tab onSelectionChanged="#handleCPUSelectionChanged" text="CPU 占有率">
<content>
<VBox>
<children>
<HBox VBox.vgrow="ALWAYS">
<children>
<LineChart fx:id="cpuChart" animated="false" HBox.hgrow="ALWAYS">
<xAxis>
<NumberAxis animated="false" label="Seconds" side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis animated="false" label="Percent" side="LEFT" />
</yAxis>
</LineChart>
</children>
</HBox>
<HBox VBox.vgrow="ALWAYS">
<children>
<GridPane hgap="10.0" vgap="10.0" HBox.hgrow="ALWAYS">
<columnConstraints>
<ColumnConstraints hgrow="NEVER" minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Text fx:id="osType" strokeType="OUTSIDE" strokeWidth="0.0" GridPane.columnIndex="1" />
<Label text="OS:" />
<Label text="Arch:" GridPane.rowIndex="1" />
<Label text="TotalMemory:" GridPane.rowIndex="2" />
<Text fx:id="cpuArch" strokeType="OUTSIDE" strokeWidth="0.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Text fx:id="TotalMemory" strokeType="OUTSIDE" strokeWidth="0.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
</children>
<HBox.margin>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</HBox.margin>
</GridPane>
</children>
<VBox.margin>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</VBox.margin>
</HBox>
</children>
</VBox>
</content>
</Tab>
(7)磁盘扫描Tab 页设计
磁盘扫描Tab 页主要是用到了<TreeTableView>
控件,绘制一个树形表格。另外设计了一个<Button>
控件来选择文件目录,通<Button>
控件的onAction
属性设置监听事件的方法:handleSelectFile。
<Label text="选择文件目录:"></Label>
<Button mnemonicParsing="false" onAction="#handleSelectFile" prefWidth="80.0"
text="选择" GridPane.columnIndex="1">
<GridPane.margin>
<Insets bottom="5.0" top="5.0" />
</GridPane.margin>
</Button>
<TreeTableView fx:id="fileStat" HBox.hgrow="ALWAYS">
<columns>
<TreeTableColumn prefWidth="75.0" text="目录名">
<cellValueFactory>
<TreeItemPropertyValueFactory property="fileName" />
</cellValueFactory>
</TreeTableColumn>
<TreeTableColumn prefWidth="75.0" text="总长度">
<cellValueFactory>
<TreeItemPropertyValueFactory property="totalLength" />
</cellValueFactory>
</TreeTableColumn>
</columns>
</TreeTableView>
(8)Controller 模块(只有Controller和xml文件是直接关联的)
Controller 主要是用于处理UI 事件,需要将此类添加到.fxml 文件中,我们用到的顶级容器是<VBox>
,通过fx:controller
属性设置Controller 类的包路径,方法如下:
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.171"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.haska.ui.OSMonitorController">
</VBox>
在Controller 中,我们需要处理两个事件:CPU 占有率和目录结构统计,代码如下:
// 磁盘目录扫描事件处理方法
public void handleSelectFile(ActionEvent actionEvent)
{
// 1. 打开文件选择对话框
// 2. 开启磁盘目录扫描线程
// 3. 渲染TreeTableView
}
// CPU 占有率事件处理方法
public void handleCPUSelectionChanged(Event event) {
// 1. 创建一个定时器,每隔一秒获取一次CPU资源绘制曲线图
// 2. 绘制LineChart
}
注意:
在Controller中可以通过xml中控件的id名对控件进行调用。
(9) 系统资源获取模块
系统资源获取采用的是OperatingSystemMXBean
,这是一个JMX 接口,用于获取运行JVM 的系统的资源信息,比如,CPU 占有率,OS 版本,内存大小等等。我们用的是com.sun.management.OperatingSystemMXBean
类。
绘制CPU 占有率曲线的核心思想:
- 每隔一秒对CPU 占有率进行一次采样,作为数轴的y 坐标
- 我们一共保存60 秒,即1 分钟的样本点,时间作为x 坐标。
- 用一个数组保存坐标(x,y),每一次采样,需要把之前采样的坐标点的x 坐标减1,这样绘制的时候就会产生移动的效果。
private static final int DATA_LENGH = 60;
private static XYPair[] cpuDatas = new XYPair[DATA_LENGH];
private static int firstIndex = DATA_LENGH;
private static void moveCPUData(double cpuPercetage){
int movIdx = -1;
if (firstIndex == 0){
movIdx = firstIndex + 1;
}else {
movIdx = firstIndex;
firstIndex--;
}
for (; movIdx < cpuDatas.length; ++movIdx){
cpuDatas[movIdx-1].setX(cpuDatas[movIdx].getX()-1);
cpuDatas[movIdx-1].setY(cpuDatas[movIdx].getY());
}
movIdx--;
cpuDatas[movIdx] = new XYPair(movIdx, cpuPercetage);
}
(10) 文件目录扫描模块
文件目录扫描比较简单,核心思想是:用递归的方式遍历文件目录结构,统计某个目录下面所有子目录占用总的磁盘空间大小,然后再做一个汇总。
File[] files = node.getFile().listFiles();
if (files == null) {
return;
}
for (File file : files) {
FileTreeNode child = new FileTreeNode();
child.setFile(file);
child.setFileName(file.getName());
if (file.isDirectory()) {
scannerDirectory(child);
} else {
child.setTotalLength(file.length());
}
node.setTotalLength(node.getTotalLength() + child.getTotalLength());
node.addChildNode(child);
}
Ⅱ CPU占有率小记
利用JMX 获取系统资源,内存,cpu占有率等
private static OperatingSystemMXBean mxBean =
ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);
需要将CPUchart和我们构建的坐标点联系起来,cpuChart即为xml中的id名。
逻辑部分和Ui部分的坐标点转换:
//拿到坐标点
OSResource.XYPair[] xyPairs = OSResource.getCPUPercetage();
XYChart.Series series = new XYChart.Series();
//用XYChart存放坐标点,Series里面有很多坐标点
for(OSResource.XYPair xyPair:xyPairs){
//将数据转换为XYChart的坐标
XYChart.Data data = new XYChart.Data(xyPair.getX(),xyPair.getY());
series.getData().add(data);
}
加入定时器:每隔一段时间,执行一次任务;
//定时器线程
private Timer timer = new Timer();
//定时器任务
private TimerTask timerTask = null;
//创建一个线程任务
timerTask = new TimerTask() {
@Override
public void run() {
//具体任务//将数据转换为XYChart的坐标
};
//0代表任务安排后,立刻执行,1000ms为周期执行时间
timer.schedule(timerTask,0,1000);
拿到要触发事件的对象
Tab tab = (Tab)event.getTarget();//拿到触发事件的对象
注意:
在gui程序中,ui界面的渲染需要在主线程完成,不能在子线程中完成。
ui里提供了一个方法,可以将线程切换到主线程中执行。
//将渲染逻辑切换到主线程执行
Platform.runLater(
() ->{
//清除上一次图表中的数据点
if(cpuChart.getData().size()>0){
cpuChart.getData().remove(0);
}
cpuChart.getData().add(series);//数据点
osType.setText(OSResource.getOSName());
cpuArch.setText(OSResource.getcpuArch());
Version.setText(OSResource.getVersion());
}
);
Ⅲ 磁盘统计小记
创建一个目录选择器
DirectoryChooser directoryChooser = new DirectoryChooser();
拿到OSmonistor的窗体primaryStage
//定义一个空的窗体
private Stage primaryStage = null;
public void setPrimaryStage(Stage primaryStage) {
this.primaryStage = primaryStage;
}
在主函数中,将窗体传给Controller。
OSMonitorController controller=loader.getController();
//加载fxml时,loader包含controller
//将主窗口primaryStage传给OSMonitorController
controller.setPrimaryStage(primaryStage);
将弹出的对话框附着在primaryStage,并返回一个我们选择的目录或文件对象
File file = directoryChooser.showDialog(primaryStage);
//file代表返回的选择目录或文件
TreeTableView是一个泛型类:@FXML private TreeTableView<FileTreeNode> fileStat;
//准备好逻辑部分后开始渲染
TreeItem rootItem = new TreeItem(rootNode, new ImageView(image));
这只是将根节点转换过来了。
注意:
private String fileName;//文件名
private long totalLength;//文件的总长
List<FileTreeNode> childrens = new ArrayList<>();//记录子目录
这里的文件名和文件总长要和xml中的一致
<TreeTableColumn prefWidth="75.0" text="目录名">
<cellValueFactory>
<TreeItemPropertyValueFactory property="fileName" />
</cellValueFactory>
</TreeTableColumn>
<TreeTableColumn prefWidth="75.0" text="总长度">
<cellValueFactory>
<TreeItemPropertyValueFactory property="totalLength" />
</cellValueFactory>
</TreeTableColumn>
xml和Controller中的一致:
<TreeTableView fx:id="fileStat" HBox.hgrow="ALWAYS">
@FXML private TreeTableView<FileTreeNode> fileStat;
组建逻辑部分树结构的时候采用的递归,那么渲染的时候也要采用递归
逻辑部分:
import java.io.File;
public class FileScanner {
//深序遍历
public static void scannerDirectory(FileTreeNode node) {
//获取当前目录的子目录或文件列表
File[] files = node.getFile().listFiles();//File的一个方法listFiles返回一个文件对象的数组
if (files == null) {
return;
}
//遍历子目录或者文件
for (File file : files) {
FileTreeNode child = new FileTreeNode();
child.setFile(file);
child.setFileName(file.getName());
if (file.isDirectory()) {
//继续递归子目录
scannerDirectory(child);
} else {
//计算文件大小
child.setTotalLength(file.length());
}
node.setTotalLength(node.getTotalLength() + child.getTotalLength());
node.addChildNode(child);
}
}
}
树表渲染部分:
//递归渲染 FileTreeNode转为TreeItem
private void fillTreeItem(FileTreeNode rootNode, TreeItem rootItem){
List<FileTreeNode> childs = rootNode.getChildrens();
for(FileTreeNode node:childs){
//树转换
TreeItem item = new TreeItem(node);
//如果孩子节点大于0,说明还有子目录
if(node.getChildrens().size()>0){
item.setGraphic(new ImageView(image));
}
rootItem.getChildren().add(item);
//递归
fillTreeItem(node,item);
}
}
对根节点进行渲染
//转换到主线程执行
Platform.runLater(
() ->{
fileStat.setRoot(rootItem);
}
);
注意:
设置setDaemon,当线程结束时,自动释放资源。
thread.setDaemon(true);//线程执行完毕自动释放资源
允许树自动展开
rootItem.setExpanded(true);//树展开
加载文件夹图标:
private final Image image = new Image(getClass().getClassLoader().getResourceAsStream("Folder.png"));
TreeItem的构造方法中允许插入图标
public TreeItem(final T value, final Node graphic)
设置根节点图标:
TreeItem rootItem = new TreeItem(rootNode, new ImageView(image));
将子树也设置文件夹图标:
//如果孩子节点大于0,说明还有子目录
if(node.getChildrens().size()>0){
item.setGraphic(new ImageView(image));
}
清空上一次选择的数据:
//在重新选择时,清空数据
fileStat.setRoot(null);
关掉定时器:
//Tab页没被选中时,关掉定时器TimerTask
if(timerTask!=null){
timerTask.cancel();
timerTask = null;
}
//程序退出时,退出timer线程
public void shutdown(){
if(timer !=null){
timer.cancel();
}
}
当请求关闭窗口时,调用shutdown方法
//当关闭窗口时,关闭timer线程
primaryStage.setOnCloseRequest((e) -> controller.shutdown());