JAVA面试八股文(基础)

基础知识八股文

Java主要的数据类型

有:整数类型(byte: 1个字节、short: 2个字节、int: 4个字节、long: 8个字节)、浮点类型、字符类型、布尔类型以及引用类型(class: 类、接口(如String、Date等)interface、array: 数组)

JDK、JRE、JVM 三者之间的关系?

JDK( java development kit):java开发工具包,用来开发Java程序的,针对java开发者。

JRE(java runtime environment):java运行时环境,针对java用户。

JVM(java virtual machine):java虚拟机用来解释执行字节码文件(class文件)的。

Java中的list

List的继承、实现关系

其继承了Collection接口并由AbstractList来实现,Collection又继承了Iterable接口

Collection:集合层次结构中的根接口。一个集合表示一组对象,称为它的元素。一些集合允许重复元素,而另一些则不允许。有些是有序的,有些是无序的。

Iterable:实现此接口允许对象成为“for-each 循环”语句的目标。

ArrayList和Linklist的区别

ArrayList底层是数组,增删慢,查找快。

LinkedList底层是双向链表,增删快,查找慢。

ArrayList:

1.基于数组,需要连续内存

2. 随机访问快(可以根据下标访问)

3.尾部插入和删除性能可以,其他的头部中间插入删除慢(数组扩容的问题因为次数不多影响不大所以不考虑)

4.可以CPU缓存,利用局部性原理。

LinkedList:

1.基于双向链表,不需要连续内存

2.随机访问慢(需要迭代遍历)

3.头尾插入删除性能高,中间插入删除性能慢,

4.占内存很多

随机访问: 为什么ArrayList随机访问快而LinkedList随机访问慢呢,这是因为连续内存的原因,ArrayList连续内存也就意味着如果知道了第一个值的内存地址时,那我们就可以很方便的计算出后面任意一个地址值的索引

如何保证ArrayList的线程安全?

(1)使用collentions.synchronizedList()方法为ArrayList加锁

(2)使用Vector,Vector底层与Arraylist相同,但是每个方法都由synchronized修饰,速度很慢

(3)使用juc下的CopyOnWriterArrayList,该类实现了读操作不加锁,写操作时为list创建一个副本,期间其它线程读取的都是原本list,写操作都在副本中进行,写入完成后,再将指针指向副本。

异常

分为三大类:检查型异常(文件未找到、网络连接断开)、非检查型异常(例如空指针、数组下标越界等)、错误

(指程序本身无法修复的问题,通常是由于系统环境变化、组件失效或内存不足等原因引起)

不管是哪种类型的异常,都可以通过 try-catch-finally 块来处理,也可以使用 throw 和 throws 关键字将异常抛出,从而传递给调用者处理。

Exception和Error有什么区别 ?

在Java中,Exception和Error是两个不同的类,它们都继承自Throwable类。下面是它们之间的区别:

Exception(异常):

Exception表示在程序执行过程中可能出现的可处理的异常情况。它一般由代码逻辑错误、外部条件变化等原因引起,可以通过适当的处理措施来恢复正常的程序执行。Exception分为两种类型:

受检异常(Checked Exception):编译器要求必须在代码中显式地处理受检异常,否则代码无法通过编译。常见的受检异常包括IOException、SQLException等。

非受检异常(Unchecked Exception):编译器对非受检异常不强制要求进行处理,但可以选择处理或者将其抛给上层调用者处理。常见的非受检异常包括NullPointerException、ArrayIndexOutOfBoundsException等。

Error(错误):

Error是指应用程序通常无法处理或恢复的严重问题。

Error通常表示虚拟机(JVM)的错误状态或系统级错误,例如OutOfMemoryError、StackOverflowError等。

Error通常意味着应用程序处于不可恢复的状态,因此一般不被捕获和处理。

与异常不同,Error没有规定要求应用程序处理或捕获它们。

总结:

Exception是预期的、可以被捕获和处理的异常,而Error是不可恢复的严重问题,通常由虚拟机或系统级错误引起。在实际编程中,我们应该根据情况选择适当的异常处理和错误处理机制,以确保程序的稳定性和可靠性。

常见的异常类有哪些?

在Java中,有一些常见的异常类,可以根据其特性和使用场景进行分类。以下是一些常见的异常类:

RuntimeException(运行时异常):

NullPointerException:空指针异常,当对一个对象引用调用方法或访问属性时,对象引用为空。

ArrayIndexOutOfBoundsException:数组下标越界异常,当尝试访问数组的不存在的索引时抛出。

IllegalArgumentException:非法参数异常,当传递给方法的参数不合法时抛出。

IOException(输入输出异常):

FileNotFoundException:文件未找到异常,当尝试打开或读取不存在的文件时抛出。

EOFException:文件结束异常,当从数据流读取数据时到达文件末尾时,而你还在试图读取更多的数据抛出。

SocketException:套接字异常,当与套接字相关的操作失败时抛出。

SQLException(数据库异常):

SQLSyntaxErrorException:SQL语法错误异常,当执行SQL语句时遇到语法错误时抛出。

DataAccessException:数据访问异常,当访问数据库或数据存储出现问题时抛出。

ClassNotFoundException:类未找到异常,当尝试加载不存在的类时抛出。

实际上在Java中还有很多其他的异常类,了解这些异常类可以帮助我们更好地进行异常处理和错误处理,提高程序的可靠性和可维护性。

