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

GWT快速排序添加延迟计时器,解决GUI仅显示最终状态问题

解决GWT快速排序GUI实时更新问题

我明白你的困扰——日志能看到每一步数组变化,但GUI只显示最终结果,这是因为GWT的UI更新机制在同步执行的代码里会被“合并”,浏览器要等当前所有代码跑完才会重绘界面,之前的setText操作都被覆盖了。要实现每步延迟1秒展示,得用GWT的Timer把排序拆成异步分步执行,给浏览器留足重绘时间。

问题根源拆解

你的快排是同步执行的,所有sortedResult.setText()调用都挤在同一个浏览器事件循环任务里。浏览器有个机制:在当前JS执行栈清空前,不会处理UI重绘请求。所以哪怕你调用了N次setText,浏览器只会在整个排序完成后,把最后一次的结果渲染出来,中间的更新都被忽略了。

解决方案:异步分步排序

我们需要把排序过程拆成一个个小任务,用Timer每隔1秒调度一个任务执行,这样每一步的UI更新都能被浏览器及时渲染。具体步骤如下:

1. 保存排序状态的成员变量

首先在你的类里定义成员变量,用来保存排序过程中的状态(避免递归/同步执行的状态丢失):

private int[] sortArray; // 待排序的数组(克隆原数组,避免修改源数据)
private Queue<int[]> sortTasks; // 待处理的子数组区间队列,存储[first, last]
private Timer sortTimer;
private Label sortedResult;
private Logger logger = Logger.getLogger(getClass().getName());

2. 启动排序的入口方法

初始化状态,把初始排序任务加入队列,启动第一个Timer任务:

public void startVisualQuickSort(int[] sourceArray) {
    // 克隆原数组,避免修改用户传入的原始数据
    sortArray = sourceArray.clone();
    // 初始化任务队列,先加入整个数组的排序任务
    sortTasks = new LinkedList<>();
    sortTasks.add(new int[]{0, sortArray.length - 1});
    
    // 先显示初始数组状态
    updateUI();
    // 初始化Timer
    sortTimer = new Timer() {
        @Override
        public void run() {
            executeNextSortTask();
        }
    };
    // 立即执行第一个任务,之后每步延迟1秒
    sortTimer.schedule(0);
}

3. 分步执行排序任务

每次从队列里取出一个子数组任务,执行partition操作,完成后把左右子数组任务加入队列,再调度下一次执行:

private void executeNextSortTask() {
    if (sortTasks.isEmpty()) {
        // 所有任务完成,取消Timer
        sortTimer.cancel();
        logger.log(Level.SEVERE, "排序完成!");
        return;
    }
    
    // 取出下一个要处理的子数组区间
    int[] task = sortTasks.poll();
    int first = task[0];
    int last = task[1];
    
    if (first < last) {
        // 执行partition操作,完成后更新UI
        int pivotIndex = performPartition(first, last);
        updateUI();
        
        // 把左右子数组的排序任务加入队列(注意顺序,保证左子数组先处理)
        sortTasks.add(new int[]{pivotIndex + 1, last});
        sortTasks.add(new int[]{first, pivotIndex - 1});
        
        // 延迟1秒执行下一个任务
        sortTimer.schedule(1000);
    } else {
        // 子数组只有一个元素,直接执行下一个任务
        sortTimer.schedule(0);
    }
}

4. 实现分步Partition(可选:每次交换后也延迟)

如果需要每次交换数组元素后都展示状态,那得把Partition也拆成分步执行(把up、down、pivot等状态也存为成员变量)。下面是简化版的Partition(如果只需要每次Partition完成后展示,直接用这个即可):

private int performPartition(int first, int last) {
    int pivot = sortArray[first];
    int up = first;
    int down = last;
    
    do {
        while (up < last && pivot >= sortArray[up]) {
            up++;
        }
        while (pivot < sortArray[down]) {
            down--;
        }
        if (up < down) {
            // 交换元素
            swap(sortArray, up, down);
            // 如果要每次交换后都更新UI并延迟,这里需要暂停Partition,用Timer继续执行
            // 这种情况需要把up、down、pivot、currentFirst、currentLast存为成员变量
            // 这里先按Partition完成后更新来处理
        }
    } while (up < down);
    
    // 交换pivot到正确位置
    swap(sortArray, first, down);
    return down;
}

// 辅助方法:交换数组元素
private void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

// 辅助方法:更新UI和日志
private void updateUI() {
    String arrayStr = arrayToString(sortArray);
    sortedResult.setText(arrayStr);
    logger.log(Level.SEVERE, arrayStr);
}

// 你的array_to_string方法,改成符合Java命名规范的版本
private String arrayToString(int[] arr) {
    StringBuilder sb = new StringBuilder();
    for (int num : arr) {
        sb.append(num).append(" ");
    }
    return sb.toString().trim();
}

关键注意事项

  • 一定要克隆原数组:避免排序过程中修改用户传入的原始数据,导致意外问题。
  • 及时取消Timer:排序完成后调用sortTimer.cancel(),防止Timer持续触发造成内存泄漏。
  • 任务队列的顺序:如果需要和标准快排的顺序一致,注意子数组任务的加入顺序(左子数组优先的话,要把右子数组先加入队列,因为队列是FIFO)。

这样修改后,每完成一次Partition(或每次交换,按需调整),UI都会更新,并且延迟1秒后执行下一步,用户就能清晰看到排序的每一步过程了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:22:29