java面试知识点精华提炼(一)

简介: java面试知识点精华提炼(一)

java基础

== 和 equals 的区别?

equals 和 == 最大的区别是一个是方法一个是运算符。

==如果比较的对象是基本数据类型,则比较的是数值是否相等;如果比较的是引用数据类型,则比较的是对象的地址值是否相等。

equals():用来比较方法两个对象的内容是否相等。

注意:equals 方法不能用于基本数据类型的变量,如果没有对 equals 方法进行重写,则比较的是引用类型的变量所指向的对象的地址。

int 和 Integer 的区别

int是基本数据类型,Integer是包装类(Integer是对象),Integer可以像对象一样被操作

java集合

  1. Collection:Collection 是集合 List、Set、Queue 的最基本的接口。
  2. Iterator:迭代器,可以通过迭代器遍历集合中的数据
  3. Map:是映射表的基础接口

1、Collection 和 Map 的继承体系:

list 集合
  • List 是有序、且可以重复的 Collection集合。List 一共三个实现类,分别是 ArrayList、Vector 和 LinkedList。
ArraList

ArrayList 内部是通过数组实现,特点:读取快,增删慢。并且它是线程不安全的。


因为数组元素之间不能有间隔,所以从中间位置增删元素,需要对数组进行复制、移动、代价比较高。因此读取快,增删慢。

ArrayList 初始大小是10,每次扩容是原来的1.5倍。

ArrayList每次扩容都是通过Arrays.copyof(elementData,newCapacity)来实现的。

在知道元素的大致数量时提前指定集合的大小,可以做到优化作用。

LinkList

  • LinkedList 是用链表结构存储数据的双向链表、支持序列化,特点:读取慢,增删快。并且它是线程不安全的。
  • LinkedList 提供了 List 接口中没有定义的方法,用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
Vector(数组实现、线程同步)
  • Vector 也是通过数组实现,不同的是它支持线程的同步,所以是线程安全的。
  • 实现同步需要很高的花费,因此访问它比访问 ArrayList 慢。
Set 集合
  • Set 是无序、且不可以重复的 Collection集合。
  • set中是以对象的hashcode值作为判断,判断两个对象是否相等。
  • 如果想要让两个不同的对象视为相等的,就必须覆盖 Object 的 hashCode 方法和 equals 方法。
HashSet(Hash 表)

HashSet存放的是哈希值,存储元素的顺序是按照哈希值来存的。

取数据也是按照哈希值取得。元素的哈希值是通过元素的hashcode 方法来获取的。

HashSet 首先判断两个元素的哈希值如果一样,会接着比较equals 方法,如果equals 方法结果相等就视为同一个元素,结果不相等就视为不同元素。

哈希值相同 equals 方法不相等的元素,是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中),也就是哈希一样的存一列

HashSet 通过 hashCode 值来确定元素在内存中的位置,一个 hashCode 位置上可以存放多个元素。


  • 如图 1 表示 hashCode 值不相同的情况; 图2 表示 hashCode 值相同,但 equals 不相同的情况。

  • 当new 一个HashSet实例时, 其实底层是新创建了一个HashMap实例。 HashSet中的元素实际上由HashMap的key来保存,而HashMap的value则存储了一个PRESENT,它是一个静态的Object对象。
TreeSet(二叉树)

TreeSet 是使用二叉树的原理对 add 的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入到二叉树指定的位置。


Integer 和 String 对象都可以进行默认的 TreeSet 排序,而自定义类的对象是不可以的,自己定义的类必须实现 Comparable 接口,并且覆写相应的 compareTo()函数,才可以正常使用。


在覆写 compare()函数时,要返回相应的值才能使 TreeSet 按照一定的规则来排序,比较此对象与指定对象的顺序。

如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

LinkHashSet(HashSet+LinkedHashMap)

LinkedHashSet 通过传递一个标识参数,调用父类的构造器,底层构造一个 LinkedHashMap 来实现,在相关操

作上与父类 HashSet 的操作相同,直接调用父类 HashSet 的方法即可。

Map
HashMap(数组+链表+红黑树)

java7中 hashmap 使用,数组+链表实现

java8以后 hashmap 使用,数组+链表+红黑树实现

HashMap 根据键的 hashCode 值存储数据,具有很快的访问速度,但遍历顺序却是不确定的。 查询快无序

HashMap 中 key 不可重复,value可以重复,且线程不安全。

HashMap 可以用 Collections 的 synchronizedMap 方法实现线程安全的能力,或者使用 ConcurrentHashMap。


大方向上,HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。

上图中每个绿色的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。

Java8 以后当链表中的元素超过了 8 个以后,会将链表转换为红黑树,

