简历撰写+面经总结

Situation: 事情是在什么情况下发⽣;
Task:: 你是如何明确你的任务的;
Action: 针对这样的情况分析,你采⽤了什么⾏动⽅式;
Result: 结果怎样,在这样的情况下你学习到了什么。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
参考即可:
在这里插入图片描述

JVM JDK和JRE的详细通俗回答

JVM

JVM是运行Java字节码的虚拟机。JVM有针对不同系统的特定实现,目的是,使用相同的字节码,他们回给出相同的结果。
在这里插入图片描述
JVM中的类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度回相对比较慢。而且,有些方法和代码块是需要经常被调用的(热点代码),后面引进了JIT编译器,JIT属于运行时编译,当JIT编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。

HotSpot 采⽤了惰性评估(Lazy Evaluation)的做法,根据⼆⼋定律,消耗⼤部分系统资源的只有那⼀⼩部分的代码(热点代码)JDK 9 引⼊了⼀种新的编译模式 AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了 JIT 预热等各⽅⾯的开销。JDK ⽀持分层编译和 AOT 协作使⽤。但是 ,AOT 编译器的编译质量是肯定⽐不上 JIT 编译器的。

重写和重载

重写发⽣在运⾏期,是⼦类对⽗类的允许访问的⽅法的实现过程进⾏重新编写。

  1. 返回值类型、⽅法名、参数列表必须相同,抛出的异常范围⼩于等于⽗类,访问修饰符范围
    ⼤于等于⽗类。
  2. 如果⽗类⽅法访问修饰符为 private/final/static 则⼦类就不能重写该⽅法,但是被 static 修饰
    的⽅法能够被再次声明。
  3. 构造⽅法⽆法被重写

⽅法的重写要遵循“两同两⼩⼀⼤”:
“两同”即⽅法名相同、形参列表相同;
“两⼩”指的是⼦类⽅法返回值类型应⽐⽗类⽅法返回值类型更⼩或相等,⼦类⽅法声明抛出
的异常类应⽐⽗类⽅法声明抛出的异常类更⼩或相等;
“⼀⼤”指的是⼦类⽅法的访问权限应⽐⽗类⽅法的访问权限更⼤或相等。

封装,继承,多态

封装

封装把⼀个对象的属性私有化,同时提供⼀些可以被外界访问的属性的⽅法,如果属性不想被外
界访问,我们⼤可不必提供⽅法给外界访问。但是如果⼀个类没有提供给外界访问的⽅法,那么
这个类也没有什么意义了。

继承

继承是使⽤已存在的类的定义作为基础建⽴新类的技术,新类的定义可以增加新的数据或新的功能,也可以⽤⽗类的功能,但不能选择性地继承⽗类。通过使⽤继承我们能够⾮常⽅便地复⽤以前的代码。
关于继承如下 3 点请记住:

  1. ⼦类拥有⽗类对象所有的属性和⽅法(包括私有属性和私有⽅法),但是⽗类中的私有属性
    和⽅法⼦类是⽆法访问,只是拥有。
  2. ⼦类可以拥有⾃⼰属性和⽅法,即⼦类可以对⽗类进⾏扩展。
  3. ⼦类可以⽤⾃⼰的⽅式实现⽗类的⽅法。

多态

多态,表示一个对象具有多种状态。具体表现为父类的引用指向子类的实例。、
在 Java 中有两种形式可以实现多态:继承(多个⼦类对同⼀⽅法的重写)和接口(实现接口并覆盖接口中同⼀⽅法)。
有两个好处:

  1. 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。//继承
  2. 派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。 //多态的真正作用,

String, StringBuffer, StringBuilder

String类种使用final关键字修饰字符数组来保存字符串,private final char value[],所以String对象是不可变的。
在 Java 9 之后,String 类的实现改⽤ byte 数组存储字符串
private final byte[] value

String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是
StringBuilder 与 StringBuffer 的公共⽗类,定义了⼀些字符串的基本操作,如 expandCapacity、
append、insert、indexOf 等公共⽅法。StringBuffer 对⽅法加了同步锁或者对调⽤的⽅法加了同
步锁,所以是线程安全的。StringBuilder 并没有对⽅法进⾏加同步锁,所以是⾮线程安全的。

