JAVA设计模式-大集合数据拆分

背景

我们在做软件开发时,经常会遇到把大集合的数据,拆分成子集合处理。例如批量数据插入数据库时,一次大约插入5000条数据比较合理,但是有时候待插入的数据远远大于5000条。这时候就需要进行数据拆分。数据拆分基本逻辑并不复杂,下面尝试把数据拆分逻辑封装一下。

拆分逻辑

拆分过程唯一要求就是数据不能遗漏,也不能重复处理。

  • 定义子集合大小
  • 遍历源数据集合,达到一个子集合大小,
  • 根据业务需要开始处理子集合数据
  • 直到处理完所有数据

代码

先实现基本功能代码

java 复制代码
	/**
     * @param dataList 原数据集合
     * @param subSize  子集合size
     * @throws Exception
     */
	public static <T> void processdSubData(List<T> dataList, int subSize) throws Exception {
      
      	//子集合对象
        List<T> subDataList = new ArrayList<>();
		//计数变量
        int count = 0;
        
        for (T t : dataList) {
			
            subDataList.add(t);
            count++;//累计子集合数据数量

            if (count >= subSize) {//这里可以使用等号==,个人习惯使用大于等于>=

                try {
                	//处理子集合数据
                    //doSomeThing(subDataList);
                } catch (Exception e) {
                    throw e;
                } finally {
                	//清空计数变量和子集合
                    count = 0;
                    subDataList.clear();
                }

            }
        }

		//这里的剩余数据处理,非常容易遗漏,这也是为什么要封装公共代码的一个原因
		//封装成公共代码后,就不用担心遗漏这一部分数据
        if (subDataList.size() > 0) {
            //最后一次剩余数据量小于subSize,这里再处理一次
            try {
                //处理子集合数据
                //doSomeThing(subDataList);
            } catch (Exception e) {
                throw e;
            }
        }
    }

以上的代码,逻辑清晰且没有复杂的索引计算,是个比较好的实现。但是代码没有通用性,每次遇到数据拆分,都要写一遍拆分呢逻辑,写的多了难免出问题。仔细看下代码,除了处理子集合数据的业务代码方法,其他代码都是一样的。下面改造一下,子集合数据的业务方法由外部传入。那么拆分逻辑部分就可以通用,不用担心出问题了。

新实现

  • 业务处理接口
java 复制代码
package cn.com.soulfox.common.functions.splitdata;

import java.util.List;

/**
 *
 * 子数据集合业务数据处理接口
 * @create 2024/6/24 10:21
 */
@FunctionalInterface//函数式接口,只有一个抽象方法
public interface SplitDataCallback<T> {

    void splitDataProcess(List<T> subDataList);
}
  • 拆分工具类
java 复制代码
package cn.com.soulfox.common.functions.splitdata;


import java.util.List;

/**
 * 大集合拆分处理
 *
 * 
 * @create 2024/6/24 10:35
 */
public class SplitDataListUtil {

    /**
     * @param dataList 待拆分数据集合
     * @param subSize  子集合的size
     * @param callback 子集合数据处理类
     * @throws Exception
     */
    public static <T> void processData(List<T> dataList, int subSize, SplitDataCallback<T> callback) throws Exception {
    	//如果不做成公共代码,下面的判空的代码,忙的时候就不会写了吧 -:)
        if (callback == null) {
            //处理类为空
            return;
        }
        if (dataList == null || dataList.isEmpty()) {
            //数据集合为空
            return;
        }
        if (subSize <= 0) {
            //子集长度小于等于 0
            return;
        }
		
        if (subSize >= dataList.size()) {
            //子集长度大于等于原集合,不需要拆分,直接处理
            try {
                callback.splitDataProcess(dataList);
            } catch (Exception e) {
                System.out.println("处理子数据集失败:"+e.getMessage());
                throw e;
            }
            return;
        }

        processdSubData(dataList, subSize, (SplitDataCallback<T>) callback);
    }

