代码程序Java 程序优化的一些最佳实践Strut2教程-java教程

最近研讨代码程序,稍微总结一下,以后继续补充:

    一、权衡程序的标准

    权衡一个程序否是质优,可以从多个度角行进分析。其中,

    

    最常见的权衡标准是程序的时光复杂度、空间复杂度,以及代码的可读性、可扩展性。

    

    针对程序的时光复杂度和空间复杂度,想要化优程序代码,要需对数据结构与算法有入深的解理,并且悉熟算计机系统的基本概念和道理;而针对代码的可读性和可扩展性,想要化优程序代码,要需入深解理软件架构计划,熟知并会应用适合的计划式模。

    

  • 首先,如今算计机系统的存储空间已足够大了,到达了 TB 别级,因此比相于空间复杂度,时光复杂度是程序员重要斟酌的素因。为了寻求高性能,在某些繁频操纵行执时,甚至可以斟酌用空间取换时光。
  • 其次,由于到受处理器制造工艺的物理制约、本成制约,CPU 频主的增加遇到了瓶颈,摩尔定律已渐渐失效,每隔 18 个月 CPU 频主即翻倍的代时已过去了,程序员的编程式方发生了完全的转变。在前目这个多核多处理器的代时,现涌了原生支撑多线程的语言(如 Java)以及分布式并行算计架框(如 Hadoop)。为了使程序充分地利用多核 CPU,单简地现实一个单线程的程序是远远不够的,程序员要需可以编写出发并或者并行的多线程程序。
  • 最后,大型软件系统的代码行数到达了百万级,如果没有一个计划好良的软件架构,想在已有代码的基础上行进发开,发开价值和维护本成是法无设想的。一个计划好良的软件该应有具可读性和可扩展性,循遵“开闭则原”、“赖依颠倒则原”、“面向接口编程”等。

    二、项目绍介

    本文将绍介笔者阅历的一个项目中的一部份,通过这个例实析剖代码化优的程过。上面简要地绍介该系统的相干部份。

    该系统的发开语言为 Java,署部在共具有 4 核 CPU 的 Linux 服务器上,相干部份主要有以下操纵:通过某外部系统 D 供给的 REST API 获得信息,从中取提出有效的信息,并通过 JDBC 存储到某数据库系统 S 中,供系统其他部份用使,上述操纵的行执率频为天天一次,一般在半夜当系统空闲时时定行执。为了现实高可用性(High Availability),外部系统 D 署部在两台服务器上,因此要需分离从这两台服务器上获得信息并将信息插入数据库中,有效信息的条数到达了上千条,数据库插入操纵次数则为有效信息条数的两倍。

    图 1. 系统体系结构图

    

    为了速快地现实预期效果,在最初的现实中优先斟酌了功能的现实,而未斟酌系统性能和代码可读性等。系统大致有以下的现实:

    

  1. REST API 获得信息、数据库操纵可能抛出的异常信息都被记载到志日文件中,作为调试用;
  2. 共有 5 次数据库连接操纵,包含第一次清空数据库表,针对两个外部系统 D 各有两次数据库插入操纵,这 5 个连接都是立独的,用完以后即释放;
  3. 有所的数据库插入语句都是用使 java.sql.Statement 类生成的;
  4. 有所的数据库插入语句,都是单条行执的,即生成一条行执一条;
  5. 全部程过都是在单个线程中行执的,包含数据库表清空操纵,数据库插入操纵,释放数据库连接;
  6. 数据库插入操纵的 JDBC 代码散布在代码中。虽然这个版本的系统可以正常行运,到达了预期的效果,但是效率很低,从通过 REST API 获得信息,到析解并取提有效信息,再到数据库插入操纵,统共耗时 100 秒左右。而预期的时光该应在一分钟内以,这显然是不符合要求的。

    三、代码化优程过

    笔者开始分析全部程过有哪些耗时操纵,以及如何晋升效率,短缩程序行执的时光。通过 REST API 获得信息,因为是用使外部系统供给的 API,所以法无在此处晋升效率;取得信息以后析解出有效部份,因为是对特定式格的信息行进析解,所以也无效率晋升的空间。所以,效率可以大幅度晋升的空间在数据库操纵部份以及程序制控部份。上面,分条述叙对耗时操纵的改良法方。

    

    1.  针对志日记载的化优

    闭关志日记载,或者变动志日输出别级。

    

    因为从两台服务器的外部系统 D 上获失掉的信息是同相的,所以数据库插入操纵会抛出异常,异常信息类似于“Attempt to insert duplicate record”,这样的异常信息跟有效信息的条数相称,有上千条。种这况情是能预料到的,所以可以斟酌闭关志日记载,或者不闭关志日记载而是变动志日输出别级,只记载严重别级(severe level)的错误信息,并将此类操纵的志日别级调整为正告别级(warning level),这样就不会记载以上异常信息了。本项目用使的是 Java 自带的志日记载类,以下配置文件将志日输出别级设置为严重别级。

    清单 1. log.properties 设置志日输出别级的段片

    

