Java中實現(xiàn)代碼優(yōu)化的技巧分享
1.用String.format拼接字符串
不知道你有沒有拼接過字符串,特別是那種有多個參數(shù),字符串比較長的情況。
比如現(xiàn)在有個需求:要用get請求調(diào)用第三方接口,url后需要拼接多個參數(shù)。
以前我們的請求地址是這樣拼接的:
String url = "http://susan.sc.cn?userName="+userName+"&age="+age+"&address="+address+"&sex="+sex+"&roledId="+roleId;
字符串使用+號拼接,非常容易出錯。
后面優(yōu)化了一下,改為使用StringBuilder拼接字符串:
StringBuilder urlBuilder = new StringBuilder("http://susan.sc.cn?"); urlBuilder.append("userName=") .append(userName) .append("&age=") .append(age) .append("&address=") .append(address) .append("&sex=") .append(sex) .append("&roledId=") .append(roledId);
代碼優(yōu)化之后,稍微直觀點。
但還是看起來比較別扭。
這時可以使用String.format方法優(yōu)化:
String requestUrl = "http://susan.sc.cn?userName=%s&age=%s&address=%s&sex=%s&roledId=%s"; String url = String.format(requestUrl,userName,age,address,sex,roledId);
代碼的可讀性,一下子提升了很多。
我們平??梢允褂肧tring.format方法拼接url請求參數(shù),日志打印等字符串。
但不建議在for循環(huán)中用它拼接字符串,因為它的執(zhí)行效率,比使用+號拼接字符串,或者使用StringBuilder拼接字符串都要慢一些。
2.創(chuàng)建可緩沖的IO流
IO流想必大家都使用得比較多,我們經(jīng)常需要把數(shù)據(jù)寫入某個文件,或者從某個文件中讀取數(shù)據(jù)到內(nèi)存中,甚至還有可能把文件a,從目錄b,復制到目錄c下等。
JDK給我們提供了非常豐富的API,可以去操作IO流。
例如:
public class IoTest1 { public static void main(String[] args) { FileInputStream fis = null; FileOutputStream fos = null; try { File srcFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/1.txt"); File destFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/2.txt"); fis = new FileInputStream(srcFile); fos = new FileOutputStream(destFile); int len; while ((len = fis.read()) != -1) { fos.write(len); } fos.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (fos != null) { fos.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (fis != null) { fis.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
這個例子主要的功能,是將1.txt文件中的內(nèi)容復制到2.txt文件中。這例子使用普通的IO流從功能的角度來說,也能滿足需求,但性能卻不太好。
因為這個例子中,從1.txt文件中讀一個字節(jié)的數(shù)據(jù),就會馬上寫入2.txt文件中,需要非常頻繁的讀寫文件。
優(yōu)化:
public class IoTest { public static void main(String[] args) { BufferedInputStream bis = null; BufferedOutputStream bos = null; FileInputStream fis = null; FileOutputStream fos = null; try { File srcFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/1.txt"); File destFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/2.txt"); fis = new FileInputStream(srcFile); fos = new FileOutputStream(destFile); bis = new BufferedInputStream(fis); bos = new BufferedOutputStream(fos); byte[] buffer = new byte[1024]; int len; while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); } bos.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (bos != null) { bos.close(); } if (fos != null) { fos.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (bis != null) { bis.close(); } if (fis != null) { fis.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
這個例子使用BufferedInputStream和BufferedOutputStream創(chuàng)建了可緩沖的輸入輸出流。
最關(guān)鍵的地方是定義了一個buffer字節(jié)數(shù)組,把從1.txt文件中讀取的數(shù)據(jù)臨時保存起來,后面再把該buffer字節(jié)數(shù)組的數(shù)據(jù),一次性批量寫入到2.txt中。
這樣做的好處是,減少了讀寫文件的次數(shù),而我們都知道讀寫文件是非常耗時的操作。也就是說使用可緩存的輸入輸出流,可以提升IO的性能,特別是遇到文件非常大時,效率會得到顯著提升。
3.減少循環(huán)次數(shù)
在我們?nèi)粘i_發(fā)中,循環(huán)遍歷集合是必不可少的操作。
但如果循環(huán)層級比較深,循環(huán)中套循環(huán),可能會影響代碼的執(zhí)行效率。
反例:
for(User user: userList) { for(Role role: roleList) { if(user.getRoleId().equals(role.getId())) { user.setRoleName(role.getName()); } } }
這個例子中有兩層循環(huán),如果userList和roleList數(shù)據(jù)比較多的話,需要循環(huán)遍歷很多次,才能獲取我們所需要的數(shù)據(jù),非常消耗cpu資源。
正例:
Map<Long, List<Role>> roleMap = roleList.stream().collect(Collectors.groupingBy(Role::getId)); for (User user : userList) { List<Role> roles = roleMap.get(user.getRoleId()); if(CollectionUtils.isNotEmpty(roles)) { user.setRoleName(roles.get(0).getName()); } }
減少循環(huán)次數(shù),最簡單的辦法是,把第二層循環(huán)的集合變成map,這樣可以直接通過key,獲取想要的value數(shù)據(jù)。
雖說map的key存在hash沖突的情況,但遍歷存放數(shù)據(jù)的鏈表或者紅黑樹的時間復雜度,比遍歷整個list集合要小很多。
4.用完資源記得及時關(guān)閉
在我們?nèi)粘i_發(fā)中,可能經(jīng)常訪問資源,比如:獲取數(shù)據(jù)庫連接,讀取文件等。
我們以獲取數(shù)據(jù)庫連接為例。
反例:
//1. 加載驅(qū)動類 Class.forName("com.mysql.jdbc.Driver"); //2. 創(chuàng)建連接 Connection connection = DriverManager.getConnection("jdbc:mysql//localhost:3306/db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8","root","123456"); //3.編寫sql String sql ="select * from user"; //4.創(chuàng)建PreparedStatement PreparedStatement pstmt = conn.prepareStatement(sql); //5.獲取查詢結(jié)果 ResultSet rs = pstmt.execteQuery(); while(rs.next()){ int id = rs.getInt("id"); String name = rs.getString("name"); }
上面這段代碼可以正常運行,但卻犯了一個很大的錯誤,即:ResultSet、PreparedStatement和Connection對象的資源,使用完之后,沒有關(guān)閉。
我們都知道,數(shù)據(jù)庫連接是非常寶貴的資源。我們不可能一直創(chuàng)建連接,并且用完之后,也不回收,白白浪費數(shù)據(jù)庫資源。
正例:
//1. 加載驅(qū)動類 Class.forName("com.mysql.jdbc.Driver"); Connection connection = null; PreparedStatement pstmt = null; ResultSet rs = null; try { //2. 創(chuàng)建連接 connection = DriverManager.getConnection("jdbc:mysql//localhost:3306/db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8","root","123456"); //3.編寫sql String sql ="select * from user"; //4.創(chuàng)建PreparedStatement pstmt = conn.prepareStatement(sql); //5.獲取查詢結(jié)果 rs = pstmt.execteQuery(); while(rs.next()){ int id = rs.getInt("id"); String name = rs.getString("name"); } } catch(Exception e) { log.error(e.getMessage(),e); } finally { if(rs != null) { rs.close(); } if(pstmt != null) { pstmt.close(); } if(connection != null) { connection.close(); } }
這個例子中,無論是ResultSet,或者PreparedStatement,還是Connection對象,使用完之后,都會調(diào)用close方法關(guān)閉資源。
在這里溫馨提醒一句:ResultSet,或者PreparedStatement,還是Connection對象,這三者關(guān)閉資源的順序不能反了,不然可能會出現(xiàn)異常。
5.使用池技術(shù)
我們都知道,從數(shù)據(jù)庫查數(shù)據(jù),首先要連接數(shù)據(jù)庫,獲取Connection資源。
想讓程序多線程執(zhí)行,需要使用Thread類創(chuàng)建線程,線程也是一種資源。
通常一次數(shù)據(jù)庫操作的過程是這樣的:
- 創(chuàng)建連接
- 進行數(shù)據(jù)庫操作
- 關(guān)閉連接
而創(chuàng)建連接和關(guān)閉連接,是非常耗時的操作,創(chuàng)建連接需要同時會創(chuàng)建一些資源,關(guān)閉連接時,需要回收那些資源。
如果用戶的每一次數(shù)據(jù)庫請求,程序都都需要去創(chuàng)建連接和關(guān)閉連接的話,可能會浪費大量的時間。
此外,可能會導致數(shù)據(jù)庫連接過多。
我們都知道數(shù)據(jù)庫的最大連接數(shù)是有限的,以mysql為例,最大連接數(shù)是:100,不過可以通過參數(shù)調(diào)整這個數(shù)量。
如果用戶請求的連接數(shù)超過最大連接數(shù),就會報:too many connections異常。如果有新的請求過來,會發(fā)現(xiàn)數(shù)據(jù)庫變得不可用。
這時可以通過命令:
show variables like max_connections
查看最大連接數(shù)。
然后通過命令:
set GLOBAL max_connections=1000
手動修改最大連接數(shù)。
這種做法只能暫時緩解問題,不是一個好的方案,無法從根本上解決問題。
最大的問題是:數(shù)據(jù)庫連接數(shù)可以無限增長,不受控制。
這時我們可以使用數(shù)據(jù)庫連接池。
目前Java開源的數(shù)據(jù)庫連接池有:
- DBCP:是一個依賴Jakarta commons-pool對象池機制的數(shù)據(jù)庫連接池。
- C3P0:是一個開放源代碼的JDBC連接池,它在lib目錄中與Hibernate一起發(fā)布,包括了實現(xiàn)jdbc3和jdbc2擴展規(guī)范說明的Connection 和Statement 池的DataSources 對象。
- Druid:阿里的Druid,不僅是一個數(shù)據(jù)庫連接池,還包含一個ProxyDriver、一系列內(nèi)置的JDBC組件庫、一個SQL Parser。
- Proxool:是一個Java SQL Driver驅(qū)動程序,它提供了對選擇的其它類型的驅(qū)動程序的連接池封裝,可以非常簡單的移植到已有代碼中。
目前用的最多的數(shù)據(jù)庫連接池是:Druid。
到此這篇關(guān)于Java中實現(xiàn)代碼優(yōu)化的技巧分享的文章就介紹到這了,更多相關(guān)Java代碼優(yōu)化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java有趣好玩的圖形界面開發(fā)八個案例實現(xiàn)
今天使用GUI技術(shù)寫了幾個練習的Demo,希望對大家學習圖形用戶界面有所幫助,感興趣的同學來看看吧,動手敲一遍理解更通透2022-05-05如何在springboot中配置和使用mybatis-plus
這篇文章主要給大家介紹了關(guān)于如何在springboot中配置和使用mybatis-plus的相關(guān)資料,MyBatis?Plus是MyBatis的增強版,旨在提供更多便捷的特性,減少開發(fā)工作,同時保留了MyBatis的靈活性和強大性能,需要的朋友可以參考下2023-11-11使用Java創(chuàng)建數(shù)據(jù)透視表并導出為PDF的方法
數(shù)據(jù)透視分析是一種強大的工具,可以幫助我們從大量數(shù)據(jù)中提取有用信息并進行深入分析,本文將介紹如何使用Java來構(gòu)建PivotTable以及實現(xiàn)數(shù)據(jù)透視分析,并將其導出為PDF2023-10-10剖析Java中HashMap數(shù)據(jù)結(jié)構(gòu)的源碼及其性能優(yōu)化
這篇文章主要介紹了Java中HashMap數(shù)據(jù)結(jié)構(gòu)的源碼及其性能優(yōu)化,文中以Java 8后HashMap的性能提升來討論了HashMap的一些優(yōu)化點,需要的朋友可以參考下2016-05-05springboot控制層傳遞參數(shù)為非必填值的操作
這篇文章主要介紹了springboot控制層傳遞參數(shù)為非必填值的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10Java實現(xiàn)迅雷地址轉(zhuǎn)成普通地址實例代碼
本篇文章主要介紹了Java實現(xiàn)迅雷地址轉(zhuǎn)成普通地址實例代碼,非常具有實用價值,有興趣的可以了解一下。2017-03-03