接口和抽象类的区别

从以下几点来说明:
语法方面:
接口用interface,抽象类用abstract
继承方面:
实现类只能继承一个抽象方法,但可以实现多个接口
数据成员:
抽象类中的成员变量可以是普通变量,接口中的成员变量只能说静态常量
构造方法:
抽象类中可以定义构造方法,接口不可以

  1. 在 JDK8 中,接⼝也可以定义静态⽅法,可以直接⽤接⼝名调⽤。实现类和实现是不可以调⽤的。如果同时实现两个接⼝,接⼝中定义了⼀样的默认⽅法,则必须重写,不然会报错。(详⻅ issue:https://github.com/Snailclimb/JavaGuide/issues/146。
  2. jdk9 的接⼝被允许定义私有⽅法 。

成员变量和局部变量的区别?

  1. 从语法形式上看:成员变量是属于类的,⽽局部变量是在⽅法中定义的变量或是⽅法的参数;
    成员变量可以被 public , private , static 等修饰符所修饰,⽽局部变量不能被访问控制修饰
    符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
  2. 从变量在内存中的存储⽅式来看:如果成员变量是使⽤ static 修饰的,那么这个成员变量是属
    于类的,如果没有使⽤ static 修饰,这个成员变量是属于实例的。对象存于堆内存,如果局
    部变量类型为基本数据类型,那么存储在栈内存,如果为引⽤数据类型,那存放的是指向堆
    内存对象的引⽤或者是指向常量池中的地址。
  3. 从变量在内存中的⽣存时间上看:成员变量是对象的⼀部分,它随着对象的创建⽽存在,⽽局
    部变量随着⽅法的调⽤⽽⾃动消失。
  4. 成员变量如果没有被赋初值:则会⾃动以类型的默认值⽽赋值(⼀种情况例外:被 final 修饰
    的成员变量也必须显式地赋值),⽽局部变量则不会⾃动赋值。

hashCode 与 equals (重要)

进程,线程和程序的概念

程序:程序是含有指令和数据的⽂件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的
代码。
进程:
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡。简单来说,一个进程就是一个执行中的程序,他在计算机中一个指令接着一个指令的执行。同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,输入输出设备的使用权等。换句话说,当程序在执行时,将会被操作系统载入内存中。
线程:
线程是进程更小的运行单位。线程和进程最大的不同在于基本上个进程是独立的,而各个线程不一定,因为同一进程中的线程极有可能会相互影响,从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是同一个程序内几乎同时执行一个以上的程序段。

线程有那些状态

初始状态:线程被创建,但是还没有调用start()方法
运行状态:Java线程将操作系统中的就绪和运行两种状态笼统称为“运行中”
Blocked: 阻塞状态,表示线程阻塞于锁。
waitting: 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一定特定动作。
TIME_WAITING : 超时等待状态,该状态不同于WATTING,它是可以在指定的时间自行返回的
TERMINATED:终止状态,表示当前线程已经执行完毕
在这里插入图片描述

线程创建之后它将处于 NEW(新建) 状态,调⽤ start() ⽅法后开始运⾏,线程这时候处于
READY(可运⾏) 状态。可运⾏状态的线程获得了 cpu 时间⽚(timeslice)后就处于
RUNNING(运⾏) 状态。
当线程执⾏ wait() ⽅法之后,线程进⼊ WAITING(等待)状态。进⼊等待状态的线程需要依靠
其他线程的通知才能够返回到运⾏状态,⽽ TIME_WAITING(超时等待) 状态相当于在等待状态
的基础上增加了超时限制,⽐如通过 sleep(long millis) ⽅法或 wait(long millis) ⽅法可以将
Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状
态。
当线程调⽤同步⽅法时,在没有获取到锁的情况下,线程将会进⼊到 BLOCKED(阻塞)
状态。线程在执⾏ Runnable 的 run() ⽅法之后将会进⼊到 TERMINATED(终⽌) 状态。

异常

在这里插入图片描述
Exception: 程序可以捕获的异常,可以通过catch来进行捕获

  • 受检查的异常IOException必须处理
  • 不受检查的异常RuntimeException可以不处理,如空指针异常,数组越界异常,类型转换异常
    Error: 错误是无法处理的,只能避免,如栈溢出,内存不够

IO流

字节流:InputStream OutputStream
字符流:Reader Writer

BIO,NIO和AIO的区别

同步阻塞IO(JAVA BIO):
同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

同步非阻塞IO(Java NIO) : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问。

异步阻塞IO(Java AIO):
此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄(如果从UNP的角度看,select属于同步操作。因为select之后,进程还需要读写数据),从而提高系统的并发性!

深拷贝和浅拷贝

  1. 浅拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型进⾏引⽤传递般的拷⻉,此为浅拷
    ⻉。
  2. 深拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型,创建⼀个新的对象,并复制其内
    容,此为深拷⻉。

Java集合

List,Set,Map三者的区别

List(对付顺序的好帮手):存储的元素是有序的,可重复的
Set(注重独一无二):存储的元素是无序的不可重复的
Map(体现在快速搜索):key是无序的不可重复的,Value是无序的可重复的

ArrayList和LinkedList的区别?

  1. 线程安全方面
  2. 数据结构方面
  3. 插入和删除速度
  4. 随机访问速度
  5. 内存空间访问

ArrayList扩容机制

   private Object[] grow(int minCapacity) {
        int oldCapacity = elementData.length;
        if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            int newCapacity = ArraysSupport.newLength(oldCapacity,
                    minCapacity - oldCapacity, /* minimum growth */
                    oldCapacity >> 1           /* preferred growth */);
            return elementData = Arrays.copyOf(elementData, newCapacity);
        } else {
            return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
        }
    }