Java 中创建对象的几种方式

  1. 使用 new 关键字:最常见的方式,这种方式我们还可以调用任意的构造器(无参的和有参的)。
  2. 使用Class.newInstance:通过 Class类的newInstance创建对象,必须有public的无参构造器才行。
  3. 使用Constructor.newInstance:通过 java.lang.relect.Constructor类中的newInstance方法调用有参数的和私有的构造函数。
  4. 使用Clone:通过对象的 clone() 方法创建一个对象的副本。需要对象实现Cloneable接口并重写 clone()方法。
  5. 使用反序列化:通过将对象转换为字节流并保存到文件或传输到网络,然后再从字节流中重新创建对象。需要对象实现Serializable

final、finally、finalize 的区别

final 关键字:用于修饰类、方法、变量、入参和对象

应用于类时,表示该类是最终类,不能被其他类继承。

应用于方法时,表示该方法是最终方法,不能被子类重写。

应用于变量时,表示该变量是一个常量,只能赋值一次。

应用于入参时,表示该入参在方法内无法被修改。

应用于对象时,该对象的引用不能被修改,但对象本身的状态是可变的。

finally 关键字:异常处理机制中的一部分,用于定义在 try-catch-finally 块中的 finally 块

不论是否发生异常,finally 块中的代码都会执行。

主要用于释放资源、关闭连接等必须确保执行的操作。

finalize 方法:是一个对象的方法,定义在 Object 类中

在垃圾回收器将对象回收之前调用。

可以重写 finalize 方法,在其中编写对象在被回收前需要进行的清理操作,如释放资源等。

请注意,不推荐使用 finalize 方法进行内存资源的释放,因为它没有被及时执行的保证,也可能导致性能问题

引用

什么是引用

简单的说,引用是一个对象别名,与被引用的对象共享同一块内存区域。对象在创建时会请求一块内存空间来保存数据,会根据对象大小分配存储空间大小不等的内存区域。在Java中,访问对象时,不会直接访问对象在内存中的数据,而是通过引用去访问。因此,引用也是一种数据类型,类似于C/C++ 语言中的指针。引用定义时在栈中分配内存,引用变量在程序运行到作用域外释。

基本数据类型

java 中一共分为 8 种基本数据类型:byte、short、int、long、float、double、char、boolean,

其中 byte、short、int、long 是整型。float、double 是浮点型,char 是字符型,boolean 是布尔型。

引用类型

java 为每种基本类型都提供了对应的封装类型,分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean。引用类型是一种对象类型,它的值是指向内存空间的引用,就是地址。Java有四种引用类型:强引用、软引用、弱引用、虚引用。

Java 中的参数传递时传值呢?还是传引用?

在 Java 中,方法参数传递是按值传递的。这意味着在方法调用时,实际上是将参数的值进行拷贝并传递给方法内部使用,而不是直接传递参数本身的引用。

对于基本数据类型(如整数、浮点数等):传递的是其值的拷贝。任何对参数值的修改都不会影响原始变量。

对于引用类型(如对象、数组等):传递的是引用的值的拷贝,也就是说方法内部的参数和原始变量将引用同一个对象。虽然我们可以通过方法内部的参数修改对象的状态,但是对于引用本身的修改是不会影响原始变量的。

== 和 equals 的区别

==:如果作用于基本数据类型的变量,则直接比较其存储的值是否相等;

    如果作用于引用类型的变量,则比较的是所指向的对象的地址是否相等。

equals:比较是否是同一个对象。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,在没有重写equals()方法的类中,和==一样比较引用类型变量所指向的对象地址是否相等。重写equals方法就看各个类重写后的逻辑,比如String类,虽然是引用类型,但是String类中重写了equals方法,方法内部比较的是字符串中的各个字符是否全部相等。

两个对象的 hashCode() 相同,则 equals() 也一定为 true 吗

两个对象的hashCode()相同,equals()不一定为true;

两个对象的equals为true,则两个对象的hashcode一定为true;

原因:我们看下hashcode的计算方法:hashcode其实就是对一个对象中的每个元素进行一次运算生成的结果值,两个不同的对象是有可能出现同一个hash值的。

& 和 && 、||和|的区别?

&&:短路与;  &:逻辑与

共同点:他们两边的条件都成立的时候最终结果才是true;

不同点:&&只要第一个条件不成立,后面的条件就不再判断,而&判断的是所有条件

使用&&:不会出现错误。因为第一个条件不满足时,直接返回false。如果第一个条件满足才会判断第二个条件。

使用&:出现错误。判断所有条件才会返回。

||:短路或    |:逻辑或

共同点:只要两个判断条件其中有一个成立最终的结果就是true。

不同点:||只要满足第一个条件,后面的条件就不再判断,而|要对所有的条件进行判断。

使用||:不会出现错误,第一个条件满足直接返回。

使用|:出现错误,所有条件都会进行判断。

什么是 Java 的序列化,如何实现 Java 的序列化?

定义:序列化是指将一个对象转换为字节流,以便在网络上传输或保存到文件中。序列化过程还可以通过反序列化将字节流重新转换为对象。

实现方式:通过实现 java.io.Serializable 接口。该接口是一个标记接口,没有任何方法定义,只要一个类实现了Serializable接口,就表示该类的对象可以被序列化。Java序列化机制会根据对象的类结构自动进行序列化和反序列化操作。

线程

是进程中的一个执行单元,一个进程可以包含多个线程。与进程不同的是,同一进程中的多个线程共享相同的内存空间和系统资源,因此,它们之间的通信和数据交换更加方便高效。

线程的启动方式

1.继承Thread类,重写run方法

2.继承Runable接口,重写run方法,然后通过Thread执行

3.前两个不能获得线程的执行结果,可以使用Callable(声明线程任务)和futuretask(管理线程结果)来执行线程并获取线程返回的结果。

