2019-01-08 17:20:08 u014629640 阅读数 2412


记一次大数据量处理案例(运用多线程完成数据快速处理)-----上(事件渊源)
中已经详细介绍了这次二次重构的起因和模块大致功能,
本次重构可分为:

1.数据库表重新设计以及业务逻辑重构

数据表重新设计

首先一改原有的数据结构表,将数据由一条电文对应生成N条数据修改为一条电文永远只生成一条数据,通过配置表进行字段翻译.字段含义在配置表中体现,其类似于有如下两个不同电文:

电文1:{
msgId:'XGF001',
name:'张三',
sex:'男',
phone:'138888888'
},
电文2:{
msgId:'PGF001',
userName:'李四',
userSex:'女',
userPhone:'139999999'
}

电文1为A公司发送电文,电文2为B公司发送电文 其电文号不同,字段名也不同,通过配置对照关系将其存储在新的数据承接表中,

数据承接表结构如下:

字段名 类型 注释
ID NUMBER 数据ID
MSG_ID VARHCAR 电文ID
QIP001 VARHCAR 字段1
QIP002 VARHCAR 字段2
QIP003 VARHCAR 字段3
QIO004 VARCHAR 字段4

在配置表中就有数据:

数据ID 电文号 字段名称 字段含义 对应电文字段
1 XGF001 QIP001 姓名 name
2 XGF001 QIP002 性别 sex
3 XGF001 QIP003 电话号码 phone
4 PGF001 QIP001 姓名 userName
5 PGF001 QIP002 性别 userSex
6 PGF001 QIP003 电话号码 userPhone

通过配置表配置字段信息 在获取到电文之后 加载配置信息,利用反射原理即可将电文数据转换为数据承接表中的数据,在打印PDF模块中亦只需要读取对应的一条数据和配置表即可轻松完成原有的打印.

业务逻辑重构

因该接口需要对接不同厂家的电文数据,电文来源不一,数据结构各不相同,所以针对不同的数据结构有不同的业务逻辑,但其所有电文都会经过:

接收电文
加载配置表信息
翻译数据
A公司数据生成逻辑
B公司数据生成逻辑
..其它公司的数据可能会有其它的生成逻辑

每一次接收电文都需要去加载配置表,并翻译数据,所以将这部分操作解耦,与数据生成的业务逻辑分开.
在配置表中数据改动频率非常非常小,所以配置中心的配置数据通过redis缓存,尽量减少对后台数据库的重复查询.
在业务逻辑部分对业务代码进行了优化为了方便以后可能针对CDEFG等更多的公司数据接入在业务处理部分灵活运用了模板方法模式
最后整个模块的结构如下:
在这里插入图片描述

在业务部分的重构大体就如上所说,具体的代码就不贴了,也没啥技术难度,感兴趣的小伙伴可down下相关模块直接阅读代码可能更直观点.

本次重构涉及到的历史数据割接才是这次重构的重点以及难点.下面将详细阐述这次历史数据处理的具体流程.

2.历史数据割接迁移

在完成本次业务模块重构之后,因为在接口开发时就对原始电文做了保存,所以处理历史数据时不需要对那冗余度非常高的2个亿的数据进行再次转译,直接通过读取原始电文调用对外接口即可.
但通过测试用例发现,单线程的情况下,循环读取效率极其低下,单线程情况下一分钟大约只能够生成60-80条数据,而原始电文有两百多万条,如此低下的处理效率作为开发人员是绝对不能允许的,必须得上多线程!

针对本次数据迁移,最终决定将电文读取与数据生成进行解耦,并分别启用多线程

将电文读取看做 producer
将数据生成看做 consumer
producer每次都将读取到的数据缓存到redis,然后consumer每次从redis上取数据

producer 实现代码如下:
注:因其它原因只展示大致实现思路,部分地方为伪代码实现

/**
 * 电文读取类
 * @author whz
 *
 */
