Java 必备八股文(一)

发布于 2021-09-07 11:25 ,所属分类:2021面试经验技巧分享




我们


Hello,大家好,我是程序员soulmate。最近有粉丝在后台回复我能不能出一个Java程序员面试题的文章啊。毕竟最近也到了提桶金九银十的好时期,那今天就来搞个很全的Java八股文集,可能会分好几个文章来更新哈。

一定要看到最后,文末有惊喜,废话不多说,开淦。

Part1Java基础

1Java语言的特点

  1. 简单易学;
  2. 面向对象(封装,继承,多态);
  3. 平台无关性(Java虚拟机实现平台无关性);
  4. 可靠性;
  5. 安全性;
  6. 支持多线程(C++语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而Java语言却提供了多线程支持);
  7. 支持网络编程并且很方便(Java语言诞生本身就是为简化网络编程设计的,因此Java语言不仅支持网络编程而且很方便);
  8. 编译与解释并存;

2什么是JDK?什么是JRE?

JDK: (Java Development Kit)顾名思义它是给开发者提供的开发工具箱,是给程序开发者用的。它除了包括完整的JRE(Java Runtime Environment),Java运行环境,还包含了其他供开发者使用的工具包。

JRE: 普通用户而只需要安装JRE(Java Runtime Environment)用来运行Java程序。而程序开发者必须安装JDK来编译、调试程序。

3public、protected、default、private访问权限

public对所有类公开,private只有本类可以访问。这里需要注意的是protected,除了对本类和子类公开外,还对本包所在类的公开,即同一包类的类都可以访问;default,如果没有明确写出修饰类型则为default,它只对本包公开。这四个修饰符的访问权限从大到小为public > protected > default > private。

修饰符访问权限

4java 面向对象编程四大特性

抽象性:抽出事物的本质特性,把某类事物的共同的特点描述出来;

封装性:把对象的属性和操作结合在一起,构成一个独立的封装体;

继承性:使得一个类可以继承另一个类的属性和方法;

多态性:指不同类型的对象接收相同的消息时产生不同的行为。

5Object类的常用方法

  1. getClass: 获取当前运行时对象的 Class 对象信息。

  2. toString 返回类名@哈希码的 16 进制。

  3. equals: 通过内存地址比较两个对象是否相等,String 类重写了这个方法使用值来比较是否相等。

  4. hashCode: 返回对象的 hash 码。

  5. clone: 拷贝当前对象,必须实现 Cloneable 接口。浅拷贝对基本类型进行值拷贝,对引用类型拷贝引用;深拷贝对基本类型进行值拷贝,对引用类型对象不但拷贝对象的引用还拷贝对象的相关属性和方法。两者不同在于深拷贝创建了一个新的对象。

  6. notify: 唤醒当前对象监视器的任一个线程。

  7. notifyAll: 唤醒当前对象监视器上的所有线程。

  8. wait:
    1、暂停线程的执行;
    2、三个不同参数方法(等待多少毫秒;额外等待多少毫秒;一直等待)
    3、与 Thread.sleep(long time) 相比,sleep 使当前线程休眠一段时间,并没有释放该对象的锁,wait 释放了锁。

  9. finalize: 对象被垃圾回收器回收时执行的方法。

6基本数据类型

  1. 整型:byte(8)、short(16)、int(32)、long(64)

  2. 浮点型:float(32)、double(64)

  3. 布尔型:boolean(8)

  4. 字符型:char(16)

7方法的重写与重载

  1. 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!如果父类方法访问修饰符为 private 或者 final 则子类就不能重写该方法。

父类

publicclassTest{

publicvoidoverloadTest(){
System.out.println("我是父类方法");
}
}

子类

publicclassChildTestextendsTest{

@Override
publicvoidoverloadTest(){
System.out.println("我是子类,重写父类方法");
}
}
  1. 重载(overloading) 是在一个类里面,方法名字相同,而参数列表不同。只能通过参数列表的数量、类型、不同类型的位置区分。被重载的方法可以改变返回类型、可以改变修饰符、也可以声明新的或更广的检查异常。方法能够在同一个类中或者在一个子类中被重载。无法以返回值类型作为重载函数的区分标准。