Java线程同步方法

同步方法(synchronized method)

使用synchronized关键字修饰方法,使得同一时刻只有一个线程能执行该方法。

同步块(synchronized block)

使用synchronized关键字和对象锁实现同步,使得同一时刻只有一个线程能进入同步块。

Lock锁

使用java.util.concurrent.locks.Lock接口实现锁机制,主要有ReentrantLock。

同步集合类

如Vector、Hashtable、ConcurrentHashMap等线程安全集合类。

线程池

使用java.util.concurrent.ExecutorService等线程池控制最大并发数。

信号量(Semaphore)

使用java.util.concurrent.Semaphore控制同时访问特定资源的线程数量。

读写锁(ReadWriteLock)

使用java.util.concurrent.locks.ReadWriteLock将锁分为读锁和写锁。

多线程:(抢红包)

说说什么是进程和线程?

进程和线程是操作系统中的概念,用于描述程序运行时的执行实体。

进程:一个程序在执行过程中的一个实例,每个进程都有自己独立的地址空间,也就是说它们不能直接共享内存。进程的特点包括:

需要占用独立的内存空间;

可以并发地执行多个任务;

进程之间需要通过进程间通信(IPC)来交换数据;

线程:进程中的一个执行单元,一个进程中可以包含多个线程,这些线程共享进程的内存空间。线程的特点包括:

线程共享进程内存空间,可以方便、高效地访问变量;

同一个进程中的多个线程可以并发地执行多个任务;

线程之间切换开销小,可以实现更细粒度的控制,例如 UI 线程控制界面刷新,工作线程进行耗时的计算等。

线程相比于进程,线程的创建和销毁开销较小,上下文切换开销也较小,因此线程是实现多任务并发的一种更加轻量级的方式。

为什么调用start()方法时会执行run()方法,那怎么不直接调用run()方法?

JVM执行start方法,会先创建一个线程,由创建出来的新线程去执行thread的run方法,这才起到多线程的效果。

start()和run()的主要区别如下:

start方法可以启动一个新线程,run方法只是类的一个普通方法而已,如果直接调用run方法,程序中依然只有主线程这一个线程。

start方法实现了多线程,而run方法没有实现多线程。

start不能被重复调用,而run方法可以。

start方法中的run代码可以不执行完,就继续执行下面的代码,也就是说进行了线程切换。然而,如果直接调用run方法,就必须等待其代码全部执行完才能继续执行下面的代码。

什么是线程上下文切换?

线程上下文切换指的是在多线程运行时,操作系统从当前正在执行的线程中保存其上下文(包括当前线程的寄存器、程序指针、栈指针等状态信息),并将这些信息恢复到另一个等待执行的线程中,从而实现线程之间的切换。

线程间有哪些通信方式?

线程间通信是指在多线程编程中,各个线程之间共享信息或者协同完成某一任务的过程。常用的线程间通信方式有以下几种:

共享变量:共享变量是指多个线程都可以访问和修改的变量,它们通常是在主线程中创建的。多个线程对同一个共享变量进行读写操作时,可能会出现竞态条件导致数据错误或程序异常。因此需要使用同步机制比如synchronized、Lock等来保证线程安全

管道通信:管道是一种基于文件描述符的通信机制,形成一个单向通信的数据流管道。它通常用于只有两个进程或线程之间的通信。其中一个进程将数据写入到管道(管道的输出端口),而另一个进程从管道的输入端口读取数据

信号量:信号量是一种计数器,用于控制多个线程对资源的访问。当一个线程需要访问资源时,它需要申请获取信号量,如果信号量的计数器值大于 0,则可以访问资源,否则该线程就会等待。当线程结束访问资源后,需要释放信号量,并将计数器加1

条件变量:条件变量是一种通知机制,用于在多个线程之间传递状态信息和控制信息。当某个线程需要等待某个条件变量发生改变时,它可以调用 wait() 方法挂起,并且释放所占用的锁。当某个线程满足条件后,可以调用 notify() 或者 signal() 方法来通知等待该条件变量的线程继续执行。

线程池七大参数

核心线程数:线程池中的基本线程数量

最大线程数:当阻塞队列满了之后,逐一启动

最大线程的存活时间:当阻塞队列的任务执行完后,最大线长的回收时间

最大线程的存活时间单位

阻塞队列:当核心线程满后,后面来的任务都进入阻塞队列

线程工厂:用于生产线程

任务拒绝策略:阻塞队列满后,拒绝任务,有四种策略(1)抛异常(2)丢弃任务不抛异常(3)打回任务(4)尝试与最老的线程竞争

死锁

是指两个或多个进程在执行过程中,因竞争资源而产生的一种僵局,它们都在等待对方释放资源,导致所有进程都无法继续执行下去。

具体来说,T1先申请A资源,然后去申请B资源,但是此时发现B资源已经被T2占用了,所以T1就会被阻塞,等待T2释放B资源。同时,T2也先申请B资源,

然后去申请A资源,但是此时发现A资源已经被T1占用了,所以T2也会被阻塞,等待T1释放A资源。这样,两个线程就形成了一个死锁的状态,无法继续执行。

因此,在编写Java程序时,需要注意避免出现死锁问题,可以采用一些技术手段,例如锁定顺序、设置超时时间、使用非阻塞算法等。

说一说什么是死锁:

多个线程,或者一组线程,他们是相互竞争的关系,但是他们又互持资源,然后又相互等待,这样子他就会导致永久的这种阻塞的现象,其实这就是死锁了。

诱发死锁的原因是?

回答:诱发死锁的原因主要有四点:

