Java

作者: 技术灭霸 | 来源:发表于2020-04-25 23:51 被阅读0次

    1、一个字符(英文字母)占多少个字节,一个中文占多少字节?

    • 一个字符占1个字节(GBK、ASCII、UTF-8)
    • 一个中文占 2 个字节(GBK、ASCII)
    • 一个中文占 3 个字节(UTF-8)

    2、 Double是怎么比较两个值的大小

    推荐使用BigDecimal

            double a = 0.01;  
            double b = 0.001;  
            BigDecimal data1 = new BigDecimal(a);  
            BigDecimal data2 = new BigDecimal(b);  
            System.out.print(new DoubleCompare().compare(data1, data2));  
    

    3、==和equals的区别?实现equals要注意哪些东西?

    ==和equals的区别

    • ==:判断两个字符串在内存中首地址是否相同,即判断两者是否是同一个字符串对象
    • equles():如果没有重写equals()方法比较的是对象的地址,因为对Object来说对象没有什么属性可以比较,只能比较最底层的地址。
      而如果重写equals()方法时,该方法的对象因为是Object的子类,所以调用时会调用子类对象里面的方法.所以只有重写equals()方法后,两者比较的才是内容.或者说重写可以使自己定义比较的规则,不想按照地址去比较.

    实现equals要注意哪些东西?
    1、自反性:对于任何非空引用x,x.equals(x)应该返回true。
    2、对称性:对于任何引用x和y,如果x.equals(y)返回true,那么y.equals(x)也应该返回true。
    3、传递性:对于任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true。
    4、一致性:如果x和y引用的对象没有发生变化,那么反复调用x.equals(y)应该返回同样的结果。
    5、非空性:对于任意非空引用x,x.equals(null)应该返回false。

    4、&& 和 & 的区别、 || 和 | 的区别

    • &:在算术运算时分别计算表达式两边的结果,再作&运算。在位运算时&表示按位与
    • &&:短路与运算,先计算左边的表达式,如果结果是false,那么不用计算右边表达式,直接返回false。

    |与||的原理同上。短路与 或 短路或的计算效率更高,建议使用。

    5、HashMap

    HashMap查询时间复杂度
    hashMap除了超过负载因子的时候会扩容,还有什么情况下会扩容?
    一种是元素达到阀值了,一种是HashMap准备树形化但又发现数组太短(没有达到64)

    HashMap是如何存储空key的?
    空key的hash值为0,创建hash为0,key为null的node。

    6、ConcurrentModificationException异常出现的原因

    原因:如果modCount不等于expectedModCount,则抛出ConcurrentModificationException异常。
    关键点就在于:调用list.remove()方法导致modCount和expectedModCount的值不一致。

    1、在单线程环境下的解决办法

    使用iterator删除,并且调用iterator的remove方法,不是list的remove方法

    2、在多线程环境下的解决方法

    1、在使用iterator迭代的时候使用synchronized或者Lock进行同步;
    2、使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。

    7、equals()与hashCode()之间的关系

    • 如果两个对象equals()方法相等则它们的hashCode返回值一定要相同,如果两个对象的hashCode返回值相同,但它们的equals()方法不一定相等。
    • hashCode()的作用是为了提高在散列结构存储中查找的效率
    • Java中重写equals()方法时尽量要重写hashCode()方法的原因:声明相等对象必须具有相等的哈希码,包括 HashMap、HashSet、Hashtable 等

    8、ConcurrentHashMap

    Segment(分段锁)技术:将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。

    1.8的优化:采用Node + CAS + Synchronized来保证并发安全进行实现,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。

    CAS主要用于修改sizeCtl的值
    sizeCtl :默认为0,用来控制table的初始化和扩容操作。
    **-1 **代表table正在初始化
    **-N **表示有N-1个线程正在进行扩容操作
    其余情况:
    1、如果table未初始化,表示table需要初始化的大小。
    2、如果table初始化完成,表示table的容量,默认是table大小的0.75倍,居然用这个公式算0.75(n - (n >>> 2))。

    Synchronized

    • 把新的Node节点按链表或红黑树的方式插入到合适的位置,这个过程采用同步内置锁实现并发
    • 生成树节点的代码块是同步的,进入同步代码块之后,再次验证table中index位置元素是否被修改过

    9、使用final的意义

    1、为方法“上锁”,防止任何继承类改变它的本来含义和实现。设计程序时,若希望一个方法的行为在继承期间保持不变,而且不可被覆盖或改写,就可以采取这种做法。
    2、提高程序执行的效率,将一个方法设成final后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用里(内嵌机制)
    3、如果一个数据既是static又是final,那么它会拥有一块无法改变的存储空间

    10、多态的好处与实现原理

    好处

    1、提高了代码的维护性(继承保证)
    2、提高了代码的扩展性(由多态保证)
    多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在复运行时,可以通过指向基类的指针,来调用实现派生类中的方法。

    实现原理

    多态是面向对象编程语言的重要特性,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定。

    Java 对于方法调用动态绑定的实现主要依赖于方法表,但通过类引用调用(invokevitual)和接口引用调用(invokeinterface)的实现则有所不同

    11、Java8新特性

    函数式接口与Lambda表达式之间的关系:lambda表达式相当于是一个行为,传入函数式接口中,进来实现各种操作,即行为参数化它们的接口内只有一个抽象方法,每一个函数式接口都有@FunctionalInterface注解。

    • 使原本需要用匿名类实现接口来传递行为,现在通过 Lambda 可以更直观的表达。
    • Lambda 表达式是一个匿名函数,即没有函数名的函数。有些函数如果只是临时一用,而且它的业务逻辑也很简单时,就没必要非给它取个名字不可。
    • 允许把函数作为一个方法的参数

    形参列表=>函数体
    (parameters) -> expression


    image.png

    -> 返回
    : 等于
    map() 类型转换、映射

    12、exception和error区别

    都继承自Throwable类
    Exception
    1.可以是可被控制(checked) 或不可控制的(unchecked)。
    2.表示一个由程序员导致的错误。
    3.应该在应用程序级被处理。
    比如 NullPointerException、IndexOutOfBoundsException、 IOException、ClassNotFoundException

    Error
    1.总是不可控制的(unchecked)。
    2.经常用来用于表示系统错误或低层资源的错误。
    3.如何可能的话,应该在系统级被捕捉。
    比如栈溢出(StackOverflowError)、堆溢出(OutOfMemoryError:java heap space)

    image.png

    13、如何去设计类和接口(Effective Java)

    1、使类和成员的可访问性最小化

    尽可能地使每个类或者成员不被外界访问,尽可能最小的访问级别。

    2、复合优先于继承

    与方法调用不同的是,继承打破了封装性。超类的实现有可能会随着发行版本的不同而有所变化,如果真的发生了变化,子类可能会遭到破坏,即使它的代码完全没有改变。

    建议新的类中增加一个私有域,它引现有类的一个实例。这种设计被称做“复合(composition)

    3、接口优于抽象类

    如果你希望让两个类扩展同一个抽象类,就必须把抽象类放到类型层次结构的高处,以便这两个类的一个祖先成为它的子类。遗憾的是这样做会间接到伤害到类层次,迫使这个公共祖先到所有后代类都扩展这个新的抽象类,无论它对于这些后代类是否合适。

    4、优先考虑静态成员类

    非静态成员类的每个实例都隐含着与外围类的一个外围实例(enclosing instance)相关联。

    14、ArrayList和LinkedList的区别

    • 底层实现:ArrayList是实现了基于动态数组的数据结构,而LinkedList是基于链表的数据结构,ArrayList需要扩容、LinkedList不需要
    • 时间复杂度:对于随机访问get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针
    • 使用场景:LinkedList是个双向链表,它同样可以被当作栈、队列或双端队列来使用。

    15、HashMap的hash函数原理

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    

    Java 8中这一步做了优化,只做一次16位右位移异或混合,而不是四次,但原理是不变的。

    优化了高位运算的算法,通过hashCode()的高16位异或低16位实现的,主要是从速度、功效、质量来考虑的

    16、为什么notify和wait方法必须在synchronized方法中使用?

    1、依赖锁对象的监视器monitor

    这是因为调用这三个方法之前必须拿要到当前锁对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象,又因为monitor存在于对象头的Mark Word中(存储monitor引用指针),而synchronized关键字可以获取monitor ,所以,notify/notifyAll和wait方法必须在synchronized代码块或者synchronized方法中调用。

    2、避免lost wake up问题

    因为会导致lost wake up问题,说白了就唤不醒消费者


    image.png

    为了避免出现这种lost wake up问题,Java强制我们的wait()/notify()调用必须要在一个同步块中。

    17、finally方法一定会被执行么?

    java中,如果想要执行try中的代码之后,不允许再执行finally中的代码,有以下两种方式:

    • 使用System.exit(1)来退出虚拟机
    • 把当前执行trycatchfinally代码的线程设置为守护线程

    18、# 为什么volatile能保证可见性?

    内存屏障(memory barrier) 是一个CPU指令。基本上,它是这样一条指令:
    a) 确保一些特定操作执行的顺序;
    b) 影响一些数据的可见性(可能是某些指令执行后的结果)。编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障, 相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。内存屏障另一个作用是强制更新一次不同CPU的缓存。例如,一个写屏障会 把这个屏障前写入的数据刷新到缓存,这样任何试图读取该数据的线程将得到最新值,而不用考虑到底是被哪个cpu核心或者哪颗CPU执行的。

    内存屏障和volatile什么关系?上面的虚拟机指令里面有提到,如果你的字段是volatile,Java内存模型将在写操作后插入一个写屏障 指令,在读操作前插入一个读屏障指令。这意味着如果你对一个volatile字段进行写操作,你必须知道:
    1、一旦你完成写入,任何访问这个字段的线程将 会得到最新的值。
    2、在你写入前,会保证所有之前发生的事已经发生,并且任何更新过的数据值也是可见的,因为内存屏障会把之前的写入值都刷新到缓存。

    明白了内存屏障这个CPU指令,回到前面的JVM指令:从Load到store到内存屏障,一共4步,其中最后一步jvm让这个最新的变量的值在所有线程可见,也就是最后一步让所有的CPU内核都获得了最新的值,但中间的几步(从Load到Store)是不安全的,中间如果其他的CPU修改了值将会丢失。

    所以volatile不能保证i++操作的原子性

    19、值传递和引用传递的区别?

    https://mp.weixin.qq.com/s/4efxpvxOAzg1E4eLIsRLiw
    值传递:
    在方法被调用时,实参通过形参把它的内容副本传入方法内部,此时形参接收到的内容是实参值的一个拷贝,因此在方法内对形参的任何操作,都仅仅是对这个副本的操作,不影响原始值的内容。

     public static void valueCrossTest(int age, float weight){
            System.out.println("传入的age:" + age);
            System.out.println("传入的weight:" + weight);
            age = 33;
            weight = 89.5f;
            System.out.println("方法内重新赋值后的age:" + age);
            System.out.println("方法内重新赋值后的weight:" + weight);
        }
    
        public static void main(String[] args) {
            int a = 25;
            float w = 77.5f;
            valueCrossTest(a, w);
            System.out.println("方法执行后的age:" + a);
            System.out.println("方法执行后的weight:"+w);
        }
    
    传入的age:25
    传入的weight:77.5
    方法内重新赋值后的age:33
    方法内重新赋值后的weight:89.5
    方法执行后的age:25
    方法执行后的weight:77.5
    
    image.png

    只是改变了当前栈帧(valueCrossTest方法所在栈帧)里的内容,当方法执行结束之后,这些局部变量都会被销毁,mian方法所在栈帧重新回到栈顶,成为当前栈帧,再次输出a和w时,依然是初始化时的内容。

    值传递传递的是真实内容的一个副本,对副本的操作不影响原内容,也就是形参怎么变化,不会影响实参对应的内容。

    引用传递:
    ”引用”也就是指向真实内容的地址值,在方法调用时,实参的地址通过方法调用被传递给相应的形参,在方法体内,形参和实参指向通愉快内存地址,对形参的操作会影响的真实内容

     public static void PersonCrossTest(Person person){
            System.out.println("传入的person的name:"+person.getName());
            person.setName("我是张小龙");
            System.out.println("方法内重新赋值后的name:"+person.getName());
        }
    
        public static void main(String[] args) {
            Person p = new Person();
            p.setName("我是马化腾");
            p.setAge(45);
            PersonCrossTest(p);
            System.out.println("方法执行后的name:"+p.getName());
        }
    
    传入的person的name:我是马化腾
    方法内重新赋值后的name:我是张小龙
    方法执行后的name:我是张小龙
    

    可以看出,person经过personCrossTest()方法的执行之后,内容发生了改变,这印证了上面所说的“引用传递”,对形参的操作,改变了实际对象的内容。

    修改一下

      public static void PersonCrossTest(Person person){
            System.out.println("传入的person的name:"+person.getName());
            person=new Person();//加多此行代码
            person.setName("我是张小龙");
            System.out.println("方法内重新赋值后的name:"+person.getName());
        }
    
    传入的person的name:我是马化腾
    方法内重新赋值后的name:我是张小龙
    方法执行后的name:我是马化腾
    

    JVM需要在堆内另外开辟一块内存来存储new Person(),假如地址为“xo3333”,那此时形参person指向了这个地址,假如真的是引用传递,那么由上面讲到:引用传递中形参实参指向同一个对象,形参的操作会改变实参对象的改变。

    20、Java中8种基本数据类型是哪些?

    byte(1)-> boolean(1) -> short(2)-> char(2)-> int(4)-> float(4)-> long(8)-> double(8)

    21、Java中文乱码原理和解决方法

    Java乱码主要有两种原因(都和字节流有关):
    1、Java和JSP源文件的保存方式是基于字节流的,如果Java和JSP编译成class文件 过程中,使用的编码方式与源文件的编码不一致,就会出现乱码。
    2、Java程序与这些 媒介交互(如数据库,文件,流等的存储方式都是基于字节流的)时就会发生字符(char)与字节(byte)之间的转换

    第一种解决方法:
    基于这种乱码,建议在Java文件中尽量不要写中文(注释部分不参与编译,写中文没关系), 如果必须写的话,尽量手动带参数-ecoding GBK或-ecoding gb2312编译;对于JSP,在文件头加上<%@ page contentType="text/html;charset=GBK"%>或<%@ page contentType="text/html;charset=gb2312"%>基本上就能解决这类乱码问题。

    第二种解决方法:

    1. 更改 D:\Tomcat\conf\server.xml,指定浏览器的编码格式为“简体中文”:
      URIEncoding='GBK'
    2. 更改 Java 程序, response.setContentType("text/html; charset=GBK");
    3. 通过byte流修改:name = new String(name.getBytes("iso-8859-1"),"utf-8");
    4. 设置编码格式:post请求:request.setCharacterEncoding("utf-8");
    5. 添加filter过滤器,在web.xml中添加过滤器:它的作用是让浏览器把Unicode字符转换为GBK字符

    23、重载

    在编译器眼里,方法名称+参数类型+参数个数,组成一个唯一键,称为方法签名。返回值并不是方法签名的一部分,会导致编译出错。

    一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

    24、基本数据类型

    image.png

    25、对象头的内部结构

    image.png

    26、什么情况finally不会执行

    1、没有进入try代码块。
    2、进入try代码块 , 但是代码运行中出现了死循环或死锁状态。
    3、进入try代码块, 但是执行了 System.exit()操作。

    注意, finally 是在 return 表达式运行后执行的 , 此时将要 return 的结果 已 经被暂 存起来 , 待 finally 代码块执行结束后再将之前暂存的结果返回

     private static int test1() {
            int tmp = 10000;
            try {
                throw new Exception();
            } catch (Exception e) {
               return ++tmp;
            } finally {
               tmp = 99999;
            }
        }
    
    

    此方法最终的返回值是 10001 ,而不是 99999。

    相对在 finally 代码块中赋值,更加危险的做法是在 finally块中使用 return 操作,这样的代码会使返回值变得非常不可控。

     private static int test1() {
     int x = 1;
     int y = 10;
     int z = 100;
            try {
               return ++x;
            } catch (Exception e) {
               return ++y;
            } finally {
               return ++z;
            }
        }
    
    

    ( 1 )最后 return 的功件是由 finally 代码块巾的 return ++z 完成的,所以为法返 回的结果是 101。
    ( 2 )语旬 return ++x 中的++x 被成功执行,所以运行结果是x=2。
    ( 3 ) 如果有异常抛出 ,那么运行结果将会是 y =11,而 x=1;

    finally代码块中使用 return语旬,使返回值的判断变得复杂,所以避免返回值不
    可控,我们不要在 finally代码块中使用 return语句。

    27、集合

    我们再回到之前 sort()方法中的 TimSort 算法 ,
    是归并排序( Merge Sort )与插入排序( Insertion Sort )优化后的排序算法。

    分析Comparable接口的排序原理(二叉树中序排序)

    实际上比较器的操作,就是经常听到的二叉树的排序算法。通过二叉树进行排序,之后利用中序遍历的方法把内容依次读取出来。


    image.png

    排序的基本原理,使用第一个元素作为根节点,之后如果后面的内容比根节点要大,则放在左子树,如果内容比根节点的内容要大,则放在右子树。

    然后以中序遍历(左根右)输出!

    28、hashCode 和 equals

    ( 1 )如果两个对象的 equals 的结果是相等的 . 则两个对象的 hashCode 的返回值也必须是相同的。
    ( 2 )任何时候 覆写 equals, 都必须同时覆写 hashCode。

    29、fail-fast机制

    这种机制经常出现在多线程环境下 , 当前线程会维护一个计数比较器, 即 expectedModCount, 记录已经修改的次数。在进入遍历前, 会把实时修改次数 modCount 赋值给 expectedModCount,如果这两个数据不相等 , 则抛出异常。

    Iterator、COW(Copy-on-write)是 fail-safe机制的

    30、JAVA开发六大原则

    1. 单一原则 : 一个类或一个方法只负责一件事情
    2. 里斯替换原则: 子类不应该重写父类已实现的方法,重载不应该比父类的参数更少
    3. 依赖倒置原则: 面向接口编程.(面向接口更能添加程序的可扩展性)
    4. 接口隔离原则: 接口中的方法应该细分,要合理的隔离开不同的功能到不同的接口中.
    5. 迪米特原则: 高内聚低耦合
    6. 开闭原则: 对修改关闭,对扩展开放
      总结: 用抽象构建框架,用实现扩展细节…

    31、HashMap和LinkedHashMap的区别

    HashMap的无序其实也有迹可循, 即按照桶下标先后排序;如果有哈希碰撞的情况,则同一个桶位置按照链表先后顺序输出。键只能允许为一条为空,value可以允许为多条为空。
    LinkedHashMap的有序是因为维护了双向链表。键和值都不可以为空。

    32、HashMap和TreeMap的区别

    HashMap:数组方式存储key/value,线程非安全,允许null作为key和value,key不可以重复,value允许重复,不保证元素迭代顺序是按照插入时的顺序,key的hash值是先计算key的hashcode值,然后再进行计算,每次容量扩容会重新计算所以key的hash值,会消耗资源,要求key必须重写equals和hashcode方法

    默认初始容量16,加载因子0.75,扩容为旧容量乘2,查找元素快,如果key一样则比较value,如果value不一样,则按照链表结构存储value,就是一个key后面有多个value;

    TreeMap:基于红黑二叉树的NavigableMap的实现,线程非安全,不允许null,key不可以重复,value允许重复,存入TreeMap的元素应当实现Comparable接口或者实现Comparator接口,会按照排序后的顺序迭代元素,两个相比较的key不得抛出classCastException。主要用于存入元素的时候对元素进行自动排序,迭代输出的时候就按排序顺序输出

    33、Java8 HashMap扩容时为什么不需要重新hash?

      if ((e.hash & oldCap) == 0) { 
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    loTail = e;
                                }
                                else {
                                    if (hiTail == null)
                                        hiHead = e;
                                    else
                                        hiTail.next = e;
                                    hiTail = e;
                                }
    
    

    可以看到它是通过将数据的hash与扩容前的长度进行与操作,根据e.hash & oldCap的结果来判断,如果是0,说明位置没有发生变化,如果不为0,说明位置发生了变化,而且新的位置=老的位置+老的数组长度。

    比如数据B它经过hash之后的值为 1111,在扩容之前数组长度是8,数据B的位置是:

    (n-1)&hash = (8-1) & 1111 = 111 & 1111 = 0111
    

    扩容之后,数组长度是16,重新计算hash位置是:

    (n-1)&hash = (16-1) & 1111 = 1111 & 1111 = 1111
    

    可见数据B的位置发生了变化,同时新的位置和原来的位置关系是:
    新的位置(1111)= 1000+原来的位置(0111)=原来的长度(8)+原来的位置(0111)
    继续看一下e.hash & oldCap的结果

    e.hash & oldCap = 1111 & 8 = 1111 & 1000 = 1000 (!=0)
    

    34、HashMap的put()方法流程

    image.png

    35、Java集合类框架的基本接口有哪些?

    总共有两大接口:Collection 和Map ,一个元素集合,一个是键值对集合; 其中List和Set接口继承了Collection接口,一个是有序元素集合,一个是无序元素集合; 而ArrayList和 LinkedList 实现了List接口,HashSet实现了Set接口,这几个都比较常用; HashMap 和HashTable实现了Map接口,并且HashTable是线程安全的,但是HashMap性能更好;

    36、四大引用

    引用类型 回收时机 使用场景
    强引用 不回收 创建对象实例
    软引用 内存不足时 图片缓存
    弱引用 垃圾回收 WeakHashMap,维护一种非强制的映射关系
    虚引用 Unknow 跟踪对象垃圾回收的活动

    37、final的作用?

    修饰变量时,不能被修改了,修改就报错
    修饰List时,可以添加和删除元素,值可以改变,但引用不能改变。不能再将这个list变量指向其他的List实例化对象了,即不能再出现list = new ArrayList(); 的代码。

    38、Java中由substring方法引发的内存泄漏

    • 内存溢出(out of memory ):通俗的说就是内存不够用了,比如在一个无限循环中不断创建一个大的对象,很快就会引发内存溢出。
    • 内存泄漏(leak of memory):是指为一个对象分配内存之后,在对象已经不在使用时未及时的释放,导致一直占据内存单元,使实际可用内存减少,就好像内存泄漏了一样。

    substring(int beginIndex, int endndex )是String类的一个方法,但是这个方法在JDK6和JDK7中的实现是完全不同的(虽然它们都达到了同样的效果)。在JDK1.6中不当使用substring会导致严重的内存泄漏问题。

    String str = "abcdefghijklmnopqrst";
    String sub = str.substring(1, 3);
    str = null;
    

    这段简单的程序有两个字符串变量str、sub。sub字符串是由父字符串str截取得到的,假如上述这段程序在JDK1.6中运行,我们知道数组的内存空间分配是在堆上进行的,那么sub和str的内部char数组value是公用了同一个,也就是上述有字符a~字符t组成的char数组,str和sub唯一的差别就是在数组中其实beginIndex和字符长度count的不同。在第三句,我们使str引用为空,本意是释放str占用的空间,但是这个时候,GC是无法回收这个大的char数组的,因为还在被sub字符串内部引用着,虽然sub只截取这个大数组的一小部分。当str是一个非常大字符串的时候,这种浪费是非常明显的,甚至会带来性能问题,解决这个问题可以是通过以下的方法:

    String str = "abcdefghijklmnopqrst";
    String sub = str.substring(1, 3) + "";
    str = null;
    

    利用的就是字符串的拼接技术,它会创建一个新的字符串,这个新的字符串会使用一个新的内部char数组存储自己实际需要的字符,这样父数组的char数组就不会被其他引用,令str=null,在下一次GC回收的时候会回收整个str占用的空间。但是这样书写很明显是不好看的,所以在JDK7中,substring 被重新实现了。

    在JDK7中改进了substring的实现,它实际是为截取的子字符串在堆中创建了一个新的char数组用于保存子字符串的字符。这样子字符串和父字符串也就没有什么必然的联系了,当父字符串的引用失效的时候,GC就会适时的回收父字符串占用的内存空间。

    相关文章

      网友评论

          本文标题:Java

          本文链接:https://www.haomeiwen.com/subject/yhyrphtx.html