栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java经典面试题1-10

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java经典面试题1-10

(1)看代码回答问题一

在这中间可以添加N行代码,但必须保证s引用的指向不变,最终将输出变成abcd——反射

package com.Test0720;

import java.lang.reflect.Field;

public class Test1 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        String s = new String("abc");
        //在这中间可以添加N行代码,但必须保证s引用的指向不变,最终将输出变成abcd

        //反射
        Field value = s.getClass().getDeclaredField("value");
        value.setAccessible(true);
        value.set(s,"abcd".toCharArray());
        System.out.println(s);
    }

}

输出结果:


(2)看代码回答问题二
package com.Test0720;

public class Test2 {
    public static void main(String[] args) {
        String s1 = new String("abc");//2个对象
        String s2 = "abc";
        //s1==s2? true or false
        System.out.println(s1 == s2);

        String s3 = s1.intern();
        //s2==s3? true or false
        System.out.println(s2 == s3);//true

        //String对象的intern方法,首先会检查字符串常量池中是否存在“abc”,
        // 如果不存在,则把“abc”添加到字符串常量池中,并返回该字符串常量使用。

    }
}

输出结果:


(3)看代码回答问题三 

      在Interger类中,存在一个静态内部类integerCache,该类中存在一个integer cache[],并且存在一个static块,会在加载的时候执行,会将-128-127这些数字提前生成integer对象,并缓存在cache数组中,当我门在定义integer数字时,会调用integer的valueOf方法,valueOf方法会判断所定义的数字是否在-128到127之间,如果存在则直接从cache数组中获取integer对象,如果超越,则生成一个新的integer对象。

package com.Test0720;

public class Test3 {
    public static void main(String[] args) {
        Integer i1 = 100;
        Integer i2 = 100;
        //i1==i2? true or false
        System.out.println(i1 == i2);//true

        Integer i3 = 128;
        Integer i4 = 128;
        //i3==i4? true or false
        System.out.println(i3 == i4);//false

        //在Interger类中,存在一个静态内部类integerCache,该类中存在一个integer cache[],并且存在一个static块,
        // 会在加载的时候执行,会将-128-127这些数字提前生成integer对象,并缓存在cache数组中,当我门在定义integer数字时,
        // 会调用integer的valueOf方法,valueOf方法会判断所定义的数字是否在-128到127之间,
        // 如果存在则直接从cache数组中获取integer对象,如果超越,则生成一个新的integer对象

    }


}

输出结果:

 


(4)String、StringBuffer、StringBuilder的区别
  • String是不可变的,如果尝试去修改,会新⽣成⼀个字符串对象,StringBuffer和StringBuilder是可变的
  • StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效率会更高
package com.Test0720;

public class Test4 {
    public static void main(String[] args) {
        String s = "abc";
        s="abcd";
        StringBuilder builder = new StringBuilder(s);
        builder.append("d");

        StringBuffer buffer = new StringBuffer(s);
        buffer.append("d");
        //1、String是不可变的,如果尝试去修改,会新生成一个字符串对象,StringBuilder和StringBuffer是可变的
        //2、StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效率会更高

    }
}

(5)ArrayList和LinkedList区别
  • 首先,他们的底层数据结构不同,ArrayList底层是基于数组实现的,LinkedList底层是基于链表实现的
  • 由于底层数据结构不同,他们所适⽤的场景也不同,ArrayList更适合随机查找,LinkedList更适合删除和添加,查询、添加、删除的时间复杂度不同
  • 另外ArrayList和LinkedList都实现了List接口,但是LinkedList还额外实现了Deque接口,所以 LinkedList还可以当做队列来使用
package com.Test0720;

import java.util.ArrayList;
import java.util.LinkedList;

public class Test5 {
    public static void main(String[] args) {

        ArrayList arrayList = new ArrayList();//数组
        arrayList.add("1");//扩容
        arrayList.add(3,"1");//扩容 移动

        LinkedList linkedList = new LinkedList();//链表
        linkedList.add("1");
        linkedList.add(3,"1");

        System.out.println(arrayList);
        System.out.println(linkedList);

        //1.首先,他们底层数据结构不同,ArrayList底层是基于数据实现的,LinkedList底层是基于链表实现的
        //2.由于底层数据结构不同,他们所使用的场景也不同,ArrayList更适合随机查找,LinkedList更适合删除和添加,查询、添加,删除时间复杂度不同
        //3.另外ArrayList和LinkedList都实现了List接口,但是LinkedList还额外实现了Deque接口,所以LinkedList还可以当做队列来使用
        
    }
}

