JAVA基础常见核心面试点

Java基础要求

企业在java基础层面试要求主要包括如下几个点:

扎实JAVA基本功、熟练应用集合、I/O、多线程、网络编程、反射API以及lambda、stream等技术。

CGLIB

熟悉JVM体系结构、类加载、运行时内存区、字节码增强技术、GC常用算法以及JVM调优策略

Java编程基础

案例代码

  • 变量赋值

哪几种数据类型 byte char short int long float double boolean

java中char不等于字符

Char与字符概念不同

java中char类型目前已经偏离了其原有的语意,也就是一个char类型表示的并不完全是我们理解上的任意一个字符,这是由于java中char类型所使用的utf-16编码的历史原因造成的。

有一点理解的没有错,java中char类型就是使用固定两个字节来表示。

jvm规范中是如下描述的:

char, whose values are 16-bit unsigned integers representing Unicode code points in the Basic Multilingual Plane, encoded with UTF-16, and whose default value is the null code point ('\u0000')。

也就是java中char类型使用16位无符号整数来描述unicode中最早一批收录的字符,这批字符后来被叫作Basic Multilingual Plane(BMP),然后编码表使用的是utf-16

所以,java中单个char类型描述的字符是有限的单个char只能描述unicode中的BMP范围的码位,也就意味着BMP范围外的字符char是无法表示的。

utf-16如何表示non-BMP中的字符

utf-16使用4个字节来表示non-BMP中的字符,这4个字节以两个字节为单位划分,也就是可以分为两个16-bit的code unit(码元)

Java语言规范规定,Java的char类型是UTF-16的code unit,也就是一定是16位(2字节);

char, whose values are 16-bit unsigned integers representing UTF-16 code units(码元) (§3.1).

然后字符串是UTF-16 code unit的序列:

The Java programming language represents text in sequences of 16-bit code units, using the UTF-16 encoding.

char类型的字面值演示:

public class DataTypeTests {
    public static void main(String[] args) {
        char c1;
        c1='B';
        c1=97; //ASCII码
        c1='\u0001'; //UNICODE编码 十六进制
        
        char smile = "😀";
        smile.charAt(0);
        char car = '𝄞';
        char c1 = '𝌆';
        String a="";
        
    }
}

问题:

你可能会碰到这样的问题。比如发送短信,短信长度为 140 字节,如果文本超过了 140 个字节,你就必须将其截成多条。同时你又希望尽可能多的利用这 140 个字节。如果每个中文 2 字节,把短信内容限制在 70 个字符,那么英文较多的短信长度就被浪费了。你希望有一个方法,按字节来截取字符串,得到不超过 140 字节的最长子字符串。

  • 运算符应用

package com.java.basic;

public class OperatorTests {
    public static void main(String[] args) {
        byte a=10;
        short b=Short.MAX_VALUE;
        b+a;
        //a=a+10; //表达式自动类型提升
        a+=10;
        //=========
        int b=10;
        int c=b+++b+++b++;
        int c=(b++)+(b++)+(b++);
        System.out.println(b);//13
        System.out.println(c);//33

        //==========
        int d=10,e=20;
        int result=d>e?d++:e++;
        System.out.println(result);

        //&&、||
        int t1=10,t2=20;
        boolean flag=t1++>20&&t2--<20;
        System.out.println(flag);
        System.out.println(t2);//20

        //&、>>> (位运算)
        result=8/2;
        result=8>>2;
        System.out.println(result);
       
    }
}
  • 面试常见语句应用

package com.java.basic;
public class SwitchTests {
    public static void main(String[] args) {
        int a=5;
        int result=0;
        switch (a){//byte/short/int/char/String/enum
          
            case 1: result+=10;
            case 2: result+=20;
            case 3: result+=30;
            default:result+=40;
        }
        System.out.println(result);
    }
}

  • 数组定义案例