    /**
     * @param dataList 原数据集合
     * @param subSize  子集合size
     * @param callback 子集合数据处理类
     * @throws Exception
     */
    private static <T> void processdSubData(List<T> dataList, int subSize, SplitDataCallback<T> callback) throws Exception {
       //子集合对象
        List<T> subDataList = new ArrayList<>();
        int count = 0;
        for (T t : dataList) {

            subDataList.add(t);
            //计数
            count++;

            if (count >= subSize) {//这里可以使用等号==,个人习惯使用大于等于>=
				//数量达到subSize,做一次处理
                try {
                    callback.splitDataProcess(subDataList);
                } catch (Exception e) {
                    System.out.println("处理子数据集失败:"+e.getMessage());
                    throw e;
                } finally {
                	//清空计数变量和子集合
                    count = 0;
                    subDataList.clear();
                }

            }
        }

		//这里的剩余数据处理,非常容易遗漏,这也是为什么要封装公共代码的一个原因
		//封装成公共代码后,就不用担心遗漏这一部分数据
        if (subDataList.size() > 0) {
            //最后一次剩余数据量小于subSize,这里再处理一次
            try {
                callback.splitDataProcess(subDataList);
            } catch (Exception e) {
                System.out.println("处理子数据集失败:"+e.getMessage());
                throw e;
            }
        }
    }
}
  • 单元测试
java 复制代码
package cn.com.soulfox.common.functions.splitdata;

import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;

/**
 *
 * @create 2024/6/24 15:50
 */
public class SplitDataListUtilTest {

    private List<String> dataList;

    @Before
    public void setup(){
        //准备数据
        dataList = Arrays.asList("a","b","c","1","2");
    }

    @Test
    public void test(){
        //定义子集合size
        int subSize = 2;
        //业务逻辑比较简单, 可直接写业务代码
        try {
            SplitDataListUtil.processData(this.dataList, subSize,
                    (subDataList -> {
                        System.out.println("简单业务代码++++");
                        subDataList.forEach(data ->{
                            System.out.println("简单业务代码: "+data);
                        });
                    }));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 业务处理逻辑复杂
    实现类
java 复制代码
package cn.com.soulfox.common.functions.splitdata;

import java.util.List;

/**
 * 业务逻辑复杂
 * @create 2024/6/24 16:05
 */
public class ComplexBusinessImpl implements SplitDataCallback<String>{

    @Override
    public void splitDataProcess(List<String> subDataList) {
        System.out.println("复杂业务代码++++");
        subDataList.forEach(data ->{
            System.out.println("复杂业务代码: "+data);
        });
    }

}

加一个测试方法

java 复制代码
	@Test
    public void testComplexBusiness(){
        //定义子集合size
        int subSize = 2;
        //业务逻辑比较复杂, 创建接口实现类ComplexBusinessImpl 传入方法中
        ComplexBusinessImpl complexBusiness = new ComplexBusinessImpl();
        try {
            SplitDataListUtil.processData(this.dataList, subSize, complexBusiness);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

测试结果

总结一下。。。

拆分数据功能并不复杂,封装公共代码,也看不什么好处,实际开发的时候直接复制拆分代码即可。

这里主要是为了提出一种,设计通用功能的思路。任何功能,总有一部分结构性代码是不变的,变化的是业务处理代码。例如,上面的例子中,把大集合拆分成小集合的逻辑是不变的,变化的是数据处理逻辑。把不变的部分抽象出来封装成公共代码,同时把一些判空,边界数据做一下统一处理,这样就会在提高代码复用率的同时,减少出错几率。

相关推荐
u0104058362 分钟前
如何利用Java Stream API简化集合操作?
java·开发语言
G皮T4 分钟前
【MyBatis】MyBatis 理论 40 问(二)
java·数据库·spring boot·spring·mybatis·关系映射
小羊子说12 分钟前
Android 开发中 C++ 和Java 日志调试
android·java·c++
TechQuester17 分钟前
解决GPT-4o耗电难题!DeepMind新算法训练效率提升13倍,能耗降低10倍!
java·c++·人工智能·python·算法·chatgpt
球球King26 分钟前
工厂模式之简单工厂模式
java·jvm·简单工厂模式
yunhuibin27 分钟前
DP学习——简单工厂模式
设计模式·简单工厂模式
java小郭29 分钟前
设计模式之责任链模式
设计模式·责任链模式
续亮~32 分钟前
6、Redis系统-数据结构-06-跳表
java·数据结构·数据库·redis·后端·缓存
不决问春风39 分钟前
102.二叉树的层序遍历——二叉树专题复习
java·算法·leetcode
哎呦没40 分钟前
MOJO编程语言的编译与执行:深入编译器与解释器的工作原理
java·开发语言·mojo