(6)CopyOnWriteArrayList的底层原理是怎样的
  • 首先CopyOnWriteArrayList内部也是用过数组来实现的,在向CopyOnWriteArrayList添加元素时,会复制一个新的数组,写操作在新数组上进行,读操作在原数组上进行
  • 并且,写操作会加把锁防止出现并发写入丢失数据的问题
  • 写操作结束之后会把原数组指向新数组
  • CopyOnWriteArrayList允许写操作时读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CopyOnWriteArrayList会比较占用内存,同时可能读到的数据不是实时最新的数据,所以不适合实时性要求很高的场景。

注意:ArrayList会导致线程不安全

package com.Test0720;

import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;

public class Test6 {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add("zhouyu");
        arrayList.add("tuling");
        //会导致线程不安全

        CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();

        list.add("zhouyu");
        String s = list.get(1);

        System.out.println(list);
        System.out.println(arrayList);
        
    }
}

(7)HashMap的扩容机制原理 1.7版本:

  1.    先生成新数组
  2.    遍历老数组中的每个位置上的链表上的每个元素
  3.    取每个元素的key,并基于新数组长度,计算出每个元素在新数组中的下标
  4.    将元素添加到新数组中去
  5.    所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
1.8版本:

  1.    先生成新数组
  2.    遍历老数组中的每个位置上的链表或红黑树
  3.    如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去
  4.    如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置
  •    统计每个下标位置的元素个数
  •    如果该位置下的元素个数超过了8,则⽣成⼀个新的红黑树,并将根节点的添加到新数组的对应位置
  •    如果该位置下的元素个数没有超过8,那么则生成⼀个链表,并将链表的头节点添加到新数组的对应位置

   5.     所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

package com.Test0720;

import java.util.HashMap;

public class Test7 {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap<>();
        hashMap.put("zhouyu","zy");
        System.out.println(hashMap);
//HashMap的扩容机制原理
        
    }
}

输出结果:


 

(8)谈谈ConcurrentHashMap的扩容机制:

1.7版本:

  1.     1.7版本的ConcurrentHashMap是基于Segment分段实现的
  2.     每个Segment相对于⼀个⼩型的HashMap
  3.     每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似
  4.     先生成新的数组,然后转移元素到新数组中
  5.     扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值
 1.8版本:

  1.     1.8版本的ConcurrentHashMap不再基于Segment实现
  2.     当某个线程进行put时,如果发现ConcurrentHashMap正在进⾏扩容那么该线程⼀起进行扩容
  3.     如果某个线程put时,发现没有正在进行扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进⾏扩容
  4.     ConcurrentHashMap是支持多个线程同时扩容的
  5.     扩容之前也先生成⼀个新的数组
  6.     在转移元素时,先将原数组分组,将每组分给不同的线程来进⾏元素的转移,每个线程负责⼀组或多组的元素转移工作

(9)ThreadLocal的底层原理:
  1. ThreadLocal是Java中所提供的线程本地存储机制,可以利⽤该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意⽅法中获取缓存的数据
  2. ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在⼀个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
  3. 如果在线程池中使用ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使用完之后,应该要把设置的key,value,也就是Entry对象进⾏回收,
  4. 但线程池中的线程不会回收,而线程对象是通过强引⽤指向ThreadLocalMap,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从⽽出现内存泄漏,
  5. 解决办法是,在使用了ThreadLocal对象之后,⼿动调⽤ThreadLocal的remove⽅法,手动清楚Entry对象
  6. ThreadLocal经典的应用场景就是连接管理(⼀个线程持有⼀个连接,该连接对象可以在不同的⽅法之间进⾏传递,线程之间不共享同⼀个连接)
package com.Test0720;

public class Test9 {

    private static ThreadLocal s = new ThreadLocal<>();

    public static void main(String[] args) {
        s.set("zhouyu");

        String s1 = Test9.s.get();
        s.remove();

    }
}


(10)如何理解volatile关键字:
  • 在并发的领域中,存在三大特性:原子性,有序性,可见性。
  • volatile关键字用来修饰对象的属性,在并发环境下可以保证这个属性的可见性。
  • 对于加了volatile关键字的属性,在这个属性进行修改时,会直接将CPU高级缓存中的数据写回到主内存,对这个变量的读取也会直接从主内存中读取,从而保证了可见性。
  • 底层是通过操作系统的内存屏障来实现的,由于使用了内存屏障,所以会禁止指令重排,所以也就同时保证了有序性,在很多并发场景下,如果用好volatile关键字可以很好的提高执行效率。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1020011.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号