public class Producer implements Callable<String> {
	public static final String DATA_KEY = "quality:qualityList";
	public String call() throws Exception {
		Jedis jedis = initJedis();//初始化jedis连接
		while (true) {
			//获取未处理数据的总数
			Long allSize = jedis.llen(DATA_KEY);
			//当redis累积到超过1万条数据未处理时,让线程等待 每3秒轮询一次
			if (allSize >10000) {
				System.err.println(Thread.currentThread().getName()+"----redis数据超过1万条,现场休眠等待消费者处理....");
				Thread.sleep(30000L);
				continue;
			}
			//充分利用redis单线程,操作原子性的特点,缓存分页页数,保证每次查询的准确,避免了多线程情况下的脏读情况
			Long incr = jedis.incr("count");//分页语句的 页数
			/*
			selectList() 为分页查询 每次查询1000条
			*/
			List<QualityData> ms =  selectList(1000,incr);
			System.out.println(Thread.currentThread().getName()+"查出数据:"+ms.size()+"  正在推送redis...");
			for (int j = 0; j < ms.size(); j++) {
				// 数据推送redis
				jedis.rpush(DATA_KEY,com.alibaba.fastjson.JSONArray.toJSONString(ms.get(j)));
			}
			dataSize = dataSize + ms.size();
			if (MyUtils.isEmpty(ms)) {
				break;
			}
			jedis.close();
			return "线程"+Thread.currentThread().getName()+"共查询出数据:"+dataSize;
		}
	}
}

/**
 * 多线程读取数据
 */
