关于java:java小记

47次阅读

共计 11403 个字符,预计需要花费 29 分钟才能阅读完成。

Java 零碎参数

零碎级全局变量,该参数能够在程序中任何地位都能够拜访到。优先级最高,笼罩程序中同名配置。
个别通过设置虚拟机参数来实现。
例如在 idea 中能够通过对应用程序进行配置:

Edit Configurations -> Add VM Options

零碎参数的规范格局为:-Dargname=agrvalue

多个参数之间用空格隔开,如果参数值两头有空格,则用引号括起来。
虚拟机零碎参数中设置的参数键值对,在程序中能够用 System.getProperty("propertyName") 获取对应参数值。

-X/-XX 为非标准零碎参数模式,个别与 JVM 虚拟机设置无关,参数名和值都由 JVM 标准规定。例如:-Xms : 初始堆大小、-Xmx : 最大堆大小。

Java 运行参数

main 办法执行时传入的参数值,如果参数有多个,用空格离开。
main 办法的个别格局为:public static void main(String[] args),其中,String[] args 就是存储运行参数的变量,在程序中能够间接应用。
Java 运行参数依然能够通过在 Edit Configurations 中设置

相似用法:

命令行中设置零碎参数或运行参数

当然也能够在 命令行 中对这两种参数进行设置
java 命令的根本格局为 java [-options] class [args...],其中:

  • [-options] 配置 Java 零碎参数
  • [args…] 配置 Java 运行参数

示例:java -Dfile.encoding=UTF-8 -Dmy=user Hi this is leo channel




字符串常量池(属于运行时常量池,只存储字符串)