1.互斥条件

2.占有且等待

3.不可抢夺资源(不可抢占)

4.循环等待

死锁问题是怎么解决的呢?

其实死锁基本上一发生,就基本上很难去认为的去干预解决掉它,所以一般我都是去规避它。那么其实刚刚我说到了死锁发生的四个条件。只要同时满足了,就会触发死锁这种现象,所以只要我们去打破其中任意一条,就不会发生死锁。

比如第一个互斥条件,这个互斥条件基本上是无法破坏的,因为线程本身就是通过互斥来解决线程安全这个问题的,所以这个基本就不用考虑了,主要考虑或者分析的就是2,3,4点。

对于第二个占有且等待,那么解决方法就是让这个线程一次性去申请所有的所需的资源,这样子就不存在去等待资源这个问题了。

第三个就是不可抢占了,这个就是当我们的线程已经持有一部分资源了,但是它还需要其他资源的时候,如果它去申请但是申请不到,或者得到的资源不够,那么我们就让这个线程主动去释放掉它已有的所有资源,这样我们就可以解决不可抢占资源这个问题了。

第四个就是循环等待了,它是按照这种申请资源来进行预防的,按顺序去申请资源来预防的,因为资源它是有线性顺序的,所以申请的时候我们可以先申请序号比较小的,然后再去申请比较大的,那么申请完毕之后他就不会存在循环的等待这种问题了。

如何排查死锁问题

可以使用jdk自带的命令行工具排查:

使用jps查找运行的Java进程:jps -l

使用jstack查看线程堆栈信息:jstack -l  进程id

基本就可以看到死锁的信息。

还可以利用图形化工具,比如JConsole(JConsole工具在JDK的bin目录中)。出现线程死锁以后,点击JConsole线程面板的检测到死锁按钮,将会看到线程的死锁信息。

抽象类和接口的区别

抽象类需要使用extends关键字继承,而接口需要使用implements实现。

抽象类的权限可以为public、protected、default,而接口权限必须为public。

定义方式不同。抽象类使用abstract关键字定义,接口使用interface关键字定义。

继承方式不同。类可以实现多个接口但只能继承一个抽象类。接口可以被多个类实现。

成员不同。抽象类可以包含抽象方法和非抽象方法,也可以包含属性。接口只能定义抽象方法,不能包含属性。

实现不同。抽象类可以具体实现一些方法,子类继承后可以直接使用,但接口所有方法都必须在实现类中实现。

泛型

泛型的本质是为了将类型参数化, 也就是说在泛型使用过程中,数据类型被设置为一个参数,在使用时再从外部传入一个数据类型;而一旦传入了具体的数据类型后,传入变量(实参)的数据类型如果不匹配,编译器就会直接报错。这种参数化类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。

用户态与内核态

用户态和内核态是操作系统中的两种不同的运行级别。

用户态指的是程序执行在非特权模式下,只能访问受限的资源,无法直接操作硬件设备。在用户态下,进程之间相互隔离,不能互相干扰,

这样可以保证系统的稳定性和安全性。

内核态指的是程序执行在特权模式下,可以直接访问系统的所有资源,包括CPU、内存、I/O设备等,也可以进行特权操作(例如修改系统设置)。

在内核态下,进程之间共享系统资源,可以相互访问和影响,这样可以提高系统的效率和灵活性。

java反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。总结:反射就是把java类中的各种成分映射成一个个的Java对象。在运行时动态地获取、操作和修改类或对象的属性、方法、构造函数等信息的能力,而不需要在编译时预先知道类的具体信息。

反射的应用场景有哪些?

反射是Java框架的灵魂技术,很多框架都使用了反射技术,如spring,Mybatis,Hibernate等。

  1. JDBC 的数据库的连接

在JDBC连接数据库中,一般包括加载驱动,获得数据库连接等步骤。而加载驱动,就是引入相关Jar包后,通过Class.forName()加载数据库的驱动程序。

  1. xml或properties等配置文件加载

Spring 通过 XML 配置模式装载 Bean,也是反射的一个典型例子。

装载过程:

将程序内XML 配置文件加载入内存中

Java类解析xml或者properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息

使用反射机制,得到Class实例

动态配置实例的属性

这样做当然是有好处的不用每次都去new实例了,并且可以修改配置文件,比较灵活

反射有什么优缺点?

反射的优点:

动态性:反射提供了在运行时动态地探索和操作类的能力。它允许我们在运行时获取类的信息、创建对象、调用方法和修改字段的值,从而使程序更加灵活、可扩展和动态。

适应复杂环境:反射可以应对一些复杂的场景,如在插件化系统中根据配置文件加载类、动态代理、识别和处理注解等。

反射的缺点:

性能问题:由于反射涉及到动态解析和调用,所以它通常比直接调用代码性能较低。反射需要进行额外的检查和处理,可能会导致性能下降。

安全问题:反射可以绕过访问控制限制,例如访问私有方法和字段。这可能会导致安全隐患,因此在使用反射时需要小心处理,并确保只在必要情况下使用。

String、StringBuilder、StringBuffer 的区别?

可变性:

String 类是不可变类,一旦创建就无法改变其内容。对于每次修改操作(例如拼接字符串),都会创建一个新的字符串对象,旧对象则成为垃圾数据,需要等待垃圾回收。

StringBuilder 和 StringBuffer 类是可变的,它们可以直接在原始对象上进行修改而不创建新的对象。这种特性使得在频繁拼接或修改字符串时更高效。

线程安全性:

String 类是线程安全的,因为它的不可变性保证了多个线程同时访问同一个字符串对象时的安全性。

