You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX ListView高CPU占用问题咨询与优化方案寻求

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();
}

三、总结

  1. 先做单元格复用和数据逻辑优化,这是见效最快、实现成本最低的方案
  2. 如果性能仍不达标,再考虑重构为自定义日程视图
  3. 线程加载部分去掉冗余操作,提升加载速度

内容的提问来源于stack exchange,提问作者Fever_Dream

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 04:50:54