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

