翻译翻译,
什么是tom的精益求精
这还用翻译?当然是经常问 “还能怎么改进” 的人 (例如项目经理) 所散发出的 “杠精” 气质。
文章目录
"还能怎么改进"的“杠精”气质
朋友在学SSM,mybatis插入多条数据,竟然是在service层循环调用dao层函数 。
我说程序总能改进的,试着传list然后按批处理后提交。
@Transactional
public void add(List<Book> bookList) {
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH,false);
BookMapper mapper = session.getMapper(BookMapper.class);
for (int i = 0; i<bookList.size(); i++) {
mapper.insertSelective(bookList.get(i));
if(i%1000==999){
session.commit();
session.clearCache();
}
}
session.commit();
session.clearCache();
}
今天给我看他写的这个,说效率提高了。但抓着我说的“总能改进的“不放,问我还能怎么改进?
好家伙,为了效率,我总不能让用JDBC。
于是最终翻译为
@Transactional
public void add(List<Book> bookList) {
SqlSession session = sqlSessionFactory.openSession( ExecutorType.BATCH, false );
BookMapper mapper = session.getMapper( BookMapper.class );
int loopInner = Integer.min( 1000, bookList.size() );
int loopOuter = bookList.size()/loopInner+1;
for( int j = 0; j < loopOuter; j++ ){
int innerBounds = Integer.min( loopInner*(j+1), bookList.size() );
for( int i = j*loopInner; i < innerBounds; i++ )
mapper.insertSelective( bookList.get(i) );
session.commit();
session.clearCache();
}
}
改进之处,也就减少了 bookList.size() 次取模和比较
如果对时间要求特别高,那先举几个简单例子
核心
- 已知总循环次数,类似于CLOCK式的迭代需求,能不用if就不用。控制边界和下标减少迭代次数。
副作用
- 代码或略显冗余,观感下降⬇
5个基础编程练习的例子
【例1】解决难见不良代码
大一新生常见不良习惯,很危险,如果 func(i)中再有循环的话…
for( i ){
for( j = 0; j < func(i); j++)
//无论是否有i,此处函数每次内层循环都会执行,也就是说没有外循环也不能这么搞
}
替代为:
for( i ){
int bounds = func(i);
while(j<bounds){
//...
j++;
}
}
【例2】解决常见不良代码
第二个就比较有意思了,当年一个的学弟写的,这要是写在循环里,或者func()有循环💀…
j = (func(index)%100==0) ? (func(index)/100) : (func(index)/100+1);
一改,其实还行,大多数人这么写
int jA = func(index);
//数值运算+比较运算+数值运算
j = (jA%100==0) ? (jA/100) : (jA/100+1);
二改, 简直 💀 …
int jA = func(index);
int remainder = jA%100;
int quotient = jA/100;
j = (remainder==0) ? quotient : (quotient+1);
数学得过0分的我改:
int jA = func(index);
j = (jA + 100 ) % 100; //数值运算
或者
j = ( func(index) + 100 ) % 100;
【例3】解决常见不良代码
for( int i = 0; i < loopTimes; i++ ){
if( i != 100 ) funcA();
//常见的还有形如if( i <= 100 ) funcA();
else funcB();
}
控制一下,将 if 部分提出:
int loopPart = Integer.min(100,loopTimes);
int i = 0;
while(i<loopPart) {
funcA();
i++;
}
if( i++ == 100 ) funcB();
while(i<loopTimes) {
funcA();
i++;
}
如果喜欢空荡荡的"for();",也可以这么写
int loopPart = Integer.min(100,loopTimes);
int i = 0;
for( ; i<loopPart; i++ ) funcA();
if( i++ == 100 ) funcB();
for( ; i<loopTimes; i++ ) funcA();
【例4】这么恶魔的数字,当然指的是把包括但不限于例1~5所有的都写在一块
……
【例5】解决很常见不良代码
for( int i = 0; i < loopTimes; i++ ){
funcA();
if( i%100 == 0 ) funcB();
}
像不像基础编程:
循环输出1~300的整数,每十个数字换行?
像不像小学数学:
化简 1(a+b)+2(a+b)+3(a+b)+...
于是,就这么写
int loopInner = Integer.min(100, listA.size());
int loopOutter = listA.size()/loopInner+1;
for( int j = 0; j < loopOutter; j++ ){
int innerBounds = Integer.min( loopInner*(j+1), listA.size() );
for(int i = j*loopInner; i<innerBounds; i++ ) funcA();
funB();
}
"还能怎么改进"的翻译结果
若没有例5的基础,直接进行修改,产生没有任何的呼吸感与节奏感的初改代码👿
@Transactional
public void add(List<Book> bookList) {
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH,false);
BookMapper mapper = session.getMapper(BookMapper.class);
int loopInner = 1000; // [noteA]此处取1000,对应于本次的需求:每千次提交并清缓存。
//数据条目数不足1000,直接循环list退出
if( bookList.size()<loopInner ){
for (int i = 0; i < bookList.size(); i++)
mapper.insertSelective(bookList.get(i));
} //end if
else{ //数据条目数大于1000时
//分为loopOuter轮(组),每轮执行loopInner次迭代:插入数据。每轮结束后提交事务
int loopOuter = bookList.size()/loopInner; //可能有余数
int i = 0;
for( int j = 0; j < loopOuter; j++ ){
i = j*loopInner;
int innerBounds = (j+1)*loopInner;
while( i<innerBounds ){
mapper.mapper.insertSelective(bookList.get(i++));
}
session.commit();
session.clearCache();
}
//有余数,经过loopInner*loopOuter次迭代后剩余部分直接循环处理
for( i = loopInner*loopOuter; i<bookList.size();i++ )
mapper.insertSelective(bookList.get(i++));
} //end if-else
session.commit();
session.clearCache();
}
在例五的基础上改出
final版本
去掉注释,充满了 节奏感与呼吸感
@Transactional
public void add(List<Book> bookList) {
SqlSession session = sqlSessionFactory.openSession( ExecutorType.BATCH, false );
BookMapper mapper = session.getMapper( BookMapper.class );
int loopInner = Integer.min( 1000, bookList.size() ); //[noteA]后期根据需求修改1000
int loopOuter = bookList.size()/loopInner+1; //外层次数:除法直接加一
//int i = 0; //是否提升内层循环index变量,取决于循环之后是否需要index;
for( int j = 0; j < loopOuter; j++ ){
int innerBounds = Integer.min( loopInner*(j+1), bookList.size() );//最后一轮取表长
for( int i = j*loopInner; i < innerBounds; i++ )
mapper.insertSelective( bookList.get(i) );
session.commit(); //如果可以被整除就会多执行一次提交与清除缓存,mybatis允许该操作
session.clearCache();
} //end for
}
noteA:
- 此处取1000,对应于本次的需求:每千次提交并清缓存
- 外层每循环一次,内层的循环次数,理论取值范围[0,bookList.size()]
- 在取值合理的范围内:
a)向数据库插入数据的效率与loopInner值成正比
b)缓存压力与loopInner值成正比
显然
“杠精”气质看final版,性能比初改版是要差一丢丢的