StringBuilder 类是非线程安全的,它的方法没有进行同步处理。如果在多线程环境下使用 StringBuilder,需要额外采取措施保证线程安全。

StringBuffer 类是线程安全的,它的方法进行了同步处理,因此可以在多线程环境下使用。

性能:

在单线程环境下,StringBuilder 的性能通常优于 StringBuffer,因为 StringBuilder 不进行同步处理,省去了同步的开销。

在多线程环境下,由于 StringBuffer 进行了同步处理,可能会带来额外的性能开销。但当线程同步是必需的时候,StringBuffer 是一个可靠的选择。

String 类由于不可变性,每次修改都要创建新的对象,性能相对较差。但由于字符串常量池的优化,字符串的比较和共享等操作依然高效。

String str = "i" 与 String str = new String("i") 一样吗?

不一样

因为内存的分配方式不一样。String str="i"的方式,JVM会将其分配到常量池中;而 String str=new String(“i”)方式,则会被分到堆内存中。

String str="i"

Java 虚拟机会将其分配到常量池中:常量池不会重复创建对象。

在String str1="i"中,把i值存在常量池,地址赋给str1。

String str2=“i”,则会把i的地址赋给str2,但是i对象不会重新创建,他们引用的是同一个地址值,共享同一个i内存。

String str = new String(“i”)

Java 虚拟机会将其分到堆内存中:堆内存会创建新的对象。

String str3=new String(“i”),会创建一个新的i对象,然后将新对象的地址值赋给str3。虽然str3和str1的值相同但是地址值不同。

Overload、Override、Overwrite的区别?

在面向对象编程中,有三个概念经常用到:Overload(重载)、Override(重写)和Overwrite(覆盖),这些概念描述了不同的方法或函数之间的关系。

重载(Overload):

定义:在同一个类中,可以定义多个具有相同名称但参数列表不同的方法,它们被称为方法的重载。

特点:

方法名相同,参数列表不同。

重载方法可以有不同的返回类型。

重载方法必须在同一个类中。

重载方法的区分依据是参数的个数、类型或者顺序

重写(Override):

定义:子类继承自父类时,可以对父类的方法进行重新实现,这被称为方法的重写。

特点:

子类中的方法与父类中的方法具有相同的名称、参数列表和返回类型。

子类中的方法不能比父类的方法访问性更严格。

子类方法抛出的异常不能比父类方法抛出的异常更多。

子类方法可以覆盖父类方法的实现,提供自己的实现逻辑。

覆盖(Overwrite):

定义:在文件操作中,覆盖(Overwrite)通常指的是将已有的文件内容替换为新的内容。

特点:

覆盖通常发生在文件写入时,用新的内容覆盖原有的内容,使其被替代。

覆盖可能会导致原文件的内容丢失,因此在进行覆盖操作时要小心。

总结:

重载(Overload)指的是在同一个类中定义多个具有相同名称但参数列表不同的方法。

重写(Override)指的是子类继承父类并重新实现父类中的方法。

覆盖(Overwrite)通常指的是在文件操作中,将文件内容替换为新的内容。

Java中的IO流的分类?说出几个你熟悉的实现类?

在Java中,IO流可以根据其功能和作用进行分类。主要分为四种类型:字节流、字符流、缓冲流和对象流。

总结:日常使用根据需要选择合适的流类型进行数据的读取和写入操作。

字节流适合处理二进制数据。

字符流适合处理文本数据。

通过缓冲流可以提高读写效率,减少对底层资源的访问次数。

并行与并发有什么区别?

并行和并发都是指多个任务同时执行的概念,但是它们之间有着明显的区别。

并行:多个任务在同一时刻同时运行,通常需要使用多个处理器或者多核处理器来实现。例如,一个计算机同时执行多个程序、多个线程或者多个进程时,就是采用并行的方式来处理任务,这样能够提高计算机的处理效率。

并发:多个任务同时进行,但是这些任务的执行是交替进行的,即一个任务执行一段时间后,再执行另外一个任务。它是通过操作系统的协作调度来实现各个任务的切换,达到看上去同时进行的效果。例如,一个多线程程序中的多个线程就是同时运行的,但是因为 CPU 只能处理一个线程,所以在任意时刻只有一个线程在执行,线程之间会通过竞争的方式来获取 CPU 的时间片。

总的来说,虽然并行和并发都是多任务处理的方式,但是并行是采用多核处理器等硬件实现任务同步执行,而并发则是通过操作系统的调度算法来合理地分配系统资源,使得多个任务看上去同时执行。

Hash表

实现哈希表我们可以采用两种方法:

1、数组+链表

2、数组+二叉树

处理哈希冲突

开放寻址法

其实简单来说就是,既然位置被占了,那就另外再找个位置不就得了,怎么找其他的位置呢?这里其实也有很多的实现,我们说个最基本的就是既然当前位置被占用了,我们就看看该位置的后一个位置是否可用,也就是1的位置被占用了,我们就看看2的位置,如果没有被占用,那就放到这里呗,当然,也有可能2的位置也被占用了,那咱就继续往下找,看看3的位置,一次类推,直到找到空位置。

 拉链法

也是比较常用的,HashMap就是使用了这种方法。

之前说的开放寻址法采用的方式是在数组上另外找个新位置,而拉链法则不同,解决办法就是链表,这时候这个1的位置存放的不单单是之前的那个Entry了,此时的Entry还额外的保存了一个next指针,这个指针指向数组外的另外一个位置,将李四安排在这里,然后张三那个Entry中的next指针就指向李四的这个位置,也就是保存的这个位置的内存地址,如果还有冲突,那就把又冲突的那个Entry放在一个新位置上,然后李四的Entry中的next指向它,这样就形成了一个链表。