扩容机制:
ArrayList每次扩容都是之前的1.5倍。(如果1.5倍不够的话那么就把新的长度作为新数组的长度)
数组进行扩容是,会将老的数组中的元素重新拷贝一份到新的数组中,最妙常数组容量增长大于原来的1.5倍
新创建的时候没有指定新添加的元素。

HashMap和Hashtable的区别

  1. 线程是否安全:HashMap是非线程安全的,HashTable是线程安全的,因为HashTable内部的方法都经过synchronized修饰。(如果要保证线程安全的话就使用ConcurrentHashMap)
  2. 效率问题,HashMap的效率要比HashTable的效率高
  3. Null的问题:HashMap可以有Null但是HashTable不可以有null作为key
  4. 初始容量大小和扩容容量大小:HashMap的默认初始化大小为16.每次扩容后大小变为之前的2倍。HashTable的默认初始值为11,之后每次扩容容量变为原来的2n+1.如果制定了初始值,Hash Map会扩充为2的幂次方大小,HashTable直接使用指定大小。
  5. 底层数据结构。JDK1.8之后HashMap在解决哈希冲突的时候有了很大的变化,当链表长度大于8的时候,会将链表转换成红黑树,以减少搜索时间。

HashMap扩容原理

JDK8以后:

  final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // 新的阈值为之前的2倍
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // 初始化容量为16,阈值为容量*0.75
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            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;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

红黑树:参考链接

红黑树是特殊的二叉查找树,又名R-B树(RED-BLACK-TREE),由于红黑树是特殊的二叉查找树,即红黑树具有了二叉查找树的特性,而且红黑树还具有以下特性:

1.每个节点要么是黑色要么是红色
2.根节点是黑色
3.每个叶子节点是黑色,并且为空节点(还有另外一种说法就是,每个叶子结点都带有两个空的黑色结点(被称为黑哨兵),如果一个结点n的只有一个左孩子,那么n的右孩子是一个黑哨兵;如果结点n只有一个右孩子,那么n的左孩子是一个黑哨兵。)
4.如果一个节点是红色,则它的子节点必须是黑色
5.从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

红黑树比AVL树的优势?
首先红黑树是不符合AVL树的平衡条件的,即每个节点的左子树和右子树的高度最多差1的二叉查找树。但是提出了为节点增加颜色,红黑是用非严格的平衡来换取增删节点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决,而AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多。所以红黑树的插入效率更高
红黑树插入过程:
如果插入节点的父节点是黑色,那么直接插入即可。
如果插入节点的父节点是红色,那么再具体分情况

  1. 如果父节点的叔叔节点也为红
    红黑树删除过程:
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值