别再自己用subList做数组分片啦,试试这个数组分片利器

1,001 阅读3分钟

前言

Hi,大家好,我是你们的秃头朋友程序员小甲,今天这篇文章主要是来和大家聊一聊List数据分片处理操作的话题。小甲之前在对接巨量广告主api时,由于巨量api传参限制带过去的广告主id数,因此需要对广告主列表进行切割操作,当时小甲刚毕业不久,一看到这需求不管三七二十一直接就是自己工具类封装subList切割方法,封装的过程中还自己沾沾窃喜(又给公司造了一个通用的好轮子,同事该怎么夸我呢?)。但是到了功能提测阶段的代码Review阶段时,小甲被组长狠狠的打了一波脸,说我重复造轮子啦,可以用...来实现,接下来就让小甲和各位分享下组长的装13现场吧。

8d4bead515c794dc07daf358a130329.png

功能实现

引入Guava包

通过guava包的Lists.partion(List records,Integer size)API来对我们查询后的数据进行切割分片,以下是guava该API的底层源码 ` ``` public static List<List> partition(List list, int size) { checkNotNull(list); checkArgument(size > 0); return (list instanceof RandomAccess) ? new RandomAccessPartition(list, size) : new Partition(list, size); }

private static class Partition extends AbstractList<List> { final List list; final int size;

Partition(List<T> list, int size) {
  this.list = list;
  this.size = size;
}
// 映射到底层subList
@Override public List<T> get(int index) {
  checkElementIndex(index, size());
  int start = index * size;
  int end = Math.min(start + size, list.size());
  return list.subList(start, end);
}
// 根据分片大小得到最终的分片个数
@Override public int size() {
  return IntMath.divide(list.size(), size, RoundingMode.CEILING);
}

@Override public boolean isEmpty() {
  return list.isEmpty();
}

}

// 实现同上,但是声明实现 RandomAccess, 最终体现到get方法的不同 private static class RandomAccessPartition extends Partition implements RandomAccess { RandomAccessPartition(List list, int size) { super(list, size); } }


最终的关键操作在于subList:

// java.util.AbstractList public List subList(int fromIndex, int toIndex) { return (this instanceof RandomAccess ? new RandomAccessSubList<>(this, fromIndex, toIndex) : new SubList<>(this, fromIndex, toIndex)); }


SubList 的逻辑同样是在原有的 List 上包装一层抽象:

class SubList extends AbstractList { private final AbstractList l; private final int offset; private int size;

SubList(AbstractList<E> list, int fromIndex, int toIndex) {
    if (fromIndex < 0)
        throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
    if (toIndex > list.size())
        throw new IndexOutOfBoundsException("toIndex = " + toIndex);
    if (fromIndex > toIndex)
        throw new IllegalArgumentException("fromIndex(" + fromIndex +
                                           ") > toIndex(" + toIndex + ")");
    l = list;
    offset = fromIndex;
    size = toIndex - fromIndex;
    this.modCount = l.modCount;
}

public E set(int index, E element) {
    rangeCheck(index);
    checkForComodification();
    return l.set(index+offset, element);
}

public E get(int index) {
    rangeCheck(index);
    checkForComodification();
    return l.get(index+offset);
}

public int size() {
    checkForComodification();
    return size;
}

public void add(int index, E element) {
    rangeCheckForAdd(index);
    checkForComodification();
    l.add(index+offset, element);
    this.modCount = l.modCount;
    size++;
}

public E remove(int index) {
    rangeCheck(index);
    checkForComodification();
    E result = l.remove(index+offset);
    this.modCount = l.modCount;
    size--;
    return result;
}

protected void removeRange(int fromIndex, int toIndex) {
    checkForComodification();
    l.removeRange(fromIndex+offset, toIndex+offset);
    this.modCount = l.modCount;
    size -= (toIndex-fromIndex);
}

public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}

public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);
    int cSize = c.size();
    if (cSize==0)
        return false;

    checkForComodification();
    l.addAll(offset+index, c);
    this.modCount = l.modCount;
    size += cSize;
    return true;
}

public List<E> subList(int fromIndex, int toIndex) {
    return new SubList<>(this, fromIndex, toIndex);
}

private void rangeCheck(int index) {
    if (index < 0 || index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 只能修改该子列表
private void rangeCheckForAdd(int index) {
    if (index < 0 || index > size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

private String outOfBoundsMsg(int index) {
    return "Index: "+index+", Size: "+size;
}

private void checkForComodification() {
    if (this.modCount != l.modCount)
        throw new ConcurrentModificationException();
}

}

class RandomAccessSubList extends SubList implements RandomAccess { RandomAccessSubList(AbstractList list, int fromIndex, int toIndex) { super(list, fromIndex, toIndex); }

public List<E> subList(int fromIndex, int toIndex) {
    return new RandomAccessSubList<>(this, fromIndex, toIndex);
}

}


在 modCount 一致的情况下,对 SubList 的操作最终都会体现在底层的 List 上。这里通过get方法可以看到是否实现 RandomAccess 接口的区别。

ArrayList 获取某个位置的元素:

// 直接存取 public E get(int index) { rangeCheck(index);

return elementData(index);

}

E elementData(int index) { return (E) elementData[index]; }

/**

  • The array buffer into which the elements of the ArrayList are stored.
  • The capacity of the ArrayList is the length of this array buffer. Any
  • empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
  • will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access

LinkedList 获取某个位置的元素:

public E get(int index) { checkElementIndex(index); return node(index).item; } // 遍历链表,从最近一端开始遍历 Node node(int index) { // assert isElementIndex(index);

if (index < (size >> 1)) {
    Node<E> x = first;
    for (int i = 0; i < index; i++)
        x = x.next;
    return x;
} else {
    Node<E> x = last;
    for (int i = size - 1; i > index; i--)
        x = x.prev;
    return x;
}

} 从源码中我们也能看出来,该API也是通过subList来对数组进行操作,但在平时的业务迭代开发过程中,已有的现成稳定轮子自然会比我们自己封装的要稳定;

Lists.partion注意事项

通过源码我们能看出来最后返回的数据是一个不可操作的ArrayList,所以如果我们还需要分片完后的数组进行操作时,需要自行再开辟可操作数据进行数据暂存和操作,不然都会往外抛出modifyUnSupportException异常哟;

碎碎念时光

这期的文章主打的就是一个轻松诙谐,下期小甲将会为大家带来之前小甲公司弹幕系统的架构搭建和研发设计思路,请各位小伙伴多多关注哟,希望小甲分享的文章能对各位小伙伴产生一定的帮助,希望小甲能和各位不断的提升自身撸码实力,最后麻烦各位小甲的好朋友们给小甲点点赞和收藏文章哈,万分感谢~

1683049905220.png