Java代码 复制代码
  1. default file output is in user ’ s home directory.    
  2.  # levels can be: SEVERE, WARNING, INFO, FINE, FINER, FINEST    
  3.  java.util.logging.ConsoleHandler.level=SEVERE    
  4.  java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter    
  5.  java.util.logging.FileHandler.append=true   
# default file output is in user ’ s home directory. 
 # levels can be: SEVERE, WARNING, INFO, FINE, FINER, FINEST 
 java.util.logging.ConsoleHandler.level=SEVERE 
 java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter 
 java.util.logging.FileHandler.append=true

    通过上述的化优以后,性能有了大幅度的晋升,从本来的 100 秒左右降到了 50 秒左右。为什么仅仅不记载志日就可以有如此大幅度的性能晋升呢?查阅料资,发明已有人做了相干的研讨与验实。经常听到 Java 程序比 C/C++ 程序慢的舆论,但是行运度速慢的真正原因是什么,估计很多人其实不清晰。对于 CPU 密集型的程序(即程序中含包大批算计),Java 程序可以到达 C/C++ 程序等同别级的度速,但是对于 I/O 密集型的程序(即程序中含包大批 I/O 操纵),Java 程序的度速就远远慢于 C/C++ 程序了,很大程度上是因为 C/C++ 程序能直接问访底层的存储设备。因此,不记载志日而失掉大幅度性能晋升的原因是,Java 程序的 I/O 操纵较慢,是一个很耗时的操纵。

    

    2.  针对数据库连接的化优

    享共数据库连接。

    

    共有 5 次数据库连接操纵,每次都需新重建立数据库连接,数据库插入操纵实现以后又即立释放了,数据库连接没有被复用。为了做到享共数据库连接,可以通过单例式模(Singleton Pattern)得获一个同相的数据库连接,每次数据库连接操纵都享共这个数据库连接。这里没有用使数据库连接池(Database Connection Pool)是因为在程序只有少许的数据库连接操纵,只有在大批发并数据库连接的时候才要需连接池。

    清单 2. 享共数据库连接的代码段片

    

Java代码 复制代码
  1. public class JdbcUtil {    
  2.     private static Connection con;    
  3.     // 从配置文件读取连接数据库的信息   
  4.     private static String driverClassName;    
  5.     private static String url;    
  6.     private static String username;    
  7.     private static String password;    
  8.     private static String currentSchema;    
  9.     private static Properties properties = new Properties();    
  10.   
  11.     static {    
  12.     // driverClassName, url, username, password, currentSchema 等从配置文件读取,代码略去   
  13.         try {    
  14.             Class.forName(driverClassName);    
  15.         } catch (ClassNotFoundException e) {    
  16.             e.printStackTrace();    
  17.         }    
  18.         properties.setProperty("user", username);    
  19.         properties.setProperty("password", password);    
  20.         properties.setProperty("currentSchema", currentSchema);    
  21.         try {    
  22.             con = DriverManager.getConnection(url, properties);    
  23.         } catch (SQLException e) {    
  24.             e.printStackTrace();    
  25.         }    
  26.     }    
  27.     private JdbcUtil() {}    
  28.  // 得获一个单例的、享共的数据库连接   
  29.  public static Connection getConnection() {    
  30.         return con;    
  31.     }    
  32.     public static void close() throws SQLException {    
  33.         if (con != null)    
  34.             con.close();    
  35.  }    
  36.  }   
public class JdbcUtil { 
    private static Connection con; 
    // 从配置文件读取连接数据库的信息
    private static String driverClassName; 
    private static String url; 
    private static String username; 
    private static String password; 
    private static String currentSchema; 
    private static Properties properties = new Properties(); 

    static { 
    // driverClassName, url, username, password, currentSchema 等从配置文件读取,代码略去
        try { 
            Class.forName(driverClassName); 
        } catch (ClassNotFoundException e) { 
            e.printStackTrace(); 
        } 
        properties.setProperty("user", username); 
        properties.setProperty("password", password); 
        properties.setProperty("currentSchema", currentSchema); 
        try { 
            con = DriverManager.getConnection(url, properties); 
        } catch (SQLException e) { 
            e.printStackTrace(); 
        } 
    } 
    private JdbcUtil() {} 
 // 得获一个单例的、享共的数据库连接
 public static Connection getConnection() { 
        return con; 
    } 
    public static void close() throws SQLException { 
        if (con != null) 
            con.close(); 
 } 
 }

    通过上述的化优以后,性能有了小幅度的晋升,从 50 秒左右降到了 40 秒左右。享共数据库连接而失掉的性能晋升的原因是,数据库连接是一个耗时耗源资的操纵,要需同近程算计机行进网络通信,建立 TCP 连接,还要需维护连接状态表,建立数据缓冲区。如果享共数据库连接,则只要需行进一次数据库连接操纵,省去了多次新重建立数据库连接的时光。

    

    3.  针对插入数据库记载的化优 - 1

    用使预译编 SQL。

    

    每日一道理
