• System.out.printf使用以及注意点


    一、System.out.printf格式化输出

    1、常用控制符

    控制符

    说明

    %d

    按十进制整型数据的实际长度输出。

    %ld

    输出长整型数据。

    %md

    m 为指定的输出字段的宽度。如果数据的位数小于 m,则左端补以空格,若大于 m,则按实际位数输出。

    %u

    输出无符号整型(unsigned)。输出无符号整型时也可以用 %d,这时是将无符号转换成有符号数,然后输出。但编程的时候最好不要这么写,因为这样要进行一次转换,使 CPU 多做一次无用功。

    %c

    用来输出一个字符。

    %f

    用来输出实数,包括单精度和双精度,以小数形式输出。不指定字段宽度,由系统自动指定,整数部分全部输出,小数部分输出 6 位,超过 6 位的四舍五入。

    %.mf

    输出实数时小数点后保留 m 位,注意 m 前面有个点。

    %o

    以八进制整数形式输出,这个就用得很少了,了解一下就行了。

    %s

    用来输出字符串。用 %s 输出字符串同前面直接输出字符串是一样的。但是此时要先定义字符数组或字符指针存储或指向字符串,这个稍后再讲。

    %x(或 %X 或 %#x 或 %#X)

    以十六进制形式输出整数,这个很重要。

    代码演示:

    public static void main(String[] args) {
    2         //最常用的主要是三个:字符串 %s, 整型%d, 浮点型保留小数位%.mf(m表示小数点后m位), 
    表示换行符
    3         System.out.printf("*学生资料*
     姓名:%s
     年龄:%d岁
     考试成绩(保留两位小数): %.2f
    ", 
                                "小明", 15, 98.456);
    4     }

    控制台显示:

    另外System.out.printf有一定程度的输出格式化效果

    输出结果:

    如果 使用System.out.print(ln)格式就出现了明显不同

    2、示例代码

    package system.out;
     
    public class Printf
    {
     
        public static void main(String[] args)
        {
            //%代表格式化
            //f代表输出浮点数,9代表输出长度,如果浮点数长度不足,则补空格,如果浮点数长度超出,则按实际长度输出,2代表保留小数点后几位小数
            System.out.printf("%9.2f",1111.3);
            System.out.println();
            //-号代表向左对齐,默认向右对齐
            System.out.printf("%-9.2f", 1111.3);
            System.out.println();
            //+号代表显示正负号
            System.out.printf("%+9.2f", 1111.3);
            System.out.println();
            //+-号代表显示正负号,且向左对齐
            System.out.printf("%+-9.2f", 1111.3);
            System.out.println();
            //d代表输出整数
            System.out.printf("%4d",15);
            System.out.println();
            //o代表输出8进制整数
            System.out.printf("%-4o",15);
            System.out.println();
            //x代表输出16进制整数
            System.out.printf("%-4x",15);
            System.out.println();
            //#x代表输出带有16进制标志的整数
            System.out.printf("%#x",15);
            System.out.println();
            //s代表输出字符串
            System.out.printf("%-8s", "我们是中心");
            System.out.println();
            //x$,整数加$表示第几个变量,如果不加,变量按默认顺序排列
            System.out.printf("%2$-5s:夺得世界杯总冠军,进球数:%1$3d,对方进球:%3$2d", 4,"法国",2);
        }
    }

    二、看下底层代码实现

    public PrintStream printf(String format, Object ... args) {
            return format(format, args);
        }
    public PrintStream format(String format, Object ... args) {
            try {
                synchronized (this) {
                    ensureOpen();
                    if ((formatter == null)
                        || (formatter.locale() != Locale.getDefault()))
                        formatter = new Formatter((Appendable) this);
                    formatter.format(Locale.getDefault(), format, args);
                }
            } catch (InterruptedIOException x) {
                Thread.currentThread().interrupt();
            } catch (IOException x) {
                trouble = true;
            }
            return this;
        }

    可以看到和String.format底层实现类似调用Formatter()类的format方法。

    public static String format(String format, Object... args) {
            return new Formatter().format(format, args).toString();
        }

    三、接下来分析下 String.format与StringBuilder与String +比较

    1、测试代码:

    class StringTest {
    
        public static void main(String[] args) {
           //  testOperatorPlus();
            //testAppend();
           testFormat();
        }
    
        private static void testFormat() {
            Runtime runtime = Runtime.getRuntime();
            long memory;
            long prev_time;
            int i;
            long time;
            StringBuilder sb = new StringBuilder();
            memory = runtime.freeMemory();
            prev_time = System.currentTimeMillis();
            for (i = 0; i < 10000; i++) {
                String s = String.format("Blah %d Blah %d Blah %d", i, i, i);
            }
            long ww=runtime.freeMemory();
            time = System.currentTimeMillis() - prev_time;
            memory = memory - ww;
            System.out.println("Time: " + time + "    Memory Usage: " + memory);
        }
    
        private static void testAppend() {
            Runtime runtime = Runtime.getRuntime();
            long memory;
            long prev_time;
            int i;
            long time;
            StringBuilder sb = new StringBuilder();
            memory = runtime.freeMemory();
            prev_time = System.currentTimeMillis();
            for (i = 0; i < 10000; i++) {
                sb.append("Blah ");
                sb.append(i);
                sb.append("Blah ");
                sb.append(i);
                sb.append("Blah ");
                sb.append(i);
            }
            time = System.currentTimeMillis() - prev_time;
            memory = memory - runtime.freeMemory();
            System.out.println("Time: " + time + "    Memory Usage: " + memory);
        }
    
        private static void testOperatorPlus() {
            Runtime runtime = Runtime.getRuntime();
            long memory;
            long prev_time;
            int i;
            long time;
            StringBuilder sb = new StringBuilder();
            memory = runtime.freeMemory();
            prev_time = System.currentTimeMillis();
            for (i = 0; i < 1000000; i++) {
                String s = "Blah " + i + "Blah " + i + "Blah " + i;
            }
            time = System.currentTimeMillis() - prev_time;
            memory = memory - runtime.freeMemory();
            System.out.println("Time: " + time + "    Memory Usage: " + memory);
        }
    }
    View Code

    结果如下

    Method Time(ms) Memory Usage(long)
    ‘+’ operator 102 44053736
    StringBuilder.append 6 884768
    String.foramt 110 22639000
      
      
      可以看到StringBuilder.append的执行时间和内存占用都是最优的。'+'运算符比直接调用StringBuilder.append要慢上不少,特别是要连接的字符串数量较多时,内存占用也特别大。String.format由于每次都有生成一个Formatter对象,较慢也是情理之中。
    分析下String.format源码可以看到底层也用到了StringBuilder
    public static String format(String format, Object... args) {
            return new Formatter().format(format, args).toString();
        }
    public Formatter() {
            this(Locale.getDefault(Locale.Category.FORMAT), new StringBuilder());
        }

    四、由此引发的优化探讨

    在编码中 System.out.println将对象结果输出到控制台,会花费大量的CPU资源,因此发布的代码中不要包含System.out.println 或 System.out.printf。

    使用日志框架代替,生产环境注意控制输出级别。

    即便使用日志框架也要注意输出编码方式,例如
    反例(不要这么做):
    logger.debug("Processing trade with id: " + id + " symbol: " + symbol);

    字符串拼接,这样会产生很多String对象,占用空间,影响性能。

    另外关于日志输出其他建议:

    1、使用[]进行参数变量隔离

    如有参数变量,应该写成如下写法:

    logger.debug("Processing trade with id:[{}] and symbol : [{}] ", id, symbol);
    这样的格式写法,可读性更好,对于排查问题更有帮助。
    2、并不是所有的service都进行出入口打点记录,单一、简单service是没有意义的(job除外,job需要记录开始和结束,)。
    反例(不要这么做):
    public List listByBaseType(Integer baseTypeId) {
     log.info("开始查询基地");
     BaseExample ex=new BaseExample();
     BaseExample.Criteria ctr = ex.createCriteria();
     ctr.andIsDeleteEqualTo(IsDelete.USE.getValue());
     Optionals.doIfPresent(baseTypeId, ctr::andBaseTypeIdEqualTo);
     log.info("查询基地结束");
     return baseRepository.selectByExample(ex);
    }

    对于复杂的业务逻辑,需要进行日志打点,以及埋点记录,比如电商系统中的下订单逻辑,以及OrderAction操作(业务状态变更)。

    如果所有的service为SOA架构,那么可以看成是一个外部接口提供方,那么必须记录入参。
    调用其他第三方服务时,所有的出参和入参是必须要记录的(因为你很难追溯第三方模块发生的问题)

    3、 生产环境需要关闭DEBUG信息

    如果在生产情况下需要开启DEBUG,需要使用开关进行管理,不能一直开启。

     
     
     
    参考文章:
  • 相关阅读:
    Redis 错误摘记篇
    搭建备份到业务迁移---mysql
    业务迁移---web
    业务迁移---redis
    redis 编译安装错误问题
    location 匹配规则 (NGINX)
    nginx虚拟目录实现两个后台使用
    零基础学python-19.10 生成器是单迭代器
    零基础学python-19.9 生成器函数与生成器表达式
    零基础学python-19.8 生成器表达式:当迭代器遇上列表解析
  • 原文地址:https://www.cnblogs.com/better-farther-world2099/p/11989696.html
Copyright © 2020-2023  润新知