package com.java.basic;
public class ArrayTests {
    public static void main(String[] args) {
         //数组变量的定义
         int[] a1;
         int[][] a2;
         int[]a3[];
         int[][][] a4;
        //数组的初始化
        int[] b2={1,2,3};
        //int[] b3;b3={2,3,4}; //error (这种形式的赋值只能发生在定义时)
        int[] b4;b4=new int[]{2,3,4};
        int[][] b5=new int[3][2];
        //int[][] b6=new int[][2];//error (不能没有高位,只有低位)
        int[]b7[]=new int[3][];
        b7[0]=new int[2];
        b7[1]=new int[3];
        b7[2]=new int[1];
        int[]b8[]={{1},{2,3}};
        System.out.println(b8[1][1]);
    }
}
  • 方法参数传递

public class ParameterTests {
    public static void main(String[] args) {
        int[] array={1,2,3};
        int a=10;
        change(a,array);
        System.out.println(a);//10
        System.out.println(array[0]);//10
    }
    static void change(int a,int[] array){
        ++a;
        array[0]=10;
    }
}

JAVA集合应用

思维导图

JAVA集合框架

技术图析

Hashmap原理分析

  • 初始大小设计

HashMap 默认的初始大小是 16,当然这个默认值是可以设置的,如果事先知道大概的数据量有多大,可以通过修改默认初始大小,减少动态扩容(2的n次方)的次数,这样会大大提高 HashMap 的性能。

  • 装载因子和动态扩容设计

最大装载因子默认是 0.75,当 HashMap 中元素个数超过 0.75*capacity(capacity表示散列表的容 量)的时候,就会启动扩容,每次扩容都会扩容为原来的两倍大小。

  • 为什么扩容因子为0.75?

为什么不是0.5,也不是1呢?是因为这个0.75是在时间和空间上取的相对平衡值,假如在1的时候扩容,数组中数据越多,产生散列冲突的几率越大,一旦产生散列冲突数据就会转换为链表进行存储,而链表方式会影响查询效率. 假如在0.5时进行扩容,但又没有那么多元素要进行存储,可能会产生大量的空间浪费.

  • JDK8中HashMap 的扩容机制

1) 空参数的构造函数:实例化的 HashMap 默认内部数组是 null,即没有实例化。第一次调 用 put 方法时,则会开始第一次初始化扩容,长度为 16。

2) 有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的 2 的幂数,将这个数设置赋值给阈值(threshold)。第一次调用 put 方法时,会将阈值赋值给容量, 然后让阈值 = 容量 x 负载因子。

3) 如果不是第一次扩容,则容量变为原来的 2 倍,阈值也变为原来的 2 倍。(容量和阈值都 变为原来的 2 倍时,负载因子还是不变)。

  • 散列冲突及解决方案设计

HashMap 底层采用链表法来解决冲突。即使负载因子和散列函数设计得再合理,也免不了会出现 链表过长的情况,一旦出现链表过长,则会严重影响 HashMap 的性能。 于是,在 JDK1.8 版本中,为了对 HashMap 做进一步优化,官方引入了红黑树。而当链表长度太 长(默认超过 8)时,链表就转换为红黑树。我们可以利用红黑树快速增删改查的特点,提高 HashMap 的性能。当红黑树结点个数小于或等于6的时候,又会将红黑树转化为链表。因为在数据量 较小的情况下,红黑树要维护平衡,比起链表来,性能上的优势并不明显。

  • 为什么是链表长度达到8的时,进行红黑树转换?

经过大量计算、测试,链表的长度达到8的几率已经很小,所以可以直接基于8作为链表转红黑树的边界值。

为什么不是大于呢,因为链表长度较长查询效率就会越低。为什么不是7呢?链表结点数量比较小时,应用

红黑树还要进行树的平衡设计,需要的成本相对比较高。

  • 为什么红黑树节点个数小于6的时要转换链表呢?

假如是7则数据在链表和红黑树之间来回转换可能会比较频繁,这样就需要更长的时间消耗。

  • 线程(thread)安全设计

HashMap本身并不是线程安全的对象,所以仅可以应用在线程安全的环境。在线程不安全的环境推荐使用ConcurrentHashMap,此map在JDK8中采用了CAS算法保证对map的操作是线程安全的;

ConcurrentHashMap

ConcurrentHashMap 的存储结构是怎样的?