红黑树为了降低时间复杂度,由原来的 O(n) 降为 O(LogN),以加快检索速度。

capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。

loadFactor:负载因子,默认为 0.75。

threshold:扩容的阈值,等于 capacity * loadFactor

ConcurrentHashMap

Java7 以前使用 segment(分段) + 数组 + 链表 实现,使用对 segment(分段)加锁实现线程安全,简称分段锁。

Java8 以后使用 数组 + 链表 + 红黑树 实现,使用Synchronized和CAS实现线程安全。

1、Java7 以前 ConcurrentHashMap 由一个个 Segment(分段) 组成。


Segment(分段) 通过继承 ReentrantLock 来进行加锁,每次加锁的操作锁住的是一个 segment(分段),这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。

Segment(分段) 数量默认是 16,初始化时可以设置为其他值,但是一旦初始化以后,它是不可以扩容的。

因为是对 Segment(分段)单独加锁,所以理论上,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上


2、Java8以后 ConcurrentHashMap 和 HashMap 实现是差不多的,只是比HashMap多了Synchronized和CAS实现线程安全。


在 put 时首先计算 key 的 hash 值,判断 hash 值有没有冲突,没有冲突直接 CAS 插入。

如果 hash 值存在冲突,就使用 Synchronized 加锁,加锁时会只锁住单一链表或者红黑树的头结点。

HashTable(线程安全)

Hashtable 很多的常用功能与 HashMap 类似,不同的是它继承自 Dictionary 类,并且是线程安全的。


并发性不如 ConcurrentHashMap,因为 ConcurrentHashMap 引入了分段锁。


Hashtable 不建议使用,可以使用 HashMap 和 ConcurrentHashMap 代替。


TreeMap(可排序)

TreeMap 实现 SortedMap 接口,根据键排序,默认是按键值的升序排序,也可以自定义排序。

如果使用到排序的功能,建议使用 TreeMap。

在使用 TreeMap 时,key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的

Comparator,否则会在运行时抛出 java.lang.ClassCastException 类型的异常。

LinkHashMap(记录插入顺序)
  • LinkedHashMap 是 HashMap 的一个子类,保存了元素的插入顺序,在用 Iterator 遍历
  • LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。

反射机制

  • 反射机制是程序在运行中,获取任意一个类的属性和方法,并且可以调用。以达到动态获取类信息、动态调用对象的方法。
  • 反射将类的各个组成部分封装成其他对象,这就是反射机制。
反射的应用场合

Java 对象在运行时可能会出现两种类型:编译时类型和运行时类型。


编译时的类型由声明对象时用的类型来决定,运行时的类型由实际赋值给对象的类型决定 。

如:Person p=new Student();

其中编译时类型为 Person,运行时类型为 Student。

程序在运行时想要获取 Student 对象的真实信息,就只能依靠运行时信息来发现该对象和类的真实信息,此时就必须使用到反射了


反射 API

Class 类:反射的核心类,可以获取类的属性,方法等信息。

Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。

Method 类: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。

Constructor 类: Java.lang.reflec 包中的类,表示类的构造方法。

获取 Class 对象的 3 种方法

// 1 调用某个对象的 getClass()方法
Person p=new Person();
Class clazz=p.getClass();
// 2 调用某个类的 class 属性来获取该类对应的 Class 对象
Class clazz=Person.class;
// 3 使用 Class 类中的 forName()静态方法(最安全/性能最好/最常用)
Class clazz=Class.forName("类的全路径"); 
  • 通过 Class 类中的方法获取并查看该类中的方法和属性。
