JavaFX ListView高CPU占用问题咨询与优化方案寻求
各位开发者好,我想咨询JavaFX中ListView导致高CPU占用是否属于正常情况?
我正在开发一款学校日程管理系统,布局结构为:一个VBox承载周日至周六的每日布局,每个每日布局包含一个Room类型的ListView;每个Room布局包含一个标签和一个Event类型的ListView,单个房间的日程覆盖7:00至20:00,事件可为空或包含课程。
目前高CPU占用大概率是因ListView加载数据量过大:每个Room的ListView需渲染15小时的事件布局,该结构会随房间数量、星期天数重复渲染。
我尝试过懒加载,虽有一定效果但速度过慢,希望获得简单高效的优化方案,或更高效的日程展示实现思路。
相关代码片段
每周布局加载方法
public void get_week_days(){ Platform.runLater(() -> { Task<Boolean> load_calendar=new Task<Boolean>() { @Override protected Boolean call() throws Exception { try{ FXMLLoader loader=new FXMLLoader(getClass().getResource("/Calendar/cells_designs/day_layout.fxml")); HBox layout=loader.load(); DayLayout_controller dayLayoutController=loader.getController(); dayLayoutController.set_day(new Calendar_day(week_day_name,i,month,null)); dayLayoutController=null; try { Thread.sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); } Platform.runLater(() -> { day_list.getChildren().add(layout); }); }catch (Exception e){ e.printStackTrace(); } return null; } }; new Thread(load_calendar).start(); }); }
房间列表单元格实现
public class Calendar_rooms_cell extends ListCell<Rooms> { private HBox layout; private Room_layout_controller roomLayoutController; private String room_day; private int month_day; private int month; public Calendar_rooms_cell(Calendar_day room_day){ FXMLLoader loader = new FXMLLoader(getClass().getResource("/Calendar/cells_designs/Room_layout.fxml")); try { layout = loader.load(); roomLayoutController = loader.getController(); this.room_day = room_day.getDay(); this.month_day = room_day.getMonth_day(); this.month = room_day.getMonth(); } catch (Exception e) { e.printStackTrace(); } } @Override protected void updateItem(Rooms room, boolean empty) { super.updateItem(room, empty); if(room==null || empty){ setGraphic(null); setText(null); } else{ roomLayoutController.set_room_name(room.getRoom_name(),room_day,month_day,month); setGraphic(layout); setText(null); } } }
事件列表单元格实现
public class Event_cell extends ListCell<Event> { private StackPane layout; private FXMLLoader loader; private Calendar_event_layout_controller calendarEventLayoutController; private Calendar_shared calendarSharedData; private Calendar_menu_controller menu; public Event_cell() { try{ loader=new FXMLLoader(getClass().getResource("/Calendar/cells_designs/Calendar_event_layout.fxml")); layout=loader.load(); calendarEventLayoutController=loader.getController(); calendarSharedData=Calendar_shared.get_data(); menu=calendarSharedData.getCalendarMenuController(); }catch (Exception e){ e.printStackTrace(); } } @Override protected void updateItem(Event event, boolean b) { super.updateItem(event, b); if(event==null || b){ setGraphic(null); setText(null); }else{ if(event.getClass()== Private_lessons.class){ calendarEventLayoutController.set_up_private_lessons((Private_lessons)event); } else if(event.getClass()== Formation_event.class){ calendarEventLayoutController.set_up_formation_events((Formation_event) event); } else{ calendarEventLayoutController.set_up_empty_events(event); } setGraphic(layout); setText(null); } } }
房间事件初始化方法
public void set_room_name(String room_name,String room_day,int month_day,int month){ ObservableList<Event> filtered_events= FXCollections.observableArrayList(); room_events.setItems(filtered_events); room_events.getItems().clear(); this.room_name.setText(room_name); for(LocalTime time=LocalTime.of(7,00) ; time.compareTo(LocalTime.of(20,00))!=0 ;time=time.plusMinutes(30)){ LocalTime finalTime = time; Optional<Event> events_exists = null; FilteredList<Event> events = null; if(all_events!=null && !all_events.isEmpty()){ events= new FilteredList<>(all_events,event -> event.getRoom().equals(room_name) && event.getEvent_day().equals(room_day) && event.getEvent_start().compareTo(finalTime)==0); events_exists=events.stream().findAny(); } if(events_exists!=null){ if(events_exists.isPresent()){ for(Event event: events.stream().toList()){ if(event.getClass()== Private_lessons.class){ Private_lessons privateLessons=(Private_lessons)event; if(privateLessons.getEvent_date()==null){ filtered_events.add(privateLessons); } else{ if(month_day==privateLessons.getEvent_date().getDayOfMonth() && month==privateLessons.getEvent_date().getMonthValue()){ filtered_events.add(privateLessons); } } } else if(event.getClass()== Formation_event.class){ filtered_events.add(event); } time=event.getEvent_end().minusMinutes(30); } } else{ Event event=new Event(time,time.plusMinutes(30),room_day,room_name); filtered_events.add(event); } } } room_events.setCellFactory(eventListView -> { return new Event_cell(); }); }
布局层级:7个星期父节点,每个节点含Room的ListView,每个Room节点含Event的ListView(对应时间段)。我尝试过懒加载,虽能生效但速度过慢。
优化方案与实现思路
一、核心问题定位
高CPU占用绝对不是正常情况,核心问题出在三个点:
- 多层ListView嵌套导致渲染量指数级上升,7天×N个房间×20+事件的组合会让CPU持续高负载
- 单元格构造时直接加载FXML,无复用机制,每次创建单元格都要解析FXML,IO和初始化开销极大
- 数据筛选逻辑重复创建FilteredList、频繁执行Stream操作,CPU消耗严重
二、针对性优化方案
1. 单元格复用与FXML懒加载
不要在单元格构造方法中提前加载FXML,改为在updateItem中懒加载并复用布局,避免重复初始化:
public class Event_cell extends ListCell<Event> { private StackPane layout; private Calendar_event_layout_controller calendarEventLayoutController; private Calendar_shared calendarSharedData; private Calendar_menu_controller menu; // 懒加载布局,仅在需要时初始化 private void loadLayout() { try { FXMLLoader loader = new FXMLLoader(getClass().getResource("/Calendar/cells_designs/Calendar_event_layout.fxml")); layout = loader.load(); calendarEventLayoutController = loader.getController(); calendarSharedData = Calendar_shared.get_data(); menu = calendarSharedData.getCalendarMenuController(); } catch (Exception e) { e.printStackTrace(); } } @Override protected void updateItem(Event event, boolean empty) { super.updateItem(event, empty); if (event == null || empty) { setGraphic(null); return; } // 仅在布局未初始化时加载 if (layout == null) { loadLayout(); } // 更新事件内容 if (event instanceof Private_lessons) { calendarEventLayoutController.set_up_private_lessons((Private_lessons) event); } else if (event instanceof Formation_event) { calendarEventLayoutController.set_up_formation_events((Formation_event) event); } else { calendarEventLayoutController.set_up_empty_events(event); } setGraphic(layout); } }
同理修改Calendar_rooms_cell,将FXML加载逻辑改为懒加载,减少不必要的初始化开销。
2. 数据处理逻辑优化
提前预筛选数据,避免循环内重复执行过滤操作:
public void set_room_name(String room_name, String room_day, int month_day, int month) { ObservableList<Event> filtered_events = FXCollections.observableArrayList(); this.room_name.setText(room_name); // 预筛选当前房间+当天的有效事件 List<Event> roomDayEvents = new ArrayList<>(); if (all_events != null && !all_events.isEmpty()) { for (Event event : all_events) { if (!event.getRoom().equals(room_name) || !event.getEvent_day().equals(room_day)) { continue; } // 过滤Private_lessons的日期条件 if (event instanceof Private_lessons) { Private_lessons pl = (Private_lessons) event; if (pl.getEvent_date() != null && (pl.getEvent_date().getDayOfMonth() != month_day || pl.getEvent_date().getMonthValue() != month)) { continue; } } roomDayEvents.add(event); } } // 遍历时间段匹配事件 LocalTime time = LocalTime.of(7, 0); LocalTime endTime = LocalTime.of(20, 0); while (time.isBefore(endTime)) { LocalTime finalTime = time; // 查找当前时间段开始的事件 Optional<Event> matchedEvent = roomDayEvents.stream() .filter(e -> e.getEvent_start().equals(finalTime)) .findFirst(); if (matchedEvent.isPresent()) { Event event = matchedEvent.get(); filtered_events.add(event); // 跳过事件覆盖的时间段 time = event.getEvent_end().minusMinutes(30); } else { filtered_events.add(new Event(time, time.plusMinutes(30), room_day, room_name)); } time = time.plusMinutes(30); } room_events.setItems(filtered_events); // 仅初始化一次cellFactory,避免重复创建 if (room_events.getCellFactory() == null) { room_events.setCellFactory(eventListView -> new Event_cell()); } }
关键改进:一次性过滤出有效事件,减少循环内的重复判断;cellFactory仅初始化一次。
3. 布局结构重构(高效日程展示思路)
多层ListView嵌套是性能瓶颈根源,建议替换为自定义单层级日程视图:
- 用
ScrollPane承载整个周视图,左侧绘制房间名称,顶部绘制时间轴(7:00-20:00) - 直接计算每个事件的位置(x对应时间,y对应房间),用
Rectangle或自定义控件绘制事件块 - 仅渲染可视区域内的内容,滚动时动态更新可见元素
这种方式彻底避免ListView的单元格复用开销,直接控制渲染范围,CPU占用会大幅降低。
4. 线程加载优化
去掉不必要的Thread.sleep和嵌套Platform.runLater,用Task的回调更新UI:
public void get_week_days() { Task<HBox> loadCalendarTask = new Task<>() { @Override protected HBox call() throws Exception { FXMLLoader loader = new FXMLLoader(getClass().getResource("/Calendar/cells_designs/day_layout.fxml")); HBox layout = loader.load(); DayLayout_controller dayLayoutController = loader.getController(); dayLayoutController.set_day(new Calendar_day(week_day_name, i, month, null)); return layout; } }; loadCalendarTask.setOnSucceeded(event -> { day_list.getChildren().add(loadCalendarTask.getValue()); }); loadCalendarTask.setOnFailed(event -> { loadCalendarTask.getException().printStackTrace(); }); new Thread(loadCalendarTask).start(); }
三、总结
- 先做单元格复用和数据逻辑优化,这是见效最快、实现成本最低的方案
- 如果性能仍不达标,再考虑重构为自定义日程视图
- 线程加载部分去掉冗余操作,提升加载速度
内容的提问来源于stack exchange,提问作者Fever_Dream