哈希表的扩容

当哈希表被占的位置比较多的时候,出现哈希冲突的概率也就变高了,所以很有必要进行扩容。

那么这个扩容是怎么扩的呢?这里一般会有一个增长因子的概念,也叫作负载因子,简单点说就是已经被占的位置与总位置的一个百分比,比如一共十个位置,现在已经占了七个位置,就触发了扩容机制,因为它的增长因子是0.7,也就是达到了总位置的百分之七十就需要扩容。

还拿HashMap来说,当它当前的容量占总容量的百分之七十五的时候就需要扩容了。

而且这个扩容也不是简单的把数组扩大,而是新创建一个数组是原来的2倍,然后把原数组的所有Entry都重新Hash一遍放到新的数组。 因为数组扩大了,所以一般哈希函数也会有变化,这里的Hash也就是把之前的数据通过新的哈希函数计算出新的位置来存放。

哈希表如何读取数据

比如我们现在要通过学号102011来查找学生的姓名,怎么操作呢?我们首先通过学号利用哈希函数得出位置1,然后我们就去位置1拿数据啊,拿到这个Entry之后我们得看看这个Entry的key是不是我们的学号102011,一看是101011,什么鬼,一边去,这不是我们要的key啊,然后根据这个Entry的next知道下一给位置,在比较key,好成功找到李四。

HashMap

是Java中的一种常用的数据结构,用于存储键值对(Key-Value)的集合。HashMap使用哈希表(Hash Table)来实现,具有高效的插入、查找和删除操作。

在HashMap中,每个键(Key)都唯一,而值(Value)可以重复。它通过使用键的哈希码(Hash Code)来确定键值对在哈希表中的存储位置,从而实现快速的查找操作。

HashMap的主要特点包括:

无序性:HashMap中的键值对没有固定的顺序,不保证存储顺序和插入顺序一致。

高效性:HashMap的插入、查找和删除操作的时间复杂度为O(1),即常数时间。

允许空键和空值:HashMap允许键和值都为null。

非线程安全:HashMap不是线程安全的,如果多个线程同时访问一个HashMap,可能会导致数据不一致的问题。

在使用HashMap时,需要特别注意键的唯一性。当两个键的哈希码相同时,称之为哈希冲突(Hash Collision)。HashMap使用链表和红黑树来解决哈希冲突,当链表长度过长时,会将链表转换为红黑树,以提高查找效率。

把元素当成key,下标当成value,key是唯一的。

HashMap不是线程安全的主要原因有以下几点:

HashMap内部的数据结构是基于数组+链表的,在进行插入、删除或修改操作时,需要重新计算hash值并重新链接节点,这个过程是非原子性的,容易导致并发问题。

在扩容操作时,需要将原来的数组中的所有节点重新插入新的更大的数组中,这个过程也是一个非原子性的过程,容易发生线程安全问题。

HashMap在进行插入和删除操作时,不会加锁或者采用其他同步策略来保证线程安全。

HashMap在进行get操作时,如果key对应的value为null,会进行扩容操作,这个过程也是非原子的。

HashTable与HashMap的区别

(1)HashTable的每个方法都用synchronized修饰,因此是线程安全的,但同时读写效率很低

(2)HashTable的Key不允许为null

(3)HashTable只对key进行一次hash,HashMap进行了两次Hash

(4)HashTable底层使用的数组加链表

面向对象和面向过程的区别

面向对象有封装、继承、多态性的特性,所以相比面向过程易维护、易复用、易扩展,但是因为类调用时要实例化,所以开销大性能比面向过程低。

深拷贝和浅拷贝

浅拷贝:浅拷贝只复制某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存

深拷贝:深拷贝会创造一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对对象。

多态的作用

多态的实现要有继承、重写,父类引用指向子类对象。它的好处是可以消除类型之间的耦合关系,增加类的可扩充性和灵活性。

Java内存模型

JMM(Java内存模型 )屏蔽了各种硬件和操作系统的内存访问差异,实现让Java程序在各平台下都能达到一致的内存访问效果,它定义了JVM如何将程序中的变量在主存中读取

具体定义为:所有变量都存在主存中,主存是线程共享区域;每个线程都有自己独有的工作内存,线程想要操作变量必须从主从中copy变量到自己的工作区,每个线程的工作内存是相互隔离的

由于主存与工作内存之间有读写延迟,且读写不是原子性操作,所以会有线程安全问题。

保证并发安全的三大特性?

 原子性:一次或多次操作在执行期间不被其他线程影响

可见性:当一个线程在工作内存修改了变量,其他线程能立刻知道

有序性:JVM对指令的优化会让指令执行顺序改变,有序性是禁止指令重排

MYsql

mysql事务特性

原子性:一个事务内的操作统一成功或失败

一致性:事务前后的数据总量不变

隔离性:事务与事务之间相互不影响

持久性:事务一旦提交发生的改变不可逆

MySQL有哪些索引

主键索引:一张表只能有一个主键索引,主键索引列不能有空值和重复值

唯一索引:唯一索引不能有相同值,但允许为空

普通索引:允许出现重复值

组合索引:对多个字段建立一个联合索引,减少索引开销,遵循最左匹配原则

全文索引:myisam引擎支持,通过建立倒排索引提升检索效率,广泛用于搜索引擎

如何设计数据库

(1)抽取实体,如用户信息,商品信息,评论

(2)分析其中属性,如用户信息:姓名、性别...

(3)分析表与表之间的关联关系

