• JAVA 中如何实现代码优化(技巧讲解)


    1.用 String.format 拼接字符串

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

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

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

    String url = "http://susan.sc.cn?userName="+userName+"&age="+age+"&address="+address+"&sex="+sex+"&roledId="+roleId;
    

    复制代码

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

    后面优化了一下,改为使用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);
    

    复制代码

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

    但还是看起来比较别扭。

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

    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);
    

    复制代码

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

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

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

    2.创建可缓冲的 IO 流

    IO流想必大家都使用得比较多,我们经常需要把数据写入某个文件,或者从某个文件中读取数据到内存中,甚至还有可能把文件 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 文件中的内容复制到 2.txt 文件中。这例子使用普通的 IO 流从功能的角度来说,也能满足需求,但性能却不太好。

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

    优化:

    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, 0len);            }            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();            }        }    }}
    

    复制代码

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

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

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

    3.减少循环次数

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

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

    反例

    for(User user: userList) {   for(Role role: roleList) {      if(user.getRoleId().equals(role.getId())) {         user.setRoleName(role.getName());      }   }}
    

    复制代码

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

    正例

    Map<LongList<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());    }}
    

    复制代码

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

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

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

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

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

    反例

    //1. 加载驱动类Class.forName("com.mysql.jdbc.Driver");//2. 创建连接Connection connection = DriverManager.getConnection("jdbc:mysql//localhost:3306/db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8","root","123456");//3.编写sqlString sql ="select * from user";//4.创建PreparedStatementPreparedStatement pstmt = conn.prepareStatement(sql);//5.获取查询结果ResultSet rs = pstmt.execteQuery();while(rs.next()){   int id = rs.getInt("id");   String name = rs.getString("name");}
    

    复制代码

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

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

    正例

    //1. 加载驱动类Class.forName("com.mysql.jdbc.Driver");
    Connection connection = null;PreparedStatement pstmt = null;ResultSet rs = null;try {    //2. 创建连接    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.创建PreparedStatement    pstmt = conn.prepareStatement(sql);    //5.获取查询结果    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 对象,使用完之后,都会调用close方法关闭资源。

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

    5.使用池技术

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

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

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

    1. 创建连接

    2. 进行数据库操作

    3. 关闭连接

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

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

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

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

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

    这时可以通过命令:

    show variables like max_connections
    

    复制代码

    查看最大连接数。

    然后通过命令:

    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

     

  • 相关阅读:
    UE4 C++联网RPC教程笔记(三)(第8~9集)完结
    这玩意也太猛了!朋友们,我在此严正呼吁大家:端好饭碗,谨防 AI!
    【Transformer从零开始代码实现】(一)输入部件:embedding+positionalEncoding
    docker常用命令:docker制作镜像过程中常用的命令
    wps阶梯表格怎么做?wps阶梯表格制作教程
    小白必备:简单几步, 使用Cpolar+Emlog在Ubuntu上搭建个人博客网站
    给大四毕业生的建议——春招、留学、考公
    剑指 Offer 30. 包含min函数的栈
    No spring.config.import property has been defined
    JVM(Java虚拟机)
  • 原文地址:https://blog.csdn.net/Q54665642ljf/article/details/127699931