参考博客(https://juejin.cn/post/693823…)(https://juejin.cn/post/685457…)

在 jdk6 中 StringTable 是固定的,就是 1009 的长度,所以如果常量池中的字符串过多就会导致效率降落很快。在 jdk7 中,StringTable 的长度能够通过一个参数指定:

  • -XX:StringTableSize=99991

1. 字符串常量池在 Java 内存区域的哪个地位

  • JDK7 之前版本字符串常量池是放在办法区中的,不过自从 JDK7 之后,Hotspot 虚拟机便将本来放在永恒代的字符串常量池移至堆中。

2. 字符串常量池是什么

  • 在 HotSpot 里实现的 string pool 性能的是一个固定大小的 HashTable,它是一个 Hash 表,默认值大小长度是 1009;这个 StringTable 在每个 HotSpot VM 的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了 StringTable 上,StringTable 外面存的是 key(字面量“abc”, 即驻留字符串)-value(字符串 ”abc” 实例对象在堆中的援用)键值对。
  • 在 JDK6.0 中,StringTable 的长度是固定的,长度就是 1009,因而如果放入 String Pool 中的 String 十分多,就会造成 hash 抵触,导致链表过长,当调用 String#intern()时会须要到链表上一个一个找,从而导致性能大幅度降落;
  • 在 JDK7.0 中,StringTable 的长度能够通过参数指定:
  • -XX:StringTableSize=66666

3. 字符串常量池里放的是什么

  • 在 JDK6.0 及之前版本中,String Pool 里放的都是字符串常量;
  • 在 JDK7.0 中,因为 String#intern()产生了扭转,因而 String Pool 中也能够寄存放于堆内的字符串对象的援用地址。
  • 须要阐明的是:字符串常量池中的字符串只存在一份!
    String s1 = "hello,world!";
    String s2 = "hello,world!";
    // 即执行完第一行代码后,常量池中已存在“hello,world!”,那么 s2 不会在常量池中申请新的空间,而是间接把已存在的字符串内存地址返回给 s2。

4.String a= “ha” 与 String a = new String(“ha”)区别

  • 间接定义的 ”ha” 是贮存在字符串常量池中;new String(“ha”)是 String 存储在堆中,“ha”在字符串常量池中;
  • 常量池中雷同的字符串只会有一个,然而 new String(“ha”)每 new 一个对象就会在堆中新建一个对象,不论这个值是否雷同;
  • String a =“ha”与 String b =“ha”:a b 都指向字符串常量池中的“ha”, 所以 a==b ;
  • String a = new String(“ha”)与 String b = new String(“ha”):是会在堆中创立两个对象,这两个对象的值都为 ha,所以 a!=b;a.equals(b)返回 true;
  • String a =“ha”在编译阶段就会在内存中创立,String a = new String(“ha”)是在运行时才会在堆中创建对象。

5. 字符串拼接状况

    应用””双引号创立:String s1 =“first”;
    应用字符串连接符拼接:String s2=”se”+”cond”;
    应用字符串加援用拼接:String s12=”first”+s2;
    应用 new String(“”)创立:String s3 = new String(“three”);
    应用 new String(“”)拼接:String s4 = new String(“fo”)+”ur”;
    应用 new String(“”)拼接:String s5 = new String(“fo”)+new String(“ur”);
    s1:中的”first”是字符串常量,在编译期就被确定了,先查看字符串常量池中是否含有”first”字符串, 若没有则增加”first”到字符串常量池中,并且间接指向它。所以 s1 间接指向字符串常量池的”first”对象。s2:“se”和”cond”也都是字符串常量,当一个字符串由多个字符串常量连贯而成时,它本人必定也是字符串常量,所以 s2 也同样在编译期就被解析为一个字符串常量,并且 s2 是常量池中”second”的一个援用。s12:JVM 对于字符串援用,因为在字符串的”+”连贯中,有字符串援用存在,而援用的值在程序编译期是无奈确定的,即 ("first"+s2) 无奈被编译器优化,只有在程序运行期来动态分配应用 StringBuilder 连贯后的新 String 对象赋给 s12。(编译器创立一个 StringBuilder 对象,并调用 append()办法,最初调用 toString()创立新 String 对象,以蕴含批改后的字符串内容)
    s3:用 new String() 创立的字符串不是常量,不能在编译期就确定,所以 new String() 创立的字符串不放入常量池中,它们有本人的地址空间。然而”three”字符串常量在编译期也会被退出到字符串常量池(如果不存在的话)s4:同样不能在编译期确定,然而”fo”和”ur”这两个字符串常量也会增加到字符串常量池中,并且在堆中创立 String 对象。(字符串常量池并不会寄存”four”这个字符串,会寄存“fo”这个字符串)

6.final 援用拼接

    public class StringConcat {
        final String a = "hello";
        final String b = "moto";
        String result = a + b + "2018";
    }
    • 对于 final 润饰的局部变量,它在编译时被解析为常量值的一个本地拷贝存储到本人的常量池中或嵌入到它的字节码流中。• 所以此时的 (a + b +“2018”) 和(“hello”+“moto”+“2018”)成果是一样的。



课外话对于 intern()函数的额定知识点:

援用
对于为什么增加了一个 append()函数之后后果不同的问题,参考博客(https://blog.csdn.net/qq_4491…)的解答

7.intern()办法

参考博客(https://tech.meituan.com/2014…)
重点!!!(https://blog.csdn.net/qq_4491…)

  • String.intern()是一个 Native 办法,它的作用是:如果字符串常量池曾经蕴含一个等于此 String 对象的字符串,则返回代表字符串常量池中这个字符串的 String 对象。【并不是堆中的字符串对象,而是字符串常量池中的字符串对象】;否则将此 String 对象的援用地址(堆中)增加到字符串常量池中,因为字符串常量池在堆中,所以没必要在字符串常量池中再多创立一份 String 对象。
  • jdk 1.7 后的字符串常量池存在于堆中。
  • jdk6 中的常量池是放在 Perm(永恒代)区中的,Perm 区和失常的 JAVA Heap 区域是齐全离开的。如果是应用引号申明的字符串都是会间接在字符串常量池中生成,而 new 进去的 String 对象是放在 JAVA Heap 区域。所以拿一个 JAVA Heap 区域的对象地址和字符串常量池的对象地址进行比拟必定是不雷同的,即便调用 String.intern 办法也是没有任何关系的。
  • 再说说 jdk7 中的状况。这里要明确一点的是,在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的 Perm 区的,Perm 区是一个类动态的区域,次要存储一些加载类的信息,常量池,办法片段等内容,默认大小只有 4m,一旦常量池中大量应用 intern 是会间接产生 java.lang.OutOfMemoryError: PermGen space 谬误的。所以在 jdk7 的版本中,字符串常量池曾经从 Perm 区移到失常的 Java Heap 区域了。为什么要挪动,Perm 区域太小是一个次要起因,当然据音讯称 jdk8 曾经间接勾销了 Perm 区域,而新建设了一个元区域。应该是 jdk 开发者认为 Perm 区域曾经不适宜当初 JAVA 的倒退了。
  • 在 intern()源码的注解上有说过一段话:
    All literal strings and string-valued constant expressions are interned. String literals are defined in section 3.10.5 of the The Java™ Language Specification.翻译如下
    所有的文字字符串 (这里指的应该是用 双引号 “” 包裹起来的字符串,比方 new String(“a”) 中的“a”), 以及字符形常量 (集体了解, 这里指的是 String a = “abc” 中的 a.) 都会具备 intern() 调用后的成果. 也就是, 前述两种字符串实例会进行判断、退出到字符串常量池中,也能够了解为前述两种定义主动调用 intern() 办法
    从这一段的正文咱们能够推断出:
    “” 包裹的字符串都会进入字符串常量池. 也就是说, String a = new String(“string”) 这一行代码, 会让 “string” 自身作为一个字符串对象进入字符串常量池、而不是 a 实例进入字符串常量池.
    此外, 从 intern() 办法的正文咱们能够晓得, 在执行 intern() 办法时会产生以下事件:
    测验指标字符串对象的具体值在字符串常量池中是否存在, 不存在则则将指标对象的援用放入字符串常量池, 并返回指标字符串的援用; 否则返回字符串常量池里与指标字符串对象内容雷同的对象援用
  • 举个例子阐明,例如 String s = new String(“1”) + new String(“1”),这时候产生了两个对象,一个是 s 指向的对象在堆上(该对象为“11”),此时在常量池中还生成了”1“对象,所以为两个对象。在 s 调用 intern() 函数后,会把 s 援用的对象”11“地址返回给字符串常量池(jdk7 当前常量池既能够寄存对象也能够寄存援用地址 ),这时候新建的一个变量 s2,使得s2 =“11”,s2 检测到常量池中有 ”11″ 的援用地址,就将寄存在常量池中 s 的援用地址返回给 s2,所以 s == s2,因为等号比拟的是援用是否相等(地址)(by the way,equals 是调用对象的 equals 办法进行判断。在 String 类中,equals 判断的是 String 外部用于存储字符串信息的 char 数组的每个元素是否齐全相等),所以 s 等于 s1,即两者地址雷同,而这种状况,在 jdk6 之前则天壤之别,应用 intern() 函数之后,jdk6 中是在字符串常量池中生成“11”这个字面量,而不是去存储放在堆中的”11“变量。
  • 应用 intern()比不应用 intern()耗费的内存更少
  • https://blog.csdn.net/vivianX… 该链接解释了为什么应用 intern()函数会节俭内存

    String aa=new String("abcdef");//"abcdef" 字符串对象,创立在字符串常量池
    String aaIntern=aa.intern();//aaIntern 为字符串常量池的 "abcdef" 对象
    System.out.println("aa==aaIntern"+(aa==aaIntern));//false   
    String aaStr="abcdef";
    System.out.println("aaIntern==aaStr"+(aaIntern==aaStr));//true
    System.out.println("aa==aaStr"+(aa==aaStr));//false  aa 在堆,aaStr 在字符串常量池           ——————————————  ——————————————  ——————————————  ——————————
    String bb = new String("123") + "456";
    String bbIntern = bb.intern();
    System.out.println("bb==bbIntern" + (bb == bbIntern));// true, 字符串常量池没有 "123456"
    String bbStr = "123456";
    System.out.println("bb==bbStr" + (bb == bbStr));// true
    System.out.println("bbIntern==bbStr" + (bbIntern == bbStr));// true
    String cc = new String("1") + "23";
    String ccIntern = cc.intern();// 字符串常量池曾经有 "123"
    System.out.println("cc==ccIntern" + (cc == ccIntern));// false



对于主动装箱和主动拆箱

先说整型常量池

java 中为了进步程序的执行效率,将 [-128, 127] 之间 256 个整数所有的包装对象提前创立好了,类加载时就曾经创好了,放在了一个办法区的“整数常量池”当中。

目标是:如果一个整数范畴在 [-128, 127] 外面的整数进行包装,包装时不须要再 new 对象了,间接从“整数常量池”中取出来。

池:就是缓存区的意思。缓存区的益处是:程序用起来执行很快,很不便。毛病是:如果没用到,就有点消耗了内存。

举例说,像 Integer 包装类,Integer 类源码中有定义 IntegerCache 子类,该类用来在编译期间向常量池中增加 Integer 常量????
如果两个援用指向同一个对象,用 == 示意它们是相等的。如果两个援用指向不同的对象,用 == 示意它们是不相等的,即便它们的内容雷同。即 == 判断的是两个对象的地址是否雷同,=== 判断的是两个对象内的值是否相等。

Integer bInteger=127;

它实际上在外部的操作是:

Integer bInteger=Integer.valueOf(127);

valueOf(int i)函数的源码:

public static Integer valueOf(int i) {
        assert IntegerCache.high >= 127;
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

能够看到,如果值的范畴在 -128 到 127 之间,它就从高速缓存返回实例。
所以…上面这两个指向同一个对象:

    Integer aInteger=127;
    Integer bInteger=127;

咱们能够失去 true。
另外,一个容易记错的点是

Integer i1 = new Integer(1)的时候是在 Java 堆中创立一个 Integer 对象,i1 指向堆中的对象,i1 与常量池没关系,而 Integer i2 = 1, 所以 i1==i2 为 false。

Integer e1 = 127;
Integer e2 = new Integer(127);
System.out.println(e1 == e2);  // false

private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer[] cache;
        static Integer[] archivedCache;

        static {
            // high value may be configured by property  常量池中的最大值
            int h = 127;
            String integerCacheHighPropValue =
                VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {h = Math.max(parseInt(integerCacheHighPropValue), 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
                } catch(NumberFormatException nfe) {// If the property cannot be parsed into an int, ignore it.}
            }
            high = h;

            // Load IntegerCache.archivedCache from archive, if possible
            CDS.initializeFromArchive(IntegerCache.class);
            int size = (high - low) + 1;

            // Use the archived cache if it exists and is large enough
            if (archivedCache == null || size > archivedCache.length) {Integer[] c = new Integer[size];
                int j = low;
                for(int i = 0; i < c.length; i++) {c[i] = new Integer(j++);
                }
                archivedCache = c;
            }
            cache = archivedCache;
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

在 Java7 之后,能够通过设置零碎参数来扩充 Integer 常量池的最大值,即更改 -Djava.lang.Integer.IntegerCache.high=<size>来批改

这是没被动去批改 java.lang.Integer.IntegerCache.high 的后果:

这是被动批改 java.lang.Integer.IntegerCache.high 的后果:




对于 String 的不可变性

参考至掘金博客:https://juejin.cn/post/6854573212886368270(对于 String 类的些许认识以及对 StringTable(字符串常量池)的性能调优)

 String s = "hello";

这时候,”hello” 字面量就被寄存在 StringTable(即字符串常量池)中,而变量 s 是一个援用,s 指向了 StringTable 中的 ”hello”

当把 s 的值改为 ”hello world”

String s = "hello";
s = "hello world";

如何去验证是指向了一个新的字符串而不是批改其内容呢,咱们能够打印一下 hash 值看看。

String s = "hello";
System.out.println(System.identityHashCode(s));
s = "hello world";
System.out.println(s.hashCode());
s = "hello";
System.out.println(System.identityHashCode(s));

能够看到,第一次和第三次的 hash 值一样,第二次 hash 值和其它两次不同,阐明的确是指向了一个新的对象而不是批改了 String 的值。

那么 String 是怎么实现不可变的呢? 咱们来看一下 String 类的源码:

从源码中咱们能够看出,首先 String 类是 final 的,阐明其不可被继承,就不会被子类扭转其不可变的个性;其次,String 的底层其实是一个被 final 润饰的数组,阐明这个 value 在确定值后就不能指向一个新的数组。这里咱们要明确一点,被 final 润饰的数组尽管不能指向一个新的数组,但却是能够批改数组的值的:

既然能够被批改,那 String 怎么是不可变的呢?因为 String 类并没有提供任何一个办法去批改数组的值,所以 String 的不可变性是因为其底层的实现,而不是一个 final。
那么 String 为什么要设计成不可变的呢? 我感觉是因为出于安全性的考量,试想一下,在一个程序中,有多个中央同时援用了一个雷同的 String 对象,然而你可能只是想在一个中央批改 String 的内容,要是 String 是可变的,导致了所有的 String 的内容都扭转了,万一这是在一个重要场景下,比方传输明码什么的,不就出大问题了吗。所以 String 就被设计成了不可变的。

字符串的拼接

看上面一段程序

public static void main(String[] args) {
    String a = "hello";
    String b = "world!";
    String c = a+b;
}

咱们来看一下这段代码对应的字节码指令:

重点看一下用红色标注的几行代码,看不懂后面的字节码指令没关系,能够看前面的正文。能够看到,字符串拼接其实就是调用 StringBuilder 的 append()办法,而后调用了 toString()办法返回一个新的字符串。




对于类加载时动态代码块、静态方法、动态变量、一般代码块、构造方法的执行程序

首先定义父类:

public class Father {
    static int age = 10;

    static {System.out.println("Father 动态代码块");
    }

    {System.out.println("Father 一般代码块");
    }

    public static void leo(){System.out.println("Father 静态方法");
    }

    public Father(){System.out.println("Father 无参构造函数");
    }
}

子类继承父类:

public class Son extends Father{
    static {System.out.println("Son 动态代码块");
    }

    {System.out.println("Son 一般代码块");
    }

    public Son(){System.out.println("Son 无参构造函数");
    }

    public static void main(String[] args) {Father.leo();
        new Son();
        System.out.println(Father.age);
    }
}

运行后果如下:

能够晓得,首先当类加载的时候,会 主动 加载 动态代码块 动态代码块 静态方法 都是和类 一起 加载的,并且 动态变量 静态方法 是依据调用程序来执行的。

当咱们运行 Son 类的时候,会产生什么呢?
首先当虚拟机加载 Son 类时,它会主动加载动态代码块,因为 Demo 继承了 Father 类,它会先去找 Father 类当中有没有动态代码块,如果有,则按程序运行父类中的动态代码块。如果执行完或没有,则运行以后类的静态方法。故会顺次输入 Father 动态代码块–>Son 动态动态代码块 ->Father 静态方法。而后执行 new Son();在执行子类的实例化之前,虚拟机又会去父类找一般代码块,如果有则执行,没有则会去调用父类的无参构造方法。随后去调用子类的一般代码块,而后才去执行子类的构造方法。所以会顺次输入:Father 一般代码块–>Father 无参构造方法–>Son 一般代码块–>Son 无参构造函数。随后执行 System.out.println(“Father.age”); 语句,输入 10。

留神点:

因为遵行先申明先执行的准则,所以在打印语句的时候,因为还没申明 age 属性,所以会报“非法向前利用”谬误。

留神:(动态)变量和(动态)代码块的也是有执行程序的,与代码书写的程序统一。在(动态)代码块中能够应用(动态)变量,然而被应用的(动态)变量必须在(动态)代码块后面申明。

即存在以下执行步骤:

1、父类动态变量和动态代码块(先申明的先执行);

2、子类动态变量和动态代码块(先申明的先执行);

3、父类的变量和代码块(先申明的先执行);

4、父类的构造函数;

5、子类的变量和代码块(先申明的先执行);

6、子类的构造函数。

执行工夫:

1. 父类动态代码块(java 虚拟机加载类时,就会执行该块代码,故只执行一次)

2 . 子类动态代码块(java 虚拟机加载类时,就会执行该块代码,故只执行一次)

3. 父类属性对象初始化

4. 父类一般代码块(每次 new, 每次执行)

5. 父类构造函数(每次 new, 每次执行)

6. 子 类 属性对象初始化

7. 子类一般代码块(每次 new, 每次执行)

8. 子 类构造函数(每次 new, 每次执行)

总结

当加载 Demo 类时,JVM 会去调用该类父类的动态代码块,父类的动态代码块运行完后去执行子类的动态代码块。当子类的动态代码块执行结束后 JVM 又会去找父类中的一般代码块,当父类的一般代码块执行结束后又会调用父类的构造方法。父类的构造方法执行结束后,JVM 又会去看子类中有没有一般代码块,如果有则执行。执行结束后,会去调用子类的构造方法。这就是他们之间的程序
留神:java 中所有类都是 Object 类的子类。当父类中含有有参结构而没有显示的写出无参结构,则子类必须用 super 关键字显示的调用父类的有参结构,否则编译将不通过。

对于类加载器

根本汇合类的接口继承关系和实现

对于重写 equals()办法就得要重写 hashcode()办法

blog:https://www.shouxicto.com/art…(略微有点帮忙)
blog:https://blog.csdn.net/javazej…
这个问题次要是针对映射相干的操作(Map 接口)。学过数据结构的同学都晓得 Map 接口的类会应用到键对象的哈希码,当咱们调用 put 办法或者 get 办法对 Map 容器进行操作时,都是依据键对象的哈希码来计算存储地位的,因而如果咱们对哈希码的获取没有相干保障,就可能会得不到预期的后果。在 java 中,咱们能够应用 hashCode()来获取对象的哈希码,其值就是对象的存储地址,这个办法在 Object 类中申明,因而所有的子类都含有该办法。
————————————————
版权申明:本文为 CSDN 博主「zejian_」的原创文章,遵循 CC 4.0 BY-SA 版权协定,转载请附上原文出处链接及本申明。
原文链接:https://blog.csdn.net/javazej…
equals()办法的底层源码调用了“==”

public boolean equals(Object obj) {return (this == obj);     }

为什么?因为是规定。当重写了 equals()办法而没重写 hashcode()办法时,equals 返回的是 TRUE,但 hashcode()就不肯定了,所以为了规定,须要重写,不能违反规定。

正文完
 0