书,各种各样的书。书,寄托着人类热切的希望;书,蕴含着人类丰富的感悟。提起书,会有说不完的话语……

    体具做法是用使 java.sql.PreparedStatement 替代 java.sql.Statement 生成 SQL 语句。PreparedStatement 使得数据库先预译编好 SQL 语句,可以传入数参。而 Statement 生成的 SQL 语句在每次提交时,数据库都需行进译编。在行执大批类似的 SQL 语句时,可以用使 PreparedStatement 高提行执效率。用使 PreparedStatement 的另一个利益是不要需拼接 SQL 语句,代码的可读性更强。通过上述的化优以后,性能有了小幅度的晋升,从 40 秒左右降到了 30~35 秒左右。

    清单 3. 用使 Statement 的代码段片

    

Java代码 复制代码
  1. // 要需拼接 SQL 语句,行执效率不高,代码可读性不强   
  2. StringBuilder sql = new StringBuilder();    
  3. sql.append("insert into table1(column1,column2) values('");    
  4. sql.append(column1Value);    
  5. sql.append("','");    
  6. sql.append(column2Value);    
  7. sql.append("');");    
  8. Statement st;    
  9. try {    
  10.     st = con.createStatement();    
  11.     st.executeUpdate(sql.toString());    
  12. catch (SQLException e) {    
  13.     e.printStackTrace();    
  14. }   
// 要需拼接 SQL 语句,行执效率不高,代码可读性不强
StringBuilder sql = new StringBuilder(); 
sql.append("insert into table1(column1,column2) values('"); 
sql.append(column1Value); 
sql.append("','"); 
sql.append(column2Value); 
sql.append("');"); 
Statement st; 
try { 
    st = con.createStatement(); 
    st.executeUpdate(sql.toString()); 
} catch (SQLException e) { 
    e.printStackTrace(); 
}

    清单 4. 用使 PreparedStatement 的代码段片

    

Java代码 复制代码
  1. // 预译编 SQL 语句,行执效率高,可读性强   
  2. String sql = “insert into table1(column1,column2) values(?,?)”;    
  3. PreparedStatement pst = con.prepareStatement(sql);    
  4. pst.setString(1,column1Value);    
  5. pst.setString(2,column2Value);    
  6. pst.execute();   
// 预译编 SQL 语句,行执效率高,可读性强
String sql = “insert into table1(column1,column2) values(?,?)”; 
PreparedStatement pst = con.prepareStatement(sql); 
pst.setString(1,column1Value); 
pst.setString(2,column2Value); 
pst.execute();

    

    4.  针对插入数据库记载的化优 - 2

    

    用使 SQL 批处理。通过 java.sql.PreparedStatement 的 addBatch 法方将 SQL 语句加入到批处理,这样在调用 execute 法方时,就会一次性地行执 SQL 批处理,而不是逐条行执。通过上述的化优以后,性能有了小幅度的晋升,从 30~35 秒左右降到了 30 秒左右。

    

    5.  针对多线程的化优

    用使多线程现实发并 / 并行。

    

    清空数据库表的操纵、把从 2 个外部系统 D 取得的数据插入数据库记载的操纵,是互相立独的任务,可以给个每任务分配一个线程行执。清空数据库表的操纵该应先于数据库插入操纵实现,可以通过 java.lang.Thread 类的 join 法方制控线程行执的前后顺序。在核单 CPU 代时,操纵系统中某一时辰只有一个线程在行运,通过程进 / 线程调度,给个每线程分配一小段行执的时光片,可以现实多个程进 / 线程的发并(concurrent)行执。而在前目的多核多处理器景背下,操纵系统中一同时辰可以有多个线程并行(parallel)行执,大大地高提了算计度速。

    清单 5. 用使多线程的代码段片

    

Java代码 复制代码
  1. Thread t0 = new Thread(new ClearTableTask());    
  2. Thread t1 = new Thread(new StoreServersTask(ADDRESS1));    
  3. Thread t2 = new Thread(new StoreServersTask(ADDRESS2));    
  4.   
  5. try {    
  6.     t0.start();    
  7.     // 行执完清空操纵后,再行进后续操纵   
  8.     t0.join();    
  9.     t1.start();    
  10.     t2.start();    
  11.     t1.join();    
  12.     t2.join();    
  13. catch (InterruptedException e) {    
  14.     e.printStackTrace();    
  15. }    
  16.   
  17. // 开断数据库连接   
  18. try {    
  19.     JdbcUtil.close();    
  20. catch (SQLException e) {    
  21.     e.printStackTrace();    
  22. }   
Thread t0 = new Thread(new ClearTableTask()); 
Thread t1 = new Thread(new StoreServersTask(ADDRESS1)); 
Thread t2 = new Thread(new StoreServersTask(ADDRESS2)); 

try { 
    t0.start(); 
    // 行执完清空操纵后,再行进后续操纵
    t0.join(); 
    t1.start(); 
    t2.start(); 
    t1.join(); 
    t2.join(); 
} catch (InterruptedException e) { 
    e.printStackTrace(); 
} 

// 开断数据库连接
try { 
    JdbcUtil.close(); 
} catch (SQLException e) { 
    e.printStackTrace(); 
}

    通过上述的化优以后,性能有了大幅度的晋升,从 30 秒左右降到了 15 秒以下,10~15 秒之间。用使多线程而失掉的性能晋升的原因是,系统署部在所的服务器是多核多处理器的,用使多线程,给个每任务分配一个线程行执,可以充分地利用 CPU 算计源资。

    笔者试着给个每任务分配两个线程行执,希望能使程序行运得更快,但是适得其反,此时程序行运的时光反而比个每任务分配一个线程行执的慢,大约 20 秒。笔者揣测,这是因为线程较多(于对相 CPU 的内核数),使得 CPU 忙于线程的上下文切换,过量的线程上下文切换使得程序的性能反而不如之前。因此,要根据现实的硬件环境,给任务分配适当的线程行执。

    

    6.  针对计划式模的化优

    用使 DAO 式模抽象出数据问访层。

    

    本来的代码中混杂着 JDBC 操纵数据库的代码,代码结构显得分十纷乱。用使 DAO 式模(Data Access Object Pattern)可以抽象出数据问访层,这样使得程序可以立独于不同的数据库,即便问访数据库的代码发生了转变,下层调用数据问访的代码无需转变。并且程序员可以脱摆调单繁琐的数据库代码的编写,注专于业务逻辑层面的代码的发开。通过上述的化优以后,性能并未有晋升,但是代码的可读性、可扩展性大大地高提了。

    图 2. DAO 式模的层次结构

    

    清单 6. 用使 DAO 式模的代码段片

    

Java代码 复制代码
  1. // DeviceDAO.java,定义了 DAO 抽象,下层的业务逻辑代码引用该接口,面向接口编程   
  2. public interface DeviceDAO {    
  3.    public void add(Device device);    
  4. }    
  5.   
  6. // DeviceDAOImpl.java,DAO 现实,体具的 SQL 语句和数据库操纵由该类现实   
  7. public class DeviceDAOImpl implements DeviceDAO {    
  8.    private Connection con;    
  9.    public DeviceDAOImpl() {    
  10.        // 得获数据库连接,代码略去   
  11.    }    
  12. @Override    
  13. public void add(Device device) {    
  14.        // 用使 PreparedStatement 行进数据库插入记载操纵,代码略去   
  15.    }    
  16. }   
// DeviceDAO.java,定义了 DAO 抽象,下层的业务逻辑代码引用该接口,面向接口编程
 public interface DeviceDAO { 
    public void add(Device device); 
 } 

 // DeviceDAOImpl.java,DAO 现实,体具的 SQL 语句和数据库操纵由该类现实
 public class DeviceDAOImpl implements DeviceDAO { 
    private Connection con; 
    public DeviceDAOImpl() { 
        // 得获数据库连接,代码略去
    } 
 @Override 
 public void add(Device device) { 
        // 用使 PreparedStatement 行进数据库插入记载操纵,代码略去
    } 
 }

    顾回以上代码化优程过:闭关志日记载、享共数据库连接、用使预译编 SQL、用使 SQL 批处理、用使多线程现实发并 / 并行、用使 DAO 式模抽象出数据问访层,程序行运时光从最初的 100 秒左右降低到 15 秒以下,在性能上失掉了很大的晋升,同时也有具了更好的可读性和可扩展性。

    四、结束语

    通过该项目例实,笔者深深地觉得,想要写出一个性能化优、可读性可扩展性强的程序,要需对算计机系统的基本概念、道理,编程语言的特性,软件系统架构计划都有较入深的解理。“纸上得来终觉浅,绝知此事要躬行”,想要将这些基本理论、编程技能融会贯通,还要需不断地践实,并总结心得体会。

文章结束给大家分享下程序员的一些笑话语录: 这个世界上只有10种人:懂得二进制的和不懂得二进制的。

posted @ 2013-05-01 23:08  xinyuyuanm  阅读(173)  评论(0编辑  收藏  举报