然后可以参考三大范式进行设计,设计主键时,主键要尽量小并且定义为自增和不可修改。

三大范式

第一范式:每个列都不可以再拆分。

第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。

第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。

五种状态码说一下。201是什么?302是什么?

100~199:提示信息,收到请求,需请求者继续执行操作

200~299:请求成功,已被成功接收并处理

300~399:重定向,需进一步操作

400~499:客户端错误,请求语法错误,或无法实现

500~599:服务器错误,不能实现请求

201 Created:成功请求并创建新的资源。有一个新的资源已经依据请求而建立,且其 URI 随Location 头信息返回。

302 Found:临时性重定向,请求的资源被分配了新的URI,本次用这个URI。

TCP和UDP

TCP是面向连接的,UDP是面向无连接的

TCP是可靠的,UDP是不可靠的(在TCP协议中使用了接收确认和重传机制,使得每一个信息都能保证到达,是可靠的。而UDP是尽力传送,没有应答和重传机制,UDP只是将信息发送出去,对方收不收到也不进行应答。所以UDP协议是不可靠的。)

TCP是面向字节流的,UDP是面向报文的

UDP的头部开销小,TCP的头部开销大

三次握手

三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。

为什么需要三次握手,两次不行吗?

弄清这个问题,我们需要先弄明白三次握手的目的是什么,能不能只用两次握手来达到同样的目的。

第一次握手:客户端发送网络包,服务端收到了。

这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。

第二次握手:服务端发包,客户端收到了。

这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。

第三次握手:客户端发包,服务端收到了。

这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。

四次挥手

1、第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。

2、第二次握手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。

3、第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。

4、第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态

5、服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。

DNS:

对于本例,简单来说,当我们在浏览器地址栏中输入某个Web服务器的域名时。用户主机首先用户主机会首先在自己的DNS高速缓存中查找该域名所应的IP地址.如果没有找到,则会向网络中的某台DNS服务器查询,DNS服务器中有域名和IP地映射关系的数据库。当DNS服务器收到DNS查询报文后,在其数据库中查询,之后将查询结果发送给用户主机。

域名服务器可以划分为以下四种不同的类型:(根域名服务器、顶级域名服务器、权限域名服务器 、本地域名服务器)

DNS为什么用UDP?

更正确的答案是 DNS 既使用 TCP 又使用 UDP。当进行区域传送(主域名服务器向辅助域名服务器传送变化的那部分数据)时会使用 TCP,因为数据同步传送的数据量比一个请求和应答的数据量要多,而 TCP 允许的报文长度更长,因此为了保证数据的正确性,会使用基于可靠连接的 TCP。

当客户端向 DNS 服务器查询域名 ( 域名解析) 的时候,一般返回的内容不会超过 UDP 报文的最大长度,即 512 字节。用 UDP 传输时,不需要经过 TCP 三次握手的过程,从而大大提高了响应速度,但这要求域名解析器和域名服务器都必须自己处理超时和重传从而保证可靠性。

DNS域名解析过程

域名解析包含两种查询方式,分别是递归查询和迭代查询。

浏览器输入地址后做了什么?

为什么要进入时间等待状态?

若客户端发送确认释放包后直接关闭,而服务端因为某种原因没有收到客户端的确认释放包,就会一直发送确认请求,而客户端永远不会再响应该请求。

TCP 滑动窗口

TCP 流量控制,主要使用滑动窗口协议,滑动窗口是接受数据端使用的窗口大小,用来告诉发送端接收端的缓存大小,以此可以控制发送端发送数据的大小,从而达到流量控制的目的。如果TCP发送方收到接收方的零窗口通知后,会启动持续计时器。计时器超时后向接收方发送零窗口探测报文,如果响应仍为0,就重新计时,不为0就打破死锁

TCP拥塞控制

发送方会维护一个拥塞窗口大小的状态变量,大小取决于网络的拥塞程度。发送方的发送窗口大小是取接收方接收窗口和拥塞窗口中较小的一个

拥塞控制有四种算法:

慢开始:从小到大主键发送窗口,每收到一个确认报文窗口大小指数增长

拥塞避免:当窗口大小到达一定阈值时,转为拥塞避免,每收到一个确认报文窗口大小+1。若此时网络超时,就把阈值调小一半,重新慢开始

快重传:要求接收方收到请求后要立即回复

快恢复:发送方连续收到多个确认时,就把拥塞避免阈值减小,然后直接开始拥塞避免

TCP超时重传

发送方在发送按数据后一定时间内没有收到接收方响应报文,就会重新发送刚刚的报文,接收到收到报文后会对该报文的序列号进行检验,已存在就抛弃

TCP可靠传输的实现

TCP是靠滑动窗口协议和连续ARQ协议配合流量控制和拥塞控制来保证的可靠传输。

ARQ是停止等待协议和自动重传请求,它规定TCP要为每一次传输的包编号,每发送一个包,要等待对方确认后才能发送下一个分组,若一段时间对方没有确认,就重新发送刚刚的报文。接收方会对数据包排序,把有序数据传给应用层,返回缺失的第一个ACK确认序列号给发送方,接收到收到报文后会对该报文的序列号进行检验,重复就丢弃。

HTTP 与 HTTPS  的区别

HTTPS:是以安全为目标的 HTTP 通道,是 HTTP 的安全版。HTTPS 的安全基础是 SSL。SSL 协议位于 TCP/IP 协议与各种应用层协议之间,为数据通讯提供安全支持。

1、HTTPS  协议需要到 CA (Certificate Authority,证书颁发机构)申请证书,一般免费证书较少,因而需要一定费用。(以前的网易官网是http,而网易邮箱是 https 。)