1) Java7 中 ConcurrnetHashMap 使用的分段锁,也就是每一个 Segment 上同时只有一个 线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它 的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变,默认 Segment 的 个数是 16 个。

2) Java8 中的 ConcurrnetHashMap 使用的 Synchronized 锁加 CAS 的机制。结构也由 Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红 30 黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红 黑树,在冲突小于一定数量时又退回链表。

案例代码

基于LinkedHashMap演示Lru原理。

源码解析

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

void afterNodeAccess(Node<K,V> e) { // move node to last

@Test
void testLinkedHashMap(){//Lru算法在LinkedHashMap中的应用
    //LinkedHashMap满的时移除最近没有访问过的数据(查构造方法,分析对象构建)
    int initialCapacity=3;
    LinkedHashMap<String,String> map=
            new LinkedHashMap<String,String>(initialCapacity,0.75f,true){
               //系统在每次调用put方法向容器放数据,默认都会调用此方法
               protected boolean removeEldestEntry(Map.Entry<String,String> eldest) {
                    return size()>initialCapacity;//true表示移除元素
                }
            };//true表示记录访问顺序
    map.put("A","100");
    map.put("B","200");
    map.put("C","300");
    map.get("A");
    map.put("D","400");
    System.out.println(map);
}

Java多线程应用

思维导图

Java并发编程

技术图析

线程状态解析

Java中线程池ThreadPoolExecutor对象分析

Java中线程池的创建有多种方式,简单池的创建方式可以借助Executors中的一些静态方法创建,实际项目中用的最多就是基于ThreadPoolExecutor对象创建线程池,例如Tomcat处理请求时的线程对象就来自于这个对象创建的池,还有Spring Boot工程中异步操作的实现使用的就是这个线程池。

 

  • 线程池参数有哪些?

    • corePoolSize 核心线程大小

    • maximumPoolSize 线程池最大线程数量。

    • keepAliveTime 空闲线程存活时间。

    • unit 空间线程存活时间单位。

    • workQueue 工作队列。

    • threadFactory 线程工厂。

    • handler 拒绝策略。

  • 线程池的拒绝策略有哪些?

    • AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常 (默认拒绝策略)。

    • DiscardPolicy:丢弃任务,但是不抛出异常。

    • DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被 拒绝的任务。

    • CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。

  • 核心线程池大小如何设置?

    • CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N (CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断, 或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而 在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。

    • I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而 线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程 使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。

  • 如何判断是 CPU 密集任务还是 IO 密集任务?

      CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。单 凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间相 比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。

说说synchronized关键字底层锁升级是怎样的?

synchronized在JDK1.6以后,描述代码块或方法时,会默认提供对优化使用,随着并发数的增加它会有一个锁升级过程。

案例代码

案例1:wait/notify/notifyAll方法方法应用

基于wait/notify/notifyAll方法实现线程之间的通讯,这里的wait/notify/notifyAll方法必须用在同步方法或同步代码块内部,由对象锁调用。

@SpringBootTest
public class ThreadCommunicationTests {
       String s1;
       @Test
       void test01() throws InterruptedException {
           System.out.println(Thread.currentThread().getName());
           new Thread(()->{
               synchronized (ThreadCommunicationTests.class) {
                   s1 = "Hello JSDTN2211";
                   //唤醒阻塞的线程
                   ThreadCommunicationTests.class.notifyAll();
               }
           }).start();
           //main线程
           synchronized (ThreadCommunicationTests.class) {
               while (s1 == null) ThreadCommunicationTests.class.wait();
               System.out.println(s1.toUpperCase());//NullPointerException
           }
       }
}

案例2:ThreadLocal的应用

基于ThreadLocal保证每个线程一个SimpleDateFormat对象(此对象线程不安全),ThreadLocal对象提供了这样的一种机制,能够将某个对象绑定到当前线程(调用set方法),也可以从当前线程获取某个对象(调用get方法)。

@SpringBootTest
public class ThreadLocalTests {

    static class DateUtil{
        //SimpleDateFormat是一个线程不安全的对象,不允许多线程共享,可以每个线程一份
        //static SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        static ThreadLocal<SimpleDateFormat> threadLocal=new ThreadLocal<>();
        public  static Date parse(String str){
            try {
                //从当前线程获取SimpleDateFormat对象
                SimpleDateFormat sdf =threadLocal.get();//ThreadLocalMap.get(threadLocal)
                if(sdf==null) {
                    System.out.println("new SimpleDateFormat()");
                    //创建SimpleDateFormat对象
                    sdf = new SimpleDateFormat("yyyy/MM/dd");
                    //将SimpleDateFormat对象绑定到当前线程
                    threadLocal.set(sdf);//ThreadLocalMap.set(threadLocal,sdf)
                }
                return sdf.parse(str);
            }catch (ParseException e){
                e.printStackTrace();
                return null;
            }
        }
    }
    @Test
    void testParse(){
        Thread t1=new Thread(()->{
            for(int i=0;i<5;i++){
                DateUtil.parse("2023/05/18");
            }
        });
        Thread t2=new Thread(()->{
            for(int i=0;i<5;i++){
                DateUtil.parse("2023/05/18");
            }
        });
        t1.start();
        t2.start();
        for(int i=0;i<5;i++){
            DateUtil.parse("2023/05/18");
        }
    }
}

基于volatile,synchronized实现双重校验锁单例

volatile 主要用于描述类中的属性 1)保证线程可见性(对变量的修改可以直接同步给线程) 2)禁止指令重排序(JVM执行时,考虑执行效率,可能会调整指令执行顺序)


