• 代码程序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种人:懂得二进制的和不懂得二进制的。

  • 相关阅读:
    UVA 12284 Digital Matrix
    lightoj 1052
    light oj 1236
    light oj 1151
    省选准备 MISTAKE 大全
    我的省选 Day -15
    「FJ2014集训」采药人的路径
    【NOI2012】迷失游乐园
    寒假这十天
    计算几何 大杂烩
  • 原文地址:https://www.cnblogs.com/xinyuyuanm/p/3053820.html
Copyright © 2020-2023  润新知