技术背景
在日常开发中,我们常遇到将一个List
列表分割成多个的场景,List
提供了subList()
方法避免开发者重复造轮子。
subList()
的用法
ArrayList
类是接口List
的一个实现,以下subList()
使用方法参考示例来自ArrayList
。
List<String> arrayList = new ArrayList<>();
arrayList.add("hello");
arrayList.add("hello1");
arrayList.add("hello2");
arrayList.add("hello3");
List<String> subString = arrayList.subList(0, 1);
System.out.println(subString);
输出: [hello]
subList
实现
ArrayList
的subList()
源码如下:
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, offset, fromIndex, toIndex);
}
SubList
类是ArrayList
的一个内部类,它继承自AbstractList
抽象类,在SubList
的构造方法中,入参有原始list的引用,SubList
类的get方法源码如下:
//ArrayList的原始数组
transient Object[] elementData;
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
可以看到,SubList
的get()
方法是通过下标来获取原数组的数据,而不是返回一个新的对象,当代码中有对分割后的列表访问时,便是对原ArrayList
的引用,导致该对象不会被GC
回收,数据量大时,有导致OOM
的风险。因此,我们需要找到新的方案去解决代码中的风险点。
解决方案
使用Stream的方式分割。
通过skip()
方法获取某个元素节点之后的数据
代码语言:javascript复制//获取第2个节点后的数据(包含第2个元素)
List<String> skipList = arrayList.stream().skip(1).collect(Collectors.toList());
输出: [hello2, hello3]
通过limit()方法获取某个元素节点之前的数据
代码语言:javascript复制//获取第2个节点前的数据
List<String> limitList = arrayList.stream().limit(1).collect(Collectors.toList());
输出: [hello]
其他解决方案
- guava的
Lists.partition()
- apache的
ListUtils.partition()
- 通过List的构造方法
List<String> originalList = new ArrayList<>();
// add elements to originalList
// create a new list with the same elements as originalList
List<String> newList = new ArrayList<>(originalList.subList(startIndex, endIndex));
详细方案请参考:
https://juejin.cn/post/7029519771670413325