以下代码是重载方法的示例

publicclasstest{

publicvoidoverloadTest(){
}

publicvoidoverloadTest(inta){
}

publicvoidoverloadTest(inta,intb){
}

publicvoidoverloadTest(doublea,intb){
}

publicvoidoverloadTest(inta,doubleb){
}

publicvoidoverloadTest(doublea,doubleb){
}

publicvoidoverloadTest(doublea,doubleb,doublec){
}
}

8final

  1. 修饰基本类型变量,一经出初始化后就不能够对其进行修改。
  2. 修饰引用类型变量,不能够指向另一个引用。
  3. 修饰类或方法,不能被继承或重写。

注意:final修饰的集合变量,例如list,add、remove等方法正常使用,只是不能被指向另一个引用,但是引用的行为不会被限制。

9Java IO/NIO

Java IO:即Java 输入/输出系统,面向流,同步阻塞线程。

  1. 区分 Java 的输入和输出:把自己当成程序, 当你从外边读数据到自己这里就用输入(InputStream/Reader), 向外边写数据就用输出(OutputStream/Writer)。

  2. Stream:Java 中将数据的输入输出抽象为流,流是一组有顺序的,单向的,有起点和终点的数据集合,就像水流。按照流中的最小数据单元又分为字节流和字符流。

Java NIO:面向缓冲区,同步非阻塞IO,主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。

10Java反射

  1. 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。

  2. 反射可以在一个类运行的时候获取类的信息的机制,可以获取在编译期不可能获得的类的信息。

  3. 对于任意一个对象,都能调用它的任意一个方法和属性。

  4. 因为类的信息是保存在Class对象中的,而这个Class对象是在程序运行时被类加载器(ClassLoader)动态加载的。

  5. 当类加载器装载运行了类后,动态获取Class对象的信息以及动态操作Class对象的属性和方法的功能称为Java语言的反射机制。

11JDK 动态代理

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:

  1. Interface InvocationHandler:该接口中仅定义了一个方法;
  2. Proxy:该类即为动态代理类

动态代理步骤:

  1. 创建一个实现接口InvocationHandler的类,它必须实现invoke方法
  2. 创建被代理的类以及接口
  3. 通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler handler)创建一个代理
  4. 通过代理调用方法

Part2Java集合

12常用的集合分类

Collection 接口的接口 对象的集合(单列集合)

  1. List 接口:元素按进入先后有序保存,可重复
    • Stack 是Vector类的实现类
    • LinkedList 接口实现类, 链表, 插入删除, 没有同步, 线程不安全
    • ArrayList 接口实现类, 数组, 随机访问, 没有同步, 线程不安全
    • Vector 接口实现类 数组, 同步, 线程安全
  2. Set 接口:仅接收一次,不可重复,并做内部排序
    • HashSet 使用hash表(数组)存储元素
    • LinkedHashSet 链表维护元素的插入次序
    • TreeSet 底层实现为二叉树,元素排好序

Map 接口 键值对的集合 (双列集合)

  1. Hashtable 接口实现类, 同步, 线程安全
  2. HashMap 接口实现类 ,没有同步, 线程不安全
    • LinkedHashMap 双向链表和哈希表实现
    • WeakHashMap
  3. TreeMap 红黑树对所有的key进行排序
  4. IdentifyHashMap

13List和Set集合详解

List和Set的区别

有序性:List保证按插入顺序排序,Set存储和取出顺序不一致。
唯一性:List可以重复,Set元素唯一。
获取元素:List可以通过索引直接操作元素,Set不能根据索引获取元素。

List

(1)ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素

(2)LinkedList 底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素

(3)Vector:底层数据结构是数组,查询快,增删慢,线程安全,效率低,可以存储重复元素

Set

(1) HashSet底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高,可以存储null元素,元素的唯一性是靠所存储元素类型是否重写hashCode()和equals()方法来保证的,如果没有重写这两个方法,则无法保证元素的唯一性。