// 获取 Person 类的 Class 对象
Class clazz=Class.forName("reflection.Person");
// 使用.newInstane 方法创建对象
Person p=(Person) clazz.newInstance();
// 获取构造方法创建对象并设置属性
Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
Person p1=(Person) c.newInstance("李四","男",20);
//获取 Person 类的所有方法信息
// getMethods(),该方法是获取本类以及父类或者父接口中所有的公共方法(public修饰符修饰的)
// getDeclaredMethods(),该方法是获取本类中的所有方法,包括私有的(private、protected、默认以及public)的方法。
Method[] method=clazz.getDeclaredMethods();
for(Method m:method){
 System.out.println(m.toString());
 // 调用方法 使方法执行
 m.invoke(p, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
}
//获取 Person 类的所有成员属性信息
Field[] field=clazz.getDeclaredFields();
for(Field f:field){
 System.out.println(f.toString());
}
//获取 Person 类的所有构造方法信息
Constructor[] constructor=clazz.getDeclaredConstructors();
for(Constructor c:constructor){
 System.out.println(c.toString());
}

序列化和反序列化

序列化:将对象写入到IO流中

反序列化:从IO流中恢复对象

在类中增加 writeObject 和 readObject 方法可以实现自定义序列化策略。

通过 ObjectOutputStream 和 ObjectInputStream 对对象进行序列化及反序列化。

意义:序列化机制允许将实现序列化的Java对象转换为字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。

使用场景:所有可在网络上传输的对象都必须是可序列化的,比如RMI(remote method invoke,即远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错;所有需要保存到磁盘的java对象都必须是可序列化的。

通常建议:程序创建的每个JavaBean类都实现Serializeable接口。并且创建序列化ID,用来判断是否可以反序列化。

序列化并不保存静态变量

要想将父类对象也序列化,就需要让父类也实现 Serializable 接口。

如果不想让某个变量被序列化,使用transient修饰,反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化。

序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。

反序列化时必须有序列化对象的class文件。

同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。


IO和NIO

1.主要区别:


io是面向流、阻塞的。 Nio是面向缓存、非阻塞的。

传统IO基于字节流和字符流进行操作。

NIO基于Channel(通道)、Buffer(缓冲区)进行操作,数据从通道读取到缓冲区中,或者从缓冲区读取到通道中。

NIO中使用Selector(选择区)监听多个Channel(通道)事件,因此单个线程可以监听多个数据通道。(比如:连接打开,数据到达)


2、IO 工作流程:

由于Java IO是阻塞的,所以当面对多个流的读写时需要多个线程处理。例如在网络IO中,Server端使用一个线程监听一个端口,一旦某个连接被accept,创建新的线程来处理新建立的连接。其中 read/write 是阻塞的。


3、NIO 工作流程:

NIO 提供 Selector 实现单个线程管理多个channel的功能。select 调用可能是阻塞的,也可以是非阻塞的。但是read/write是非阻塞的!

4、NIO为什么会被阻塞:

//这个方法可能会阻塞,直到有一个已注册的事件发生,或者当一个或者更多的事件发生时
//可以设置超时时间,防止进程阻塞
selector.select(long timeout);
 
//使用此方法可以防止阻塞,阻塞在select()方法上的线程也可以立刻返回,不阻塞
selector.selectNow();
 
//可以唤醒阻塞状态下的selector
selector.wakeup();

5、BIO、NIO、AIO 有什么区别:


BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方 便,并发处理能力低。

NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道) 通讯,实现了多路复用。

AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的 操作基于事件和回调机制。

代码执行顺序:父类子类 静态代码块、构造代码块、构造方法执行顺序

父静态、子静态、父构造代码块、父构造方法、子构造代码块、子构造方法

java多线程实现

创建多线程有4种方式,其中两种有返回值,两种没有返回值。

1.继承Thread类,重写run方法(其实Thread类本身也实现了Runnable接口)

2.实现Runnable接口,重写run方法

3.实现Callable接口,重写call方法(有返回值)

4.使用线程池(有返回值)


四种线程池

Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 ExecutorService。


newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
  • newFixedThreadPool
    创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
new ThreadPoolExecutor(int, int, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
  • newScheduledThreadPool
    创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
new ScheduledThreadPoolExecutor(10);
线程生命周期(状态)

线程有五种状态 新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead) 。

新建(New):使用 new 创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值

就绪(Runnable):线程对象调用 start()方法之后,该线程处于就绪状态。JVM 为其创建方法调用栈和程序计数器,等待调度运行。

运行(Running):如果处于就绪状态的线程获得了 CPU,开始执行 run() 方法的线程执行体,则该线程处于运行状态。

阻塞(Blocked):

指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。

直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状态。

阻塞的情况分三种:

等待阻塞(o.wait->等待对列):运行(running)的线程执行 o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。

同步阻塞(lock->锁池):运行(running)的线程没有获取到同步锁,该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。

其他阻塞(sleep/join):运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。

线程死亡(DEAD):线程会以下面三种方式结束,结束后就是死亡状态。

正常结束,run()或 call()方法执行完成,线程正常结束。

异常结束,线程抛出一个未捕获的 Exception 或 Error。

调用 stop,直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。

sleep 与 wait 区别

sleep()方法属于 Thread 类,而 wait()方法,则是属于Object 类中的。

sleep()方法是暂停执行指定的时间,让出 cpu 给其他线程,但是他的监控状态依然保持,当指定的时间到了又会自动恢复运行状态。

在调用 sleep()方法的过程中,线程不会释放对象锁。

在调用 wait()方法的时候,线程会放弃对象锁,并进入等待队列,当其他线程调用notify()或者notifyAll()方法时,当前线程进入就绪状态


start 与 run 区别
  • start()方法使用来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码。
  • run()方法是线程体,包含了要执行的内容,直接调用run()方法,并不是启动线程,和普通方法是一样的。

java框架

spring框架

spring核心

IOC(Inverse of Control 控制反转):Spring 通过配置文件来利用 Java 反射,实例化并控制对象的,生命周期和对象间的关系。还提供了 Bean 实例缓存、生命周期管理、 Bean 实例代理、事件发布、资源装载等高级服务。

AOP(Aspect Oriented Programming 面向切面编程):AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。

Spring 常用模块
  • spring Beans:
  • spring context:
  • spring AOP:
  • spring DAO:
  • spring ORM:
  • spring web:


spring常用注解

@Controller(@RestController):使用在类上,提供分发器扫描使用,@RestController相当于@ResponseBody+@Controller

@RequestMapping(@GetMapping、@PostMapping):使用在类上,指定URL在哪里处理Get,Post使用在方法上,用来处理请求。

@ResponseBody:用于方法上,用来返回JSON格式

@RequestBody:用于参数上,表示改参数是JSON字符串

@RequestParam:用于参数上,用来修改接收参数名称

@PathVariable:使用在方法上,把参数绑定到路径上

@Service:使用在Service层类上用于实例化Bean

@Repository:使用在Dao层类上用于实例化Bean

@Autowired:使用在字段上用于根据类型依赖注入

@Component:使用在类上用于实例化Bean

@Configuration:使用在类上,用于定义配置类

@ModeAttribute:将参数和返回值绑定到Model中

@SessionAttribute:设置在Model中的属性名称

@Valid:使用在属性上,用于校验数据

@CookieValue:用来获取Cokkie中的值

Spring IOC

IOC容器初始化时序图:


java面试知识点精华提炼(二)

https://developer.aliyun.com/article/1583814?spm=a2c6h.13148508.setting.16.3cca4f0enPJc3f

相关文章
|
22天前
|
安全 Java 容器
【Java集合类面试二十七】、谈谈CopyOnWriteArrayList的原理
CopyOnWriteArrayList是一种线程安全的ArrayList,通过在写操作时复制新数组来保证线程安全,适用于读多写少的场景,但可能因内存占用和无法保证实时性而有性能问题。
|
22天前
|
存储 安全 Java
【Java集合类面试二十五】、有哪些线程安全的List?
线程安全的List包括Vector、Collections.SynchronizedList和CopyOnWriteArrayList,其中CopyOnWriteArrayList通过复制底层数组实现写操作,提供了最优的线程安全性能。
|
21天前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
43 2
|
22天前
|
Java
【Java集合类面试二十八】、说一说TreeSet和HashSet的区别
HashSet基于哈希表实现,无序且可以有一个null元素;TreeSet基于红黑树实现,支持排序,不允许null元素。
|
22天前
|
Java
【Java集合类面试二十三】、List和Set有什么区别?
List和Set的主要区别在于List是一个有序且允许元素重复的集合,而Set是一个无序且元素不重复的集合。
|
22天前
|
Java
【Java集合类面试二十六】、介绍一下ArrayList的数据结构?
ArrayList是基于可动态扩展的数组实现的,支持快速随机访问,但在插入和删除操作时可能需要数组复制而性能较差。
|
22天前
|
存储 Java 索引
【Java集合类面试二十四】、ArrayList和LinkedList有什么区别?
ArrayList基于动态数组实现,支持快速随机访问;LinkedList基于双向链表实现,插入和删除操作更高效,但占用更多内存。
|
22天前
|
Java
【Java集合类面试二十二】、Map和Set有什么区别?
该CSDN博客文章讨论了Map和Set的区别,但提供的内容摘要并未直接解释这两种集合类型的差异。通常,Map是一种键值对集合,提供通过键快速检索值的能力,而Set是一个不允许重复元素的集合。
|
22天前
|
消息中间件 缓存 算法
Java多线程面试题总结(上)
进程和线程是操作系统管理程序执行的基本单位,二者有明显区别: 1. **定义与基本单位**:进程是资源分配的基本单位,拥有独立的内存空间;线程是调度和执行的基本单位,共享所属进程的资源。 2. **独立性与资源共享**:进程间相互独立,通信需显式机制;线程共享进程资源,通信更直接快捷。 3. **管理与调度**:进程管理复杂,线程管理更灵活。 4. **并发与并行**:进程并发执行,提高资源利用率;线程不仅并发还能并行执行,提升执行效率。 5. **健壮性**:进程更健壮,一个进程崩溃不影响其他进程;线程崩溃可能导致整个进程崩溃。
27 2
|
22天前
|
存储 Java
【Java集合类面试二十九】、说一说HashSet的底层结构
HashSet的底层结构是基于HashMap实现的,使用一个初始容量为16和负载因子为0.75的HashMap,其中HashSet元素作为HashMap的key,而value是一个静态的PRESENT对象。