• 怎样去优化JAVA中的实现代码


    1.用String.format拼接字符串

    不知道你有没有拼接过字符串,特别是那种有多个参数,字符串比较长的情况。

    比如现在有个需求:要用get请求调用第三方接口,url后需要拼接多个参数。

    以前我们的请求地址是这样拼接的:

    <pre class="prettyprint hljs nginx" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">String url = "http://susan.sc.cn?userName="+userName+"&age="+age+"&address="+address+"&sex="+sex+"&roledId="+roleId;
    

    字符串使用 + 号拼接,非常容易出错。

    后面优化了一下,改为使用 StringBuilder 拼接字符串:

    1. "prettyprint hljs go" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">StringBuilder urlBuilder = new StringBuilder("http://susan.sc.cn?");
    2. urlBuilder.append("userName=")
    3. .append(userName)
    4. .append("&age=")
    5. .append(age)
    6. .append("&address=")
    7. .append(address)
    8. .append("&sex=")
    9. .append(sex)
    10. .append("&roledId=")
    11. .append(roledId);

    代码优化之后,稍微直观点。

    但还是看起来比较别扭。

    这时可以使用 String.format 方法优化:

    1. <pre class="prettyprint hljs perl" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">String requestUrl = "http://susan.sc.cn?userName=%s&age=%s&address=%s&sex=%s&roledId=%s";
    2. String url = String.format(requestUrl,userName,age,address,sex,roledId);

    代码的可读性,一下子提升了很多。

    我们平常可以使用 String.format 方法拼接url请求参数,日志打印等字符串。

    但不建议在for循环中用它拼接字符串,因为它的执行效率,比使用+号拼接字符串,或者使用StringBuilder拼接字符串都要慢一些。

    2.创建可缓冲的IO流

    IO流 想必大家都使用得比较多,我们经常需要把数据 写入 某个文件,或者从某个文件中 读取 数据到 内存 中,甚至还有可能把文件a,从目录b, 复制 到目录c下等。

    JDK给我们提供了非常丰富的API,可以去操作IO流。

    例如:

    1. <pre class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">public class IoTest1 {
    2. public static void main(String[] args) {
    3. FileInputStream fis = null;
    4. FileOutputStream fos = null;
    5. try {
    6. File srcFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/1.txt");
    7. File destFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/2.txt");
    8. fis = new FileInputStream(srcFile);
    9. fos = new FileOutputStream(destFile);
    10. int len;
    11. while ((len = fis.read()) != -1) {
    12. fos.write(len);
    13. }
    14. fos.flush();
    15. } catch (IOException e) {
    16. e.printStackTrace();
    17. } finally {
    18. try {
    19. if (fos != null) {
    20. fos.close();
    21. }
    22. } catch (IOException e) {
    23. e.printStackTrace();
    24. }
    25. try {
    26. if (fis != null) {
    27. fis.close();
    28. }
    29. } catch (IOException e) {
    30. e.printStackTrace();
    31. }
    32. }
    33. }
    34. }

    这个例子主要的功能,是将1.txt文件中的内容复制到2.txt文件中。这例子使用普通的IO流从功能的角度来说,也能满足需求,但性能却不太好。

    因为这个例子中,从1.txt文件中读一个字节的数据,就会马上写入2.txt文件中,需要非常频繁的读写文件。

    优化:

    1. <pre class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">public class IoTest {
    2. public static void main(String[] args) {
    3. BufferedInputStream bis = null;
    4. BufferedOutputStream bos = null;
    5. FileInputStream fis = null;
    6. FileOutputStream fos = null;
    7. try {
    8. File srcFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/1.txt");
    9. File destFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/2.txt");
    10. fis = new FileInputStream(srcFile);
    11. fos = new FileOutputStream(destFile);
    12. bis = new BufferedInputStream(fis);
    13. bos = new BufferedOutputStream(fos);
    14. byte[] buffer = new byte[1024];
    15. int len;
    16. while ((len = bis.read(buffer)) != -1) {
    17. bos.write(buffer, 0, len);
    18. }
    19. bos.flush();
    20. } catch (IOException e) {
    21. e.printStackTrace();
    22. } finally {
    23. try {
    24. if (bos != null) {
    25. bos.close();
    26. }
    27. if (fos != null) {
    28. fos.close();
    29. }
    30. } catch (IOException e) {
    31. e.printStackTrace();
    32. }
    33. try {
    34. if (bis != null) {
    35. bis.close();
    36. }
    37. if (fis != null) {
    38. fis.close();
    39. }
    40. } catch (IOException e) {
    41. e.printStackTrace();
    42. }
    43. }
    44. }
    45. }>

    这个例子使用 BufferedInputStream 和 BufferedOutputStream 创建了 可缓冲 的输入输出流。

    最关键的地方是定义了一个buffer字节数组,把从1.txt文件中读取的数据临时保存起来,后面再把该buffer字节数组的数据,一次性批量写入到2.txt中。

    这样做的好处是,减少了读写文件的次数,而我们都知道读写文件是非常耗时的操作。也就是说使用可缓存的输入输出流,可以提升IO的性能,特别是遇到文件非常大时,效率会得到显著提升。

    3.减少循环次数

    在我们日常开发中,循环遍历集合是必不可少的操作。

    但如果循环层级比较深,循环中套循环,可能会影响代码的执行效率。

    反例 :

    1. <pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">for(User user: userList) {
    2. for(Role role: roleList) {
    3. if(user.getRoleId().equals(role.getId())) {
    4. user.setRoleName(role.getName());
    5. }
    6. }
    7. }

    这个例子中有两层循环,如果userList和roleList数据比较多的话,需要循环遍历很多次,才能获取我们所需要的数据,非常消耗cpu资源。

    正例 :

    1. <pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">Map<Long, List<Role>> roleMap = roleList.stream().collect(Collectors.groupingBy(Role::getId));
    2. for (User user : userList) {
    3. List<Role> roles = roleMap.get(user.getRoleId());
    4. if(CollectionUtils.isNotEmpty(roles)) {
    5. user.setRoleName(roles.get(0).getName());
    6. }
    7. }

    减少循环次数,最简单的办法是,把第二层循环的集合变成 map ,这样可以直接通过 key ,获取想要的 value 数据。

    虽说map的key存在 hash冲突 的情况,但遍历存放数据的 链表 或者 红黑树 的 时间复杂度 ,比遍历整个list集合要小很多。

    4.用完资源记得及时关闭

    在我们日常开发中,可能经常访问 资源 ,比如:获取数据库连接,读取文件等。

    我们以获取数据库连接为例。

    反例 :

    1. <pre class="prettyprint hljs rust" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">//1\. 加载驱动类
    2. Class.forName("com.mysql.jdbc.Driver");
    3. //2\. 创建连接
    4. Connection connection = DriverManager.getConnection("jdbc:mysql//localhost:3306/db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8","root","123456");
    5. //3.编写sql
    6. String sql ="select * from user";
    7. //4.创建PreparedStatement
    8. PreparedStatement pstmt = conn.prepareStatement(sql);
    9. //5.获取查询结果
    10. ResultSet rs = pstmt.execteQuery();
    11. while(rs.next()){
    12. int id = rs.getInt("id");
    13. String name = rs.getString("name");
    14. }

    上面这段代码可以正常运行,但却犯了一个很大的错误,即:ResultSet、PreparedStatement和Connection对象的资源,使用完之后,没有关闭。

    我们都知道,数据库连接是非常宝贵的资源。我们不可能一直创建连接,并且用完之后,也不回收,白白浪费数据库资源。

    正例 :

    1. <pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">//1\. 加载驱动类
    2. Class.forName("com.mysql.jdbc.Driver");
    3. Connection connection = null;
    4. PreparedStatement pstmt = null;
    5. ResultSet rs = null;
    6. try {
    7. //2\. 创建连接
    8. connection = DriverManager.getConnection("jdbc:mysql//localhost:3306/db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8","root","123456");
    9. //3.编写sql
    10. String sql ="select * from user";
    11. //4.创建PreparedStatement
    12. pstmt = conn.prepareStatement(sql);
    13. //5.获取查询结果
    14. rs = pstmt.execteQuery();
    15. while(rs.next()){
    16. int id = rs.getInt("id");
    17. String name = rs.getString("name");
    18. }
    19. } catch(Exception e) {
    20. log.error(e.getMessage(),e);
    21. } finally {
    22. if(rs != null) {
    23. rs.close();
    24. }
    25. if(pstmt != null) {
    26. pstmt.close();
    27. }
    28. if(connection != null) {
    29. connection.close();
    30. }
    31. }

    这个例子中,无论是ResultSet,或者PreparedStatement,还是Connection对象,使用完之后,都会调用 close 方法关闭资源。

    在这里温馨提醒一句:ResultSet,或者PreparedStatement,还是Connection对象,这三者关闭资源的顺序不能反了,不然可能会出现异常。

    5.使用池技术

    我们都知道,从数据库查数据,首先要连接数据库,获取 Connection 资源。

    想让程序多线程执行,需要使用 Thread 类创建线程,线程也是一种资源。

    通常一次数据库操作的过程是这样的:

    1. 创建连接

    2. 进行数据库操作

    3. 关闭连接

    而创建连接和关闭连接,是非常耗时的操作,创建连接需要同时会创建一些资源,关闭连接时,需要回收那些资源。

    如果用户的每一次数据库请求,程序都都需要去创建连接和关闭连接的话,可能会浪费大量的时间。

    此外,可能会导致数据库连接过多。

    我们都知道数据库的 最大连接数 是有限的,以mysql为例,最大连接数是: 100 ,不过可以通过参数调整这个数量。

    如果用户请求的连接数超过最大连接数,就会报: too many connections 异常。如果有新的请求过来,会发现数据库变得不可用。

    这时可以通过命令:

    <pre class="hljs sql" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">show variables like max_connections
    

    查看最大连接数。

    然后通过命令:

    <pre class="hljs sql" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">set GLOBAL max_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 。

  • 相关阅读:
    自动控制原理 - 2 控制系统的数学模型 节2.7-2.10
    【ACM学习】【STL】顺序容器的基本功能
    Servlet生命周期
    java基础知识点
    canal1.1.5不能同步update(修改)类型的数据到ES中
    【力扣】杨辉三角问题
    时间序列的数据分析(二):数据趋势的计算
    数据库的基本信息,都在这几张表里了
    聚名企服教您如何避免DNS劫持攻击?避免DNS劫持攻击方法有哪些
    HTML+CSS个人电影网页设计——电影从你的全世界路过(4页)带音乐特效
  • 原文地址:https://blog.csdn.net/Java_ttcd/article/details/126438799