public void  produceData () {
		//获取CPU线程数 根据机器配置灵活配置线程数
		int cpuPoolSize = Runtime.getRuntime().availableProcessors();
		ExecutorService newFixedThreadPool= Executors.newFixedThreadPool(cpuPoolSize );
		List<Future<String>> result = new ArrayList<Future<String>>();
		for (int i = 0;i < cpuPoolSize ;i++) {
			Future<String> future = newFixedThreadPool.submit(producer);
			result.add(future);
		}
		for (Future<String> future : result) {
			try {
				System.out.println(future.get());
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (ExecutionException e) {
				e.printStackTrace();
			}
		}
		newFixedThreadPool.shutdown();


}

producer设计的核心之处就是利用redis的特性保证每个线程每次进行分页查询的时候读取到的数据不会出现脏读,
多线程这里采用newFixedThreadPool定长线程池是为了保证线程不会因为闲置超时而被销毁.

consumer实现代码如下:

/**
 * 数据生成
 * @author whz
 *
 */
public class QualityConsumer  implements  Callable<String>{

	
	
	private Logger logger = Logger.getLogger("quality");

	private List<Quality> messages;
	
	@Override
	public String call() throws Exception {
		Long bt = System.currentTimeMillis();
		Jedis jedis = initJedis();
		Integer countSize = 0;
		Long beginTime = System.currentTimeMillis();
		String threadName = Thread.currentThread().getName();
		QualityVoucherMessage me = null;
		while (true) {
		    //通过jedis提供的blpop方法采用阻塞式读取数据,即仅队列里有数据时才读取,如果producer还未将数据推送过来则阻塞,该方法也可用于简易的消息队列实现.
			List<String> list = jedis.blpop(60*60*10, QualityProducer.DATA_KEY);
			if (MyUtils.isEmpty(list)) {
				continue;
			}
			try {
				me = JSONArray.parseObject(list.get(1), Quality.class);
				//加载配置中心,选择数据处理模板
				QualityTemplateMethodService operater = factory.createQualityFactory(me);	
				//执行数据生成逻辑
				operater.createQuality(me.getId().toString());
				countSize = countSize + 1;
			}
			catch (Exception e) {
				//保存处理错误的数据....
				jedis.lpush("errorData",com.alibaba.fastjson.JSONArray.toJSONString(me));
				logger.error(e.getMessage(), e);
				e.printStackTrace();
			}
			//每十分钟记录一次每个线程处理的数据总量
			if ((System.currentTimeMillis() - beginTime)/1000 > 600) {
				logger.info(threadName+"已生成数据:"+countSize);
				//重置起始时间
				beginTime = System.currentTimeMillis();
			}
		}
		jedis.close();
		return threadName+"处理完毕,耗时:"+((System.currentTimeMillis()-bt)/1000+"共处理数据:"+countSize);
	}
		
}

	/**
	多线程生成数据
	*/
	public void createQualityInfoJob () {
		int cpuPoolSize = Runtime.getRuntime().availableProcessors();
		ExecutorService newFixedThreadPool= Executors.newFixedThreadPool(cpuPoolSize);
		List<Future<String>> result = new ArrayList<Future<String>>();
		for (int i = 0;i<cpuPoolSize+1;i++) {
			Future<String> future = newCachedThreadPool.submit(consumer);
			result.add(future);
		}
		for (Future<String> future : result) {
			try {
				logger.info(future.get());
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (ExecutionException e) {
				e.printStackTrace();
			}
		}
		newFixedThreadPool.shutdown();

	}

consumer设计的核心之处就是利用redis的阻塞队列保证每次取出的数据不重复并且不会有并发问题,在没有数据时程序阻塞直至有电文推送过来

因为充分利用了redis读写快,单线程的特点,所以在实际使用过程中,不光可以运用多线程处理数据,还可以根据需要进行多服务部署,大大的加快了数据处理速度,最终整体结构如下:

在这里插入图片描述

在实际使用中,通过监测redis中存储的数据,以及根据日志实时打印的consumer数据处理速度发现在多线程的情况下采用分页查询每次只取1000条,数据读取速度远高于consumer的处理速度,然后通过利用公司闲置测试服务器以及其它备用电脑,架设多个consumer服务,最终200多万条电文数据,大小在10-20GB 通过架设多个服务最终在较短的时间内完成了历史数据的割接,保证了功能的正常上线与使用.

总结

本次处理案例中运用到的核心知识点:
1.利用redis特性保证多线程环境下分页查询不出错
2.通过redis保证了数据的读写效率,并将数据通过第三方缓存,方便进行多机部署,使服务灵活扩展性大大提高,读取慢就多架设producer,处理慢就多架设consumer
3.充分利用多线程的性能优势加快数据处理速度.
4.因redis缓存了分页页数和数据,在有其它突发情况下可暂停处理,不会出现程序的不可逆性(突然断电断网或程序异常导致服务宕机)

其它踩过的坑

因 对象的实例化交由了Spring来托管,在多线程的情况下,我用复用对象field时会有并发问题:
例:


@Service
public class QualityOperateServiceImpl  implements QualityOperateService{

	private String msgId;//电文ID
	private List<ConfigDetails> configs//配置中心
	private String content;//电文内容
	
}

在多线程的情况下假如A线程读取到的电文为PGF001,在调用数据生成逻辑之前 因为QualityOperateServiceImpl 对象为单例,此时B线程将其field又设置为了XGF001的电文,此时A线程重新读取该对象field就会拿到B线程的值,及有概率出现并发问题.
为解决这个问题只需要将这几个field设置为Threadlocal ,即每个线程都持有一份它的本地变量便可轻松解决.

最后

唯有良好的编码习惯,开发前充分思考,充分考虑到程序的扩展性,梳理好模块结构,尽可能的做到灵活配置,方能做到最大的提高自身的工作效率,并应该学会融合贯通,充分的发挥自身所学,将其应用到实际项目中,才能提高产品质量,提高自我修养,希望各位小伙伴看后能够对其有所帮助,在日后的开发中能够编写质量更好的代码.

2010-08-03 16:49:15 xwqixia 阅读数 182

背景:一个页面需要输出list中所有数据 (list大小>1000,客户要求不分页),生成的页面时间会很长,浏览器会出现短暂的空白。

 

700条XX信息数据大小965.9KB,耗时1.66s

1000条XX数据大小1803.6KB,耗时2.68s

100条数据 大小  194.8KB 耗时 140ms

 

 

 

优化思路:将数据分成多块,分多次响应给客户端。 如:优化700条数据,第一次输出100条数据(这样用户就很快看到有页面出来),剩余的600条数据,通过ajax分6次请求,每次也是100条数据,给客户端。(这里相当于:用户的一个请求结果,被切割成7请求来完成)

 

这里有关键的几个点:

  第一: 7次的请求怎么保证他们操作的都说同一个list。(缓存第一次从数据库查出来的list,缓存的key要唯一性,可以采用时间戳来做key)

  第二: 判断是否是最后一次请求,是。。就要清空这次的缓存

  第三: 后台返回的不是一个list,而是一个已经在jsp中生成好的html,ajax直接把后台生成html,inner进当前页面。

       

  流程:  

 

  解释:

        1.用户第一次请求

        2.方法1>>去数据库取数据,判断返回list的大小。若大于100,取list中的 0-99放到listB中,返回。同时把list缓存起来,以时间戳做key,同时把key也返回。

        3,4.去jsp1>> jsp1中有<div id="data"> <table>表头</table> <jsp:include page="jsp2.jsp"></jsp:include> </div> 。

        5.jsp1最后还会调用一段js,把list大小和key传进去,

        6.js 根据list大小算出还要发送多少个请求

       

 /**
	  * 分页请求方法
	  * @param {} count 结果集大小
	  * @param {}  key  
	  */
	 page:function(count,key){
		var divId="data";
	 	var pageCount = count/fundQuery.limit;//默认100
	 	if(pageCount == 0){//结果小于 每页显示数
	 		return;
	 	}
	 	for(var i=1;i<=pageCount;i++){//循环创建div
	 		var _span = document.createElement("div");
			_span.id = divId + i;
			var newText = document.createTextNode(" ");
			_span.appendChild(newText); 
		  	TFL.dom.selector(divId).append(_span);	
	 	}
	 	for(var i=1;i<=pageCount;i++){//循环发送请求
	 		var last=false;
	 		if(i==pageCount){
	 			last=true;
	 		}
	 		var pageurl ="funds/fundsquery.jhtm";
	 		var vars ="service=page&start="+i+"&limit="+fundQuery.limit+"&last="+last+"&key="+key;
	 		var requestObject = 
		 	{
		 		url: pageurl,
		 		getType: "1",
		 		vars: vars, 
		 		method: "post",
		 		
		 		callBack: function(html){
		 			var start = parseInt(ajaxGetPage.gup('start',this.vars));//获取 vars 中的参数
		 			TFL.dom.selector("data"+start).html(html); //将后台返回的html inner进指定的div
		 		}
			};
			var ajax = new TFL.ajax2();
			ajax.ajaxRequest(requestObject);//ajax封装
	 	}
	 	
	 }

 

 

 

 

 

  7.ajax调用controller的方法:

     

	public OutputVO page(FundNavInput in,Boolean last,Long key){
		OutputVO out = new OutputVO();
		List<FundNavInfoVO> result = (List<FundNavInfoVO>)map.get(key);
		List<FundNavInfoVO> res = null;
		if(result != null){
			int startIndex = in.getStart() * in.getLimit(); //开始的索引 = 页码*大小
			int toIndex = startIndex + in.getLimit() >result.size()?result.size():startIndex+in.getLimit();//结束索引=开始索引+每页条数
			
			res = result.subList(startIndex, toIndex);
			out.setList(res);
			out.setCount(res.size());
			if(last){//最后一次 移除这次的结果
				map.remove(key);
			}	
		}
		ViewUtil.setViewName("jsp2");
		return out;
		
	}

 

 

 8.jsp2中  <table cellpadding="0" cellspacing="0" class="list" style="border-bottom: #d3d2d3 0px solid;">
              <c:forEach var="dataRow" items="${outputVO.list}" varStatus="stat"> <tr>.....<tr> </c:forEach>

 

 

            </table>

 9.返回一段已经画好的表格。js ajax回调函数中把这段html inner 进指定的div

 

总结:

     这种解决方案其实相当于  隐式的分页。。。。  个人挺满意这种方案。! 欢迎大伙拍砖!

 

2017-03-22 22:45:25 ning521513 阅读数 4589

在做一个excel导出的时候,数据量超过2千多条的时候就会报错。刚开始以为是服务器或者数据库崩掉了。但是报错时页面反应速度很快,不像是服务器性能问题。后来经过反复的测试发现是在处理数据的时候使用的如pdo中fetchAll这种的函数,一次性将所有的数据全部装入到一个数组中,导致类似内存溢出的问题。


解决办法:

在对数据进行处理的时候可以逐条取数据,如:


while($data = $pdo->fetch()){
		......
	}

如果必须要将数据整理撑数组的形式再处理,可以在循环中边获取,边处理,边删除,如:

while($data = $pdo->fetch()){
		......
		$arr[data['name']][] = $data;
		.........
		unset($arr);
	}



2019-03-21 11:10:41 Srodong 阅读数 54

MySQL的预处理语句

预处理语句大大减少了分析时间,只做了一次查询(虽然语句多次执行)。

绑定参数减少了服务器带宽,你只需要发送查询的参数,而不是整个语句。

预处理语句针对SQL注入是非常有用的,因为参数值发送后使用不同的协议,保证了数据的合法性。

不带参数预处理

准备预处理语句:prepare 语句名称 from “预处理的sql语句”;

prepare sql_y from "select * from user";

 

执行预处理语句:execute 语句名称;

execute sql_y;

 删除预处理语句:drop prepare 语句名称;

drop prepare sql_y;

带参数预处理

查询操作

设置预处理语句

prepare sql_y from "select * from user where id = ?";

定义参数变量

set @变量名 = 值;  

set @id = 1;

传递参数变量执行预处理语句

execute sql_y using @id; 

 插入操作

设置预处理语句

prepare sql_y from "INSERT INTO user (name,uid)VALUES (?,?)";

定义参数变量

set @name = '大王';
set @uid = 8;

传递参数变量执行预处理语句

execute sql_y using @name,@uid; 

更新操作

设置预处理语句

prepare sql_y from "update user SET name=?,uid=? where id =?";

定义参数变量 

set @name = '小王';
set @uid = 9;
set @id = 12;

传递参数变量执行预处理语句

execute sql_y using @name,@uid,@id;  

php下使用PDO预处理语句

我是在thinkphp5下使用的 

使用占位符生成预处理语句准备SQL语句

public function index()
    {
		$pdo  = new  \PDO ('mysql:host=localhost;dbname=test' , 'root' ,  '');  //链接PDO
		$pdo->exec('set names utf8');  //防止中文乱码
		$stmt  =  $pdo -> prepare ( "INSERT INTO user (name, uid) VALUES (:name, :uid)" );  //设置预处理语句
		//使用bindParam方法绑定参数变量
		$stmt -> bindParam ( ':name' ,  $name );
		$stmt -> bindParam ( ':uid' ,  $uid);
		// 插入一行
		$name  =  '无敌' ;
		$uid=  11 ;
		$res = $stmt -> execute ();
    }

使用?占位符生成预处理语句准备SQL语句

public function index()
    {
		$pdo  = new  \PDO ('mysql:host=localhost;dbname=test' , 'root' ,  '');  //链接PDO
		$pdo->exec('set names utf8');  //防止中文乱码
		//设置预处理语句
		$stmt  =  $pdo -> prepare ( "INSERT INTO user (name, uid) VALUES (?,?)" );  //使用?占位符生成预处理语句准备SQL语句
		//使用bindParam方法绑定参数变量
		$stmt -> bindParam ( 1 ,  $name );  //1代表第一个数据占位符
        $stmt -> bindParam ( 2 ,  $uid );
		// 插入一行
		$name  =  '无敌' ;
		$uid  =  11 ;
		$res = $stmt -> execute ();  //执行预处理语句
    }

使用数组绑定参数

public function index()
    {
		$pdo  = new  \PDO ('mysql:host=localhost;dbname=test' , 'root' ,  '');  //链接PDO
		$pdo->exec('set names utf8');  //防止中文乱码
		//设置预处理语句
		$stmt  =  $pdo -> prepare ( "INSERT INTO user (name, uid) VALUES (:name, :uid)" );  //使用占位符生成预处理语句准备SQL语句
		// 插入一行
		$name  =  '无敌1' ;
		$uid  =  12 ;
		$arr = array(
		    ':name'  =>  $name,
		    ':uid'   =>  $uid 
		    );
		$res = $stmt -> execute ($arr);  //执行预处理语句
    }

2017-05-21 16:54:37 hnzmdpan 阅读数 563
摘要: 公司业务,某个逻辑会涉及到大批量数据。举个例子,现有手机(Mobile),和日志对象(说是日志对象,其实并不太准备,因是日志大多数情况下是不会修改的,暂且这么理解吧,假设这里讨论的日志对象允许做修改。其实我想表达很简单,就是一对多的关系,具体的你可以自己想像理解。)

首先,公司里用到的是Hibernate。现在的代码大约是这个样子的


String hql = " from detail where detail.parent.id = :id";
List<XXX> objs = query(XXX);
for(XXX xxx : objs){
   xxx.setXXX
   xxx.setXXX
   store(xxxx);
}

先查询出明细,然后循环设置值,最后保存。看到这里,可能有人会说了,直接根据ID进行UPDATE不就行了吗?是的,当然可以,只不过业务中还有其它的逻辑,更新,只是其中的一部分。回到主题,对于类似的情形,在公司我写了有好几次了,决心封装一下。另外,上述的代码中还有一个隐患,那就是,当明细多了以后,比如成百上千条时,一次都加载出来,可能会出现性能的问题吧,比如卡了,或是内存溢出。

贴上我封装的代码吧

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

public class DataHelper {
	/**
	 * 处理数据量较多的情况。
	 * @param size 希望一次处理多少条
	 * @param ids 对象的ID的集合
	 * @param callBack 回调函数
	 */
	public static void splitGroup(int size, List<Long> ids, GroupCallBack callBack) {
		if (ids == null || ids.size() == 0) {
			return;
		}
		
		boolean doAction = false;
		List<Long> temp = new ArrayList<Long>();
		int total = ids.size();

		for (int i = 0; i < ids.size(); i++) {
			doAction = false;
			temp.add(ids.get(i));

			if (temp.size() == size || (i == total - 1)) {
				doAction = true;
			}

			if (doAction) {
				callBack.doAction(temp);

				temp = new ArrayList<Long>();
			}
		}
	}
}

上述代码,主要是对数据进行拆分,比如希望一次100,那么,程序就计算是否到达了100,如果是的话,则调用回调函数。回调函数,是一个接口类型的,需要自己写实现。回调方法中,传入一个集合,其大小则为100。

import java.util.List;

/**
 * 处理大数据的回调函数
 *
 */
public interfaceGroupCallBack{
	
	/**
	 * 回调方法。当size达得指定的大小时,或是到达最后一个数据时,调用此方法
	 * @param ids 包含指定的IDS。其大小为指定的size
	 */
	public void doAction(List<Long> ids);
}

另外,传入的集合,很有可能会出现不足100的情况。比如356,则会调用4次回调函数,即,100,200,300各一次,最后的56条,会再调用一次。

最后,写个test

	public static void main(String[] args) {
        // 这4行代码,模拟一下查询明细。这里模似一下,假设查询到356条明细。没有链接数据库,直接填充list。
		List<Long> moveDocIds = new ArrayList<Long>();
		for (int i = 0; i < 356; i++) {
			moveDocIds.add(Long.parseLong(i + ""));
		}

        // 代码改进的地方
		DataHelper.splitGroup(100, moveDocIds, new GroupCallBack() {
			@Override
			public void doAction(List<Long> ids) {
                // 这里的参数,就是分组完以后的了。
				System.out.println(Arrays.toString(ids.toArray()));

				// to do
				String hql = "from detail where id in (:ids)";
				// load
				// for(wmmovedoc doc : docs){ doc.setXXX doc.setXXXX
				// store(doc);}
			}
		});

		System.out.println("end");
	}

输出的结果为

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
[100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199]
[200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299]
[300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355]
end

 

总结:以上这么多,说到底就是对大批量数据的处理方法。我的代码实现,有点类似于spring的XXXTempleate。

 

打个小广告

我建立了个群  622539266  JAVA知识交流,有在学JAVA的,或是想学JAVA的,可以加进来哦。

没有更多推荐了,返回首页