前言:今天叶秋学长跟大家谈谈优化这个话题,那么我们一起聊聊Java中如何实现代码优化这个问题,学长这里有几个实用的小技巧分享给大家,希望会对你们有所帮助。
博主传送门:
叶秋学长
推荐专栏:
秋招面试题
Vue讲解
Spring系列
Spring Boot 系列
云原生系列(付费专栏)
目录
1.用String.format拼接字符串
2.创建可缓冲的IO流
3.减少循环次数
4.用完资源记得及时关闭
5.使用池技术
1.用String.format拼接字符串
不知道你有没有拼接过字符串,特别是那种有多个参数,字符串比较长的情况。
比如现在有个需求:要用get请求调用第三方接口,url后需要拼接多个参数。
以前我们的请求地址是这样拼接的:
Stringurl="http://susan.sc.cn" />
2.创建可缓冲的IO流
IO流
想必大家都使用得比较多,我们经常需要把数据写入
某个文件,或者从某个文件中读取
数据到内存
中,甚至还有可能把文件a,从目录b,复制
到目录c下等。
JDK给我们提供了非常丰富的API,可以去操作IO流。
例如:
publicclassIoTest1{publicstaticvoidmain(String[]args){FileInputStreamfis=null;FileOutputStreamfos=null;try{FilesrcFile=newFile("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/1.txt");FiledestFile=newFile("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/2.txt");fis=newFileInputStream(srcFile);fos=newFileOutputStream(destFile);intlen;while((len=fis.read())!=-1){fos.write(len);}fos.flush();}catch(IOExceptione){e.printStackTrace();}finally{try{if(fos!=null){fos.close();}}catch(IOExceptione){e.printStackTrace();}try{if(fis!=null){fis.close();}}catch(IOExceptione){e.printStackTrace();}}}}
这个例子主要的功能,是将1.txt文件中的内容复制到2.txt文件中。这例子使用普通的IO流从功能的角度来说,也能满足需求,但性能却不太好。
因为这个例子中,从1.txt文件中读一个字节的数据,就会马上写入2.txt文件中,需要非常频繁的读写文件。
优化:
publicclassIoTest{publicstaticvoidmain(String[]args){BufferedInputStreambis=null;BufferedOutputStreambos=null;FileInputStreamfis=null;FileOutputStreamfos=null;try{FilesrcFile=newFile("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/1.txt");FiledestFile=newFile("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/2.txt");fis=newFileInputStream(srcFile);fos=newFileOutputStream(destFile);bis=newBufferedInputStream(fis);bos=newBufferedOutputStream(fos);byte[]buffer=newbyte[1024];intlen;while((len=bis.read(buffer))!=-1){bos.write(buffer,0,len);}bos.flush();}catch(IOExceptione){e.printStackTrace();}finally{try{if(bos!=null){bos.close();}if(fos!=null){fos.close();}}catch(IOExceptione){e.printStackTrace();}try{if(bis!=null){bis.close();}if(fis!=null){fis.close();}}catch(IOExceptione){e.printStackTrace();}}}}
这个例子使用BufferedInputStream
和BufferedOutputStream
创建了可缓冲
的输入输出流。
最关键的地方是定义了一个buffer字节数组,把从1.txt文件中读取的数据临时保存起来,后面再把该buffer字节数组的数据,一次性批量写入到2.txt中。
这样做的好处是,减少了读写文件的次数,而我们都知道读写文件是非常耗时的操作。也就是说使用可缓存的输入输出流,可以提升IO的性能,特别是遇到文件非常大时,效率会得到显著提升。
3.减少循环次数
在我们日常开发中,循环遍历集合是必不可少的操作。
但如果循环层级比较深,循环中套循环,可能会影响代码的执行效率。
反例
:
for(Useruser:userList){for(Rolerole:roleList){if(user.getRoleId().equals(role.getId())){user.setRoleName(role.getName());}}}
这个例子中有两层循环,如果userList和roleList数据比较多的话,需要循环遍历很多次,才能获取我们所需要的数据,非常消耗cpu资源。
正例
:
Map<Long,List>roleMap=roleList.stream().collect(Collectors.groupingBy(Role::getId));for(Useruser:userList){Listroles=roleMap.get(user.getRoleId());if(CollectionUtils.isNotEmpty(roles)){user.setRoleName(roles.get(0).getName());}}
减少循环次数,最简单的办法是,把第二层循环的集合变成map
,这样可以直接通过key
,获取想要的value
数据。
虽说map的key存在hash冲突
的情况,但遍历存放数据的链表
或者红黑树
的时间复杂度
,比遍历整个list集合要小很多。
4.用完资源记得及时关闭
在我们日常开发中,可能经常访问资源
,比如:获取数据库连接,读取文件等。
我们以获取数据库连接为例。
反例
:
//1.加载驱动类Class.forName("com.mysql.jdbc.Driver");//2.创建连接Connectionconnection=DriverManager.getConnection("jdbc:mysql//localhost:3306/db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8","root","123456");//3.编写sqlStringsql="select*fromuser";//4.创建PreparedStatementPreparedStatementpstmt=conn.prepareStatement(sql);//5.获取查询结果ResultSetrs=pstmt.execteQuery();while(rs.next()){intid=rs.getInt("id");Stringname=rs.getString("name");}
上面这段代码可以正常运行,但却犯了一个很大的错误,即:ResultSet、PreparedStatement和Connection对象的资源,使用完之后,没有关闭。
我们都知道,数据库连接是非常宝贵的资源。我们不可能一直创建连接,并且用完之后,也不回收,白白浪费数据库资源。
正例
:
//1.加载驱动类Class.forName("com.mysql.jdbc.Driver");Connectionconnection=null;PreparedStatementpstmt=null;ResultSetrs=null;try{//2.创建连接connection=DriverManager.getConnection("jdbc:mysql//localhost:3306/db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8","root","123456");//3.编写sqlStringsql="select*fromuser";//4.创建PreparedStatementpstmt=conn.prepareStatement(sql);//5.获取查询结果rs=pstmt.execteQuery();while(rs.next()){intid=rs.getInt("id");Stringname=rs.getString("name");}}catch(Exceptione){log.error(e.getMessage(),e);}finally{if(rs!=null){rs.close();}if(pstmt!=null){pstmt.close();}if(connection!=null){connection.close();}}
这个例子中,无论是ResultSet,或者PreparedStatement,还是Connection对象,使用完之后,都会调用close
方法关闭资源。
在这里温馨提醒一句:ResultSet,或者PreparedStatement,还是Connection对象,这三者关闭资源的顺序不能反了,不然可能会出现异常。
5.使用池技术
我们都知道,从数据库查数据,首先要连接数据库,获取Connection
资源。
想让程序多线程执行,需要使用Thread
类创建线程,线程也是一种资源。
通常一次数据库操作的过程是这样的:
创建连接
进行数据库操作
关闭连接
而创建连接和关闭连接,是非常耗时的操作,创建连接需要同时会创建一些资源,关闭连接时,需要回收那些资源。
如果用户的每一次数据库请求,程序都都需要去创建连接和关闭连接的话,可能会浪费大量的时间。
此外,可能会导致数据库连接过多。
我们都知道数据库的最大连接数
是有限的,以mysql为例,最大连接数是:100
,不过可以通过参数调整这个数量。
如果用户请求的连接数超过最大连接数,就会报:too many connections
异常。如果有新的请求过来,会发现数据库变得不可用。
这时可以通过命令:
showvariableslikemax_connections
查看最大连接数。
然后通过命令:
setGLOBALmax_connections=1000
手动修改最大连接数。
这种做法只能暂时缓解问题,不是一个好的方案,无法从根本上解决问题。
最大的问题是:数据库连接数可以无限增长,不受控制。
这时我们可以使用数据库连接池
。
目前Java开源的数据库连接池有:
DBCP:是一个依赖Jakarta commons-pool对象池机制的数据库连接池。
C3P0:是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。
Druid:阿里的Druid,不仅是一个数据库连接池,还包含一个ProxyDriver、一系列内置的JDBC组件库、一个SQL Parser。
Proxool:是一个Java SQL Driver驱动程序,它提供了对选择的其它类型的驱动程序的连接池封装,可以非常简单的移植到已有代码中。
目前用的最多的数据库连接池是:Druid
。
本期分享到此为止,关注博主不迷路叶秋学长带你上高速~~