DiffUtil介绍
DiffUtil是Android中的一个实用工具类,用于计算并应用RecyclerView中数据集的更改。它可以高效地计算出两个数据集之间的差异,并且只更新发生变化的部分,从而避免不必要的刷新操作,提高了RecyclerView的性能和流畅度。
DiffUtil的主要作用是在数据集发生变化时,计算出新旧数据集之间的差异,并提供给RecyclerView.Adapter进行局部刷新。它通过计算出数据集的差异,可以精确地知道哪些项目需要插入、删除、移动或更新,从而避免了全局刷新,减少了不必要的UI更新操作。
在使用DiffUtil时,需要创建一个继承自DiffUtil.Callback的类,然后在其中实现两个方法:getOldListSize()和getNewListSize()用于返回旧数据集和新数据集的大小,以及areItemsTheSame()和areContentsTheSame()用于判断两个对象是否代表同一个item和内容是否相同。DiffUtil会根据这些方法的返回结果来计算出数据集的差异,并提供给RecyclerView.Adapter进行局部刷新。
总的来说,DiffUtil差量算法能够帮助开发者高效地处理RecyclerView数据集的更新,提升了列表的性能和用户体验。
DiffUtil使用
- areItemsTheSame(oldItemPosition: Int, newItemPosition: Int):用于判断两个项是否代表同一个对象。
- areContentsTheSame(oldItemPosition: Int, newItemPosition: Int):用于判断两个项的内容是否相同。
- getOldListSize():返回旧数据集的大小。
- getNewListSize():返回新数据集的大小。
创建一个继承自DiffUtil.ItemCallback的类,用于比较两个数据对象是否相同:
public class MyDiffCallback extends DiffUtil.ItemCallback {
@Override
public boolean areItemsTheSame(@NonNull MyDataModel oldItem, @NonNull MyDataModel newItem) {
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(@NonNull MyDataModel oldItem, @NonNull MyDataModel newItem) {
return oldItem.equals(newItem);
}
}
在你的Adapter中使用DiffUtil进行数据更新:
public class MyAdapter extends RecyclerView.Adapter {
private List dataList = new ArrayList();
// ... 其他方法
public void updateDataList(List newDataList) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new MyDiffCallback(dataList, newDataList));
dataList.clear();
dataList.addAll(newDataList);
diffResult.dispatchUpdatesTo(this);
}
// ... 其他方法
}
MyDiffCallback类用于比较两个数据对象是否相同,然后在Adapter的updateDataList方法中使用DiffUtil.calculateDiff方法计算出数据变化,并通过dispatchUpdatesTo方法应用到RecyclerView上。
当你调用updateDataList方法更新数据时,DiffUtil会帮你计算出数据的变化,并只更新发生变化的部分,而不是整个列表都进行刷新,从而提升了性能。
DiffUtil差量算法
DiffUtil中的Myers算法是一种用于比较两个序列差异的算法。它通常用于RecyclerView的数据更新中,以便有效地计算出两个列表之间的差异,并且只更新发生变化的部分。
Myers算法的核心思想是将两个序列的比较转化为一个图形化的问题,然后通过动态规划的方式来找到最小的编辑路径,从而确定两个序列之间的差异。
在DiffUtil中,Myers算法被用于计算出两个列表之间的差异,并生成用于更新RecyclerView的操作指令,比如插入、删除、移动等操作,以便实现高效的列表更新。
public class MyDiffUtilCallback extends DiffUtil.Callback {
private List oldList;
private List newList;
public MyDiffUtilCallback(List oldList, List newList) {
this.oldList = oldList;
this.newList = newList;
}
@Override
public int getOldListSize() {
return oldList.size();
}
@Override
public int getNewListSize() {
return newList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).getId() == newList.get(newItemPosition).getId();
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
MyItem oldItem = oldList.get(oldItemPosition);
MyItem newItem = newList.get(newItemPosition);
return oldItem.equals(newItem);
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
// 如果areContentsTheSame返回false,则可以在这里返回具体的变化内容,以便进行局部刷新
return super.getChangePayload(oldItemPosition, newItemPosition);
}
}
DiffUtil差量算法实现:
public class DiffUtil {
//部分代码省略
@NonNull
public static DiffResult calculateDiff(@NonNull Callback cb, boolean detectMoves) {
final int oldSize = cb.getOldListSize();
final int newSize = cb.getNewListSize();
final List snakes = new ArrayList();
// instead of a recursive implementation, we keep our own stack to avoid potential stack
// overflow exceptions
final List stack = new ArrayList();
stack.add(new Range(0, oldSize, 0, newSize));
final int max = oldSize + newSize + Math.abs(oldSize - newSize);
// allocate forward and backward k-lines. K lines are diagonal lines in the matrix. (see the
// paper for details)
// These arrays lines keep the max reachable position for each k-line.
final int[] forward = new int[max * 2];
final int[] backward = new int[max * 2];
// We pool the ranges to avoid allocations for each recursive call.
final List rangePool = new ArrayList();
while (!stack.isEmpty()) {
final Range range = stack.remove(stack.size() - 1);
final Snake snake = diffPartial(cb, range.oldListStart, range.oldListEnd,
range.newListStart, range.newListEnd, forward, backward, max);
if (snake != null) {
if (snake.size > 0) {
snakes.add(snake);
}
// offset the snake to convert its coordinates from the Range's area to global
//使路径点的偏移以将其坐标从范围区域转换为全局
snake.x += range.oldListStart;
snake.y += range.newListStart;
//拆分左上角和右下角进行递归
// add new ranges for left and right
final Range left = rangePool.isEmpty() ? new Range() : rangePool.remove(
rangePool.size() - 1);
//起点为上一次的起点
left.oldListStart = range.oldListStart;
left.newListStart = range.newListStart;
//如果是逆向得到的中间路径,那么左上角的终点为该中间路径的起点
if (snake.reverse) {
left.oldListEnd = snake.x;
left.newListEnd = snake.y;
} else {
if (snake.removal) {//中间路径是向右操作,那么终点的x需要退一
left.oldListEnd = snake.x - 1;
left.newListEnd = snake.y;
} else {//中间路径是向下操作,那么终点的y需要退一
left.oldListEnd = snake.x;
left.newListEnd = snake.y - 1;
}
}
stack.add(left);
// re-use range for right
//noinspection UnnecessaryLocalVariable
final Range right = range;//右下角终点和之前的终点相同
if (snake.reverse) {
if (snake.removal) {//中间路径是向右操作,那么起点的x需要进一
right.oldListStart = snake.x + snake.size + 1;
right.newListStart = snake.y + snake.size;
} else {//中间路径是向下操作,那么起点的y需要进一
right.oldListStart = snake.x + snake.size;
right.newListStart = snake.y + snake.size + 1;
}
} else {//如果是逆向得到的中间路径,那么右下角的起点为该中间路径的终点
right.oldListStart = snake.x + snake.size;
right.newListStart = snake.y + snake.size;
}
stack.add(right);
} else {
rangePool.add(range);
}
}
// sort snakes
Collections.sort(snakes, SNAKE_COMPARATOR);
return new DiffResult(cb, snakes, forward, backward, detectMoves);
}
//diffPartial方法主要是来寻找一条snake,它的核心也就是Myers算法。
private static Snake diffPartial(Callback cb, int startOld, int endOld,
int startNew, int endNew, int[] forward, int[] backward, int kOffset) {
final int oldSize = endOld - startOld;
final int newSize = endNew - startNew;
if (endOld - startOld < 1 || endNew - startNew 0
&& cb.areItemsTheSame(startOld + x - 1, startNew + y - 1)) {
x--;
y--;
}
backward[kOffset + backwardK] = x;
//如果delta为偶数,那么相连通的节点一定是反向移动的节点,也就是执行backward操作所触发的节点
//if delta is even and ( k >= -d - delta and k = -d && k + delta = backward[kOffset + backwardK]) {
Snake outSnake = new Snake();
outSnake.x = backward[kOffset + backwardK];
outSnake.y = outSnake.x - backwardK;
outSnake.size =
forward[kOffset + backwardK] - backward[kOffset + backwardK];
outSnake.removal = removal;
outSnake.reverse = true;
return outSnake;
}
}
}
}
throw new IllegalStateException("DiffUtil hit an unexpected case while trying to calculate"
+ " the optimal path. Please make sure your data is not changing during the"
+ " diff calculation.");
}
//部分代码省略
}
Myers差分算法是一种在每一步选择中都采取当前状态下最优决策的算法。在软件测试中,Myers差分算法使用了差分算法来寻找两个文本之间的最小差异集合。该算法通过比较两个文本的不同版本,找出它们之间的差异,并生成一个表示差异的最小集合。
Myers差分算法会尽可能地选择最长的公共子序列,以便最小化差异集合的大小。这种方法在实际应用中通常能够产生较好的结果,尽管并不一定能够找到最优解。