2、HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。

3、HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

4、HTTP 的连接很简单,是无状态的。HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。

session 和 cookie 有什么区别?

存储位置不同:session 存储在服务器端;cookie 存储在浏览器端。

安全性不同:cookie 安全性一般,在浏览器存储,可以被伪造和修改。

容量和个数限制:cookie 有容量限制,每个站点下的 cookie 也有个数限制。

存储的多样性:session 可以存储在 Redis 中、数据库中、应用程序中;而 cookie 只能存储在浏览器中。

说一下 session 的工作原理?

session 的工作原理是客户端登录完成之后,服务器会创建对应的 session,session 创建完之后,会把 session 的 id 发送给客户端,

客户端再存储到浏览器中。这样客户端每次访问服务器时,都会带着 sessionid,服务器拿到 sessionid 之后,在内存找到与之对应的 session 这样就可以正常工作了。

session和cookievue中local.storage)、token

cookie有点像身份证,浏览器第一次访问服务器时,服务器给浏览器发送一个身份证(cookie),浏览器存在本地,当浏览器再次访问服务器时带上身份证(cookie),服务器就知道浏览器是谁了

浏览器第一次访问服务器时,服务器生成一个session用于标识该浏览器,服务器返回给浏览器的响应中带了cookie,cookie中带了session id,浏览器把cookie存在本地,下次访问时带上cookie,服务器通过cookie中的session id标识浏览器

利用token进行用户身份验证的流程:

客户端使用用户名和密码请求登录

服务端收到请求,验证用户名和密码

验证成功后,服务端会签发一个token,再把这个token返回给客户端

客户端收到token后可以把它存储起来,比如放到cookie中

客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带

服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据.

session认证有如下的问题:

每个用户的登录信息都会保存到服务器的session中,随着用户的增多,服务器开销会明显增大

对于非浏览器的客户端、手机移动端等不适用,因为session依赖于cookie,而移动端经常没有cookie

因为session认证本质基于cookie,所以如果cookie被截获,用户很容易收到跨站请求伪造攻击。并且如果浏览器禁用了cookie,这种方式也会失效

由于基于Cookie,而cookie无法跨域,所以session的认证也无法跨域,对单点登录不适用

设计模式六大原则

(1)单一职责原则:一个类或者一个方法只负责一项职责,尽量做到类只有一个行为引起变化;

(2)里氏替换原则:子类可以扩展父类的功能,但不能改变原有父类的功能

(3)依赖倒置原则:高层模块不应该依赖底层模块,两者都应该依赖接口或抽象类

(4)接口隔离原则:建立单一接口,尽量细化接口

(5)迪米特原则:只关心其它对象能提供哪些方法,不关心过多内部细节

(6)开闭原则:对于拓展是开放,对于修改是封闭的

算法八股文

排序算法:

冒泡排序

思路:

左边大于右边交换一趟排下来最大的在右边。

插入排序

一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法

步骤:

1.从第一个元素开始,该元素可以认为已经被排序

2.取下一个元素tem,从已排序的元素序列从后往前扫描

3.如果该元素大于tem,则将该元素移到下一位

4.重复步骤3,直到找到已排序元素中小于等于tem的元素

5.tem插入到该元素的后面,如果已排序所有元素都大于tem,则将tem插入到下标为0的位置

6.重复步骤2~5

选择排序

思路:

每次从待排序列中选出一个最小值,然后放在序列的起始位置,直到全部待排数据排完即可。

实际上,我们可以一趟选出两个值,一个最大值一个最小值,然后将其放在序列开头和末尾,这样可以使选择排序的效率快一倍。

快速排序(Quick sort)

是对冒泡排序的一种改进

算法思路

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快速排序算法通过多次比较和交换来实现排序,其排序流程如下:

1、首先设定一个分界值,通过该分界值将数组分成左右两部分。

2、将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。

3、然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。

4、重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

贪心算法

每次都做出当前情况下最优的选择,以期望达到全局最优解。贪心算法做出选择时,只关注当前状态下的最优解,并不考虑更远的后效性影响,因此不能保证最终的决策一定是最优的。

深度优先搜索(DFS)和广度优先搜索(BFS)

是图和树中常用的遍历算法。DFS从一个节点开始,递归地访问它的所有子节点,

直到遇到叶节点或者其他结束条件;BFS则从一个节点开始,依次访问它的所有邻居节点,然后再访问这些邻居节点的邻居节点,以此类推。

深度优先搜索除了使用递归实现外,还可以用来实现:

使用栈实现深度优先搜索的步骤:

将起始节点加入栈中

如果栈不为空,则执行以下操作:

弹出栈顶节点作为当前节点

如果当前节点是目标节点,则搜索成功结束

否则,将当前节点的未访问的邻接节点压入栈

标记当前节点已访问

如果栈为空但未找到目标节点,则搜索失败

广度优先搜索除了使用队列实现外,也可以用双向链表来实现:

将起始节加入双向链表的头部

重复以下步骤,直到链表为空为止:

弹出链表头节点作为当前节点

如果当前节点是目标节点,则搜索成功结束

否则,将当前节点的未访问的邻接节点加入链表尾部

标记当前节点已访问

如果链表为空但未找到目标节点,则搜索失败

速记

冒泡排序:O(n^2)

选择排序:O(n^2)

插入排序:O(n^2)

快速排序:O(nlogn)

归并排序:O(nlogn)

先序遍历:根左右

中序遍历:左根右

后序遍历:左右根

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菲飛绯长美丽的巨兔12138

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值