public class SingletonTests {

    static class Singleton{//双重校验单例
        private Singleton(){}
        /**
         * volatile 主要用于描述类中的属性
         * 1)保证线程可见性(对变量的修改可以直接同步给线程)
         * 2)禁止指令重排序(JVM执行时,考虑执行效率,可能会调整指令执行顺序)
         */
        private static volatile Singleton instance;
        public static Singleton getInstance(){
            if(instance==null){//A,B,C
                synchronized (Singleton.class){
                    if(instance==null)//单例双重校验法
                        instance=new Singleton();
                        //1)分配空间
                        //2)属性默认初始化
                        //3)调用构造方法
                        //4)将对象地址赋值给instance变量
                }
            }
            return instance;
        }
    }
}

i++

Java反射技术

思维导图

案例分析

通过反射技术理解泛型类型擦除

@Test
void testGeneric() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    ArrayList<String> list=new ArrayList<>();
    list.add("A");
    list.add("B");
    //list.add(100);编译时无法添加
    //请运用反射技术将100这个整数添加到list集合
    //获取类的字节码对象
    Class<?> cls=list.getClass();
    //获取ArrayList对象的add方法
    Method addMethod = cls.getDeclaredMethod("add", Object.class);
    //通过反射执行方法
    addMethod.invoke(list,100);
    System.out.println(list);
}

通过反射技术操作注解

反射技术与注解的应用。

 
public class AnnotationTests {
    //模拟Knife4J中的一个定义方法参数的注解
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @interface ApiImplicitParam{//一个Interface,默认继承Annotation接口
        String name();
        String value();
        String dataType();
    }
    //模拟一个Controller类型
    class XxxController{
        @ApiImplicitParam(name = "page", value = "页码", dataType = "int")
        public void doSelect(){}
    }
    //通过反射技术获取XxxController类中doSelect()方法上的注解以及注解中方法的值
   public static void main(String[] args) throws NoSuchMethodException {
        Class<XxxController> xxxControllerClass = XxxController.class;
        Method doSelect = xxxControllerClass.getDeclaredMethod("doSelect");
        ApiImplicitParam annotation = doSelect.getAnnotation(ApiImplicitParam.class);
        String name = annotation.name();
        String value=annotation.value();
        String dataType = annotation.dataType();
        System.out.println(name+"/"+value+"/"+dataType);
    }
}

Java技术新特性

JDK5新特性

JDK5新特性,后续文章更新,也可查阅官方文档。

JDK8新特性

JDK5新特性,后续文章更新,也可查阅官方文档。

  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值