(2 LinkedHashSet底层数据结构采用链表和哈希表共同实现,链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性。线程不安全,效率高。

(3) TreeSet底层数据结构采用二叉树来实现,元素唯一且已经排好序;唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性。

14Map集合详解

Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value,它们都可以使任何引用类型的数据,但key不能重复。所以通过指定的key就可以取出对应的value。

请注意: Map 没有继承 Collection 接口, Map 提供 key 到 value 的映射,你可以通过“键”查找“值”。一个 Map 中不能包含相同的 key ,每个 key 只能映射一个 value 。Map 接口提供 3 种集合的视图, Map 的内容可以被当作一组 key 集合,一组 value 集合,或者一组 key-value 映射。

  1. HashMap 非线程安全。
    HashMap:基于哈希表实现。使用HashMap要求添加的键类明确定义了hashCode()和equals()可以重写hashCode()和equals(),为了优化HashMap空间的使用,您可以调优初始容量和负载因子。

  2. TreeMap:非线程安全。
    TreeMap:基于红黑树实现,没有调优选项,因为该树总处于平衡状态。

Part3Java8新特性

  1. Java8 Lambda表达式
  2. 双冒号方法引用
  3. 函数式接口
  4. Java8 Stream
  5. Java8 日期时间API
  6. 接口的默认方法和静态方法

Part4多线程

15synchronized

  1. 修饰代码块 底层实现,通过 monitorenter & monitorexit 标志代码块为同步代码块。
  2. 修饰方法 底层实现,通过 ACC_SYNCHRONIZED 标志方法是同步方法。
  3. 修饰类 class 对象时,实际锁在类的实例上面。

单例模式实例:

publicclassSingleton{

privatestaticvolatileSingletoninstance=null;

privateSingleton(){}

publicstaticSingletongetInstance(){
if(null==instance){
synchronized(Singleton.class){
if(null==instance){
instance=newSingleton();
}
}
}
returninstance;
}
}
  1. 偏向锁,自旋锁,轻量级锁,重量级锁

通过 synchronized 加锁,第一个线程获取的锁为偏向锁,这时有其他线程参与锁竞争,升级为轻量级锁,其他线程通过循环的方式尝试获得锁,称自旋锁。若果自旋的次数达到一定的阈值,则升级为重量级锁。需要注意的是,在第二个线程获取锁时,会先判断第一个线程是否仍然存活,如果不存活,不会升级为轻量级锁。

16Lock

ReentrantLock

基于 AQS (AbstractQueuedSynchronizer)实现,主要有 state (资源) + FIFO (线程等待队列) 组成。

公平锁与非公平锁:区别在于在获取锁时,公平锁会判断当前队列是否有正在等待的线程,如果有则进行排队。使用 lock() 和 unLock() 方法来加锁解锁。

ReentrantReadWriteLock

同样基于 AQS 实现,内部采用内部类的形式实现了读锁(共享锁)和写锁 (排它锁)。

非公平锁吞吐量高,在获取锁的阶段来分析,当某一线程要获取锁时,非公平锁可以直接尝试获取锁,而不是判断当前队列中是否有线程在等待。一定情况下可以避免线程频繁的上下文切换,这样,活跃的线程有可能获得锁,而在队列中的锁还要进行唤醒才能继续尝试获取锁,而且线程的执行顺序一般来说不影响程序的运行。

17volatile

Java内存模型

  1. 在多线程环境下,保证变量的可见性。使用了 volatile 修饰变量后,在变量修改后会立即同步到主存中,每次用这个变量前会从主存刷新。

  2. 禁止 JVM 指令重排序。

  3. 单例模式双重校验锁变量为什么使用 volatile 修饰?禁止 JVM 指令重排序,new Object()分为三个步骤:申请内存空间,将内存空间引用赋值给变量,变量初始化。如果不禁止重排序,有可能得到一个未经初始化的变量。

18线程的五种状态

  1. New:一个新的线程被创建,还没开始运行。

  2. Runnable
    一个线程准备就绪,随时可以运行的时候就进入了 Runnable 状态。

    Runnable 状态可以是实际正在运行的线程,也可以是随时可以运行的线程。

    多线程环境下,每个线程都会被分配一个固定长度的 CPU 计算时间,每个线程运行一会儿就会停止让其他线程运行,这样才能让每个线程公平的运行。这些等待 CPU 和正在运行的线程就处于 Runnable 状态。

  3. Blocked
    例如一个线程在等待 I/O 资源,或者它要访问的被保护代码已经被其他线程锁住了,那么它就在阻塞 Blocked 状态,这个线程所需的资源到位后就转入 Runnable 状态。

  4. Waiting(无限期等待)
    如果一个线程在等待其他线程的唤醒,那么它就处于 Waiting 状态。以下方法会让线程进入等待状态:
    Object.wait()
    Thread.join()
    LockSupport.park()

  5. Timed Waiting(有期限等待)
    无需等待被其他线程显示唤醒,在一定时间后有系统自动唤醒。

    以下方法会让线程进入有限等待状态:Thread.sleep(sleeptime)
    Object.wait(timeout)
    Thread.join(timeout)
    LockSupport.parkNanos(timeout)
    LockSupport.parkUntil(timeout)

  6. dead 死亡状态):一个线程正常执行完毕,或者意外失败,那么就结束了。

