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, 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创建了可缓冲的输入输出流。

最关键的地方是定义了一个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<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());
    }
}

减少循环次数,最简单的办法是,把第二层循环的集合变成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.编写sql
String sql ="select * from user";
//4.创建PreparedStatement
PreparedStatement 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类创建线程,线程也是一种资源。

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

创建连接

进行数据库操作

关闭连接

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

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

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

我们都知道数据库的最大连接数是有限的,以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。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容