19线程使用方式

  1. 继承 Tread 类

  2. 实现 Runnable 接口

  3. 实现 Callable 接口:带有返回值

20ThreadLocal

  1. 场景:主要用途是为了保持线程自身对象和避免参数传递,主要适用场景是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。

  2. 原理:为每个线程创建变量副本,不同线程之间不可见,保证线程安全。使用 ThreadLocalMap 存储变量副本,以 ThreadLocal 为 K,这样一个线程可以拥有多个 ThreadLocal 对象。

  3. 实际:使用多数据源时,需要根据数据源的名字切换数据源,假设一个线程设置了一个数据源,这个时候就有可能有另一个线程去修改数据源,可以使用 ThreadLocal 维护这个数据源名字,使每个线程持有数据源名字的副本,避免线程安全问题。

21线程池

1、 线程池分类
FixThreadPool 固定数量的线程池,适用于对线程管理,高负载的系统

SingleThreadPool 只有一个线程的线程池,适用于保证任务顺序执行

CacheThreadPool 创建一个不限制线程数量的线程池,适用于执行短期异步任务的小程序,低负载系统

ScheduledThreadPool 定时任务使用的线程池,适用于定时任务

2、 线程池的几个重要参数

  • int corePoolSize, 核心线程数
  • int maximumPoolSize, 最大线程数
  • long keepAliveTime, TimeUnit unit, 超过 corePoolSize 的线程的存活时长,超过这个时间,多余的线程会被回收。
  • BlockingQueue workQueue, 任务的排队队列
  • ThreadFactory threadFactory, 新线程的产生方式
  • RejectedExecutionHandler handler, 拒绝策略

3、 线程池线程工作过程
corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝策略

核心线程在线程池中一直存活,当有任务需要执行时,直接使用核心线程执行任务。当任务数量大于核心线程数时,加入等待队列。当任务队列数量达到队列最大长度时,继续创建线程,最多达到最大线程数。当设置回收时间时,核心线程以外的空闲线程会被回收。如果达到了最大线程数还不能够满足任务执行需求,则根据拒绝策略做拒绝处理。

4、 线程池拒绝策略(默认抛出异常)

5、 如何根据 CPU 核心数设计线程池线程数量

  • IO 密集型 2nCPU
  • 计算密集型 nCPU+1
    • 其中 n 为 CPU 核心数量,可通过 Runtime.getRuntime().availableProcessors() 获得核心数:。
    • 为什么加 1:即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保 CPU 的时钟周期不会被浪费。


gongzhong号后台回复:八股文、Java八股文领取本文PDF资料,也可以加我领取,回复pdf。



01

回复 (Java八股文、八股文) :获取Java程序员八股文

回复 (简历模板) :获取300套精美简历模板
回复 (电子书):获取500本程序员必备电子书
回复 (大数据):获取大数据学习资料
回复 (101) :获取kw免费听音乐软件
回复 (彩色昵称) :获取彩色昵称,做别人列表最靓的仔





相关资源