简约版八股文(day1)

Java基础

面向对象的三大基本特征

封装:将一些数据和对这些数据的操作封装在一起,形成一个独立的实体。隐藏内部的操作细节,并向外提供一些接口,来暴露对象的功能。
继承:继承是指子类继承父类,子类获得父类所有的非私有属性和方法,子类可继续拓展自己的属性和方法,这样就大大提高了代码的复用性和扩展性。
多态:多态是指子类对象可以直接赋值给父类变量,但运行时依然保持子类的行为特征,也就是说相同类型的变量调用同一个方法时,可以有不同的行为特性。

重写和重载的区别

重写:是子类对父类方法的重新实现,方法名和参数要与父类一样,另外,返回值要小于等于父类方法返回值,抛出的异常也要小于等于父类方法,访问修饰符大于等于父类方法,不能重写父类private方法
重载:重载发生在同一个类中,重载方法与原有方法具有相同的方法名,但参数个数或者类型必须不同,返回类型、访问修饰符以及抛出的异常可以相同也可以不同,所以,调用方法的时候只能以参数列表来区分。

==和equals()的区别

==运算符:作用于基本数据类型时,比较两个数值是否相等。作用于引用数据类型时,是比较两个对象的内存地址是否相同,即判断它们是否为同一个对象。
equals():没有被重写时,与==一致。重写后,比较两个对象的内容,内容相等即返回true

String、StringBuffer和StringBuilder的区别

String:String类是不可变的,一旦String对象被创建后,包含在对象中的字符序列不可改变。
StringBuffer:StringBuffer是一个可变的字符串对象,并且它在对字符串的操作时,使用了synchronze关键字,对方法进行同步处理,是线程安全的。
StringBuilder:StringBuilder和StringBuffer一样是可变字符串对象,不同的是,StringBuilder是线程不安全的,所以效率更高,但只能在单线程情况下使用。

接口和抽象类的区别

1.接口只有定义,不能有方法实现。jdk8以后可以定义default方法体。而抽象类可以有定义,也可以有实现。
2.一个类可以实现多个接口,但只能继承一个抽象类。
3.接口体现的是一种规范,强调特定功能的实现。而抽象类是一种模板式设计,强调所属关系。
4.接口成员变量默认为public static final ,必须赋予初始值,且不能被修改。其所有的成员方法都是public abstract 修饰的。而抽象类中可以定义任意变量和常量。也可以定义普通方法和抽象方法。
5.接口中不能有构造方法,构造方法用于初始化成员变量,但接口中成员变量是常量,无需修改,接口时一种规范,被调用时,主要关注里面的方法,而方法不需要初始化。抽象类可以有构造方法,只是不能直接创建实例对象。而实例化子类的时候,就会初始化父类,不管是不是抽象类都会调用父类构造方法。

多态

多态是指同一行为,对于不同的对象具有多个不同表现形式:当一个子类继承了父类并且重写了父类的方法时,这个方法可以在不同的子类对象上表现出不同的行为,即同一个方法调用可以有不同的实现方式。
多态的使用场景包括但不限于以下几个方面:

  1. 方法重写(Overriding):多态允许子类重写父类的方法,通过子类对象调用同名方法时,根据实际的子类类型来决定调用哪个具体的方法。这样可以实现不同子类对同一方法的不同实现,增加了代码的灵活性和扩展性。
  2. 方法参数和返回类型:多态可以用于方法参数的传递和返回类型的定义。通过使用父类或接口类型作为方法参数或返回类型,可以接受或返回不同子类的对象,实现了代码的通用性和可复用性。
  3. 继承关系的统一处理:多态可以方便地对具有相同父类或实现相同接口的一组对象进行统一的处理。可以通过父类或接口类型的引用来引用不同子类的对象,从而对它们进行统一的操作和管理,减少了重复的代码。
  4. 接口的应用:接口是多态的一种常见应用场景。通过接口定义一组共同的行为,不同的类可以实现同一个接口,并在不同的情况下表现出不同的行为。通过接口类型的引用,可以调用实现了该接口的不同类的方法,实现了代码的灵活性和可扩展性。

_ 关于 final 关键字的一些总结 _

final关键字主要用在三个地方:变量、方法、类。

  1. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的
    变量,则在对其初始化之后便不能再让其指向另一个对象。
  2. 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。
  3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。
    在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的
    任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为fianl。

_ java异常如何处理_

捕获异常:将代码包裹在try块内部。当代码发生异常时,系统会为这个异常创建一个异常对象,然后java虚拟机会在在try块之后寻找可以处理这个异常对象的catch块
处理异常:在catch块中更具当前业务情况,进行相应的处理,比如返回空值,向外抛出异常等
回收资源:如果业务代码打开某个资源,比如数据库、网络连接、磁盘文件等,就需要在代码执行后关闭这些资源。关闭资源的代码就需要写在finally块内,不论是否发生异常,都会执行回收资源。

String

String 类型被声明为final ,不可以被继承;String字符串是不能被改变的。

String不可以改的原因:
  1. 内部使用char[]存储数据,该数组被声明为final
  2. String内部没有提供更改value数组的方法

image.png

反射

Java 反射是指在运行时动态地获取类的信息并操作类的成员变量、方法和构造方法等。
Java 反射的作用是增强程序的灵活性和扩展性。
反射在以下场景中有广泛的应用:

JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序
Eclispe、IDEA等开发工具利用反射动态解析对象的类型与结构,动态提示对象的属性和方法
Web服务器中利用反射调用了Sevlet的service方法
JDK动态代理底层依赖反射实现

  1. 框架和库开发:许多框架和库,如依赖注入框架(如Spring)和对象关系映射(ORM)工具(如Hibernate),使用反射来实现通用的、可扩展的功能。通过反射,这些框架可以在运行时动态地加载类、创建对象和调用方法。
  2. 编写通用代码:反射允许编写通用代码,可以处理未知的类和对象。例如,编写一个可以处理任意类的序列化或比较工具。
  3. 调试和测试:反射可用于调试和测试工具,使其能够在运行时检查和修改类的状态。
  4. 配置文件解析:反射可以用于解析配置文件,根据配置文件的信息动态地加载和使用类。

Java集合

常见的集合有那些?底层是?使用场景?

  1. ArrayList:底层使用数组实现的动态数组。ArrayList允许快速访问元素,并且支持动态增加和删除元素。适用于需要随机访问元素的场景,但不适合频繁的插入和删除操作。
  2. LinkedList:底层使用链表实现的双向链表。LinkedList支持高效的插入和删除操作,但访问元素的效率较低。适用于需要频繁插入和删除元素的场景。
  3. HashSet:底层使用哈希表实现的无序集合。HashSet不允许重复元素,并且提供了常数时间复杂度的添加、删除和查找操作。适用于需要快速查找和去重的场景。
  4. TreeSet:底层使用红黑树实现的有序集合。TreeSet按照元素的自然排序进行排序,或者通过Comparator进行自定义排序。适用于需要有序集合的场景。
  5. HashMap:底层使用哈希表实现的键值对映射。HashMap提供了常数时间复杂度的添加、删除和查找操作,但不保证元素的顺序。适用于需要快速查找和根据键获取值的场景。
  6. TreeMap:底层使用红黑树实现的有序键值对映射。TreeMap按照键的自然排序进行排序,或者通过Comparator进行自定义排序。适用于需要有序键值对映射的场景。

使用集合的场景如下:

  • 当需要存储一组对象并对其进行增删改查操作时,可以选择适合的集合类来满足需求。
  • 当需要快速访问和搜索元素,且不关心元素的顺序时,可以使用ArrayList或HashSet。
  • 当需要频繁插入和删除元素,或者需要维护元素的顺序时,可以使用LinkedList或TreeSet。
  • 当需要按照键进行查找和排序时,可以使用HashMap或TreeMap。

JDK7和JDK8的HashMap有什么区别

jdk7中的HashMap是基于数组+链表来实现的,它的底层维护一个Entry数组,它会根据键计算的hashCode将对应的KV键值对存储该数组中,一旦发生hashCode冲突,就会将KV键值对放到对应已有元素的后面,此时便形成一个链表式的存储结构。(hash冲突严重时,链表会很长,导致查询效率低下)
jdk8中的HashMap是基于数组+链表+红黑树实现的,它的底层维护一个Node数组。当链表存储数据个数大于等于8时,不在采用链式存储,而是采用红黑树存储结构。(查询时间复杂度优化,链表为O(n),红黑树为O(logN),提高查询效率

ArrayList和LinkedList的区别

1、ArrayList的实现基于数组,LinkedList的实现基于双向链表。
2、对于随机访问ArrayList要优于LinkedList,因为可以根据下标对元素进行随机访问,而LinkedList的每个元素都依靠地址指针和它后一个元素连接在一起,查找某个元素的时间复杂度是O(n);
3、在进行插入或者删除操作时,LinkedList要优于ArrayList,因为当元素被添加到LinkedList任意位置的时候不需要向ArrayList那样重新计算大小或者更新索引;
4、LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储两个引用,一个指向前一个元素,一个指向后一个元素;

_扩展: _

_1. HashMap 底层: -红黑树 _

HashMap的底层数据结构是一个数组,每个数组元素称为桶(bucket)。当向HashMap中添加键值对时,首先根据键的哈希值计算出存放的桶的位置,然后将键值对存储在该桶中。
每个桶中可以存储多个键值对,这些键值对使用链表(或红黑树)连接起来,以解决哈希冲突问题。当多个键经过哈希计算后,它们可能映射到同一个桶,形成冲突。在链表结构中,键值对按照插入的顺序存储在链表中;而在红黑树结构中,当链表长度超过一定阈值(默认为8)时,链表会被转换为红黑树,以提高查找效率。
这种数组加链表(或红黑树)的组合结构,使得HashMap能够在平均情况下提供常数时间复杂度的插入、删除和查找操作。
下面是一个简单示意图,展示了HashMap底层数据结构的组织方式:

   index 0    index 1     index 2      index 3
     ↓          ↓           ↓            ↓
+--------+  +--------+  +--------+   +--------+
|        |  |        |  |        |   |        |
|        |  |        |  |        |   |        |
+--------+  +--------+  +--------+   +--------+
  ↓          ↓           ↓            ↓
   Entry      Entry       Entry        Entry
  (链表/红黑树)   (链表/红黑树)    (链表/红黑树)    (链表/红黑树)

在实际使用HashMap时,我们可以通过键来查找值,HashMap会根据键的哈希值找到对应的桶,然后在桶内的链表(或红黑树)中进行遍历或搜索,以找到对应的值。
需要注意的是,为了保持HashMap的性能,在使用HashMap时应尽量避免哈希冲突过多,即要保证键的哈希函数能够均匀分布键的哈希值。如果哈希冲突过多,会导致链表过长或红黑树过高,进而影响HashMap的性能。

2. CurrentHashmap

ConcurrentHashMap是在多线程环境下使用的哈希表,它提供了线程安全的操作,并且性能较高。与普通的HashMap相比,ConcurrentHashMap采用了一些特殊的技术来实现并发访问的安全性,例如分段锁和CAS(Compare and Swap)操作。
以下是ConcurrentHashMap的一些特点和使用场景:

  1. 线程安全:ConcurrentHashMap通过细粒度的锁机制(分段锁)来实现线程安全,不同的线程可以并发地访问不同的段(Segment),从而提高并发性能。这使得ConcurrentHashMap非常适合在多线程环境下使用,多个线程可以同时读取和写入数据,而不会发生数据不一致的问题。
  2. _ 高并发性能_:由于ConcurrentHashMap采用了分段锁机制,它可以支持并发的读取操作,多个线程可以同时读取不同的段,从而提高了读取的性能。只有当写入操作发生时,才需要加锁保证数据的一致性,从而减少了锁的竞争,提高了并发性能。
  3. 适用于读多写少的场景:ConcurrentHashMap在读取操作上具有良好的扩展性和性能,特别适合于读多写少的场景。在并发读取较多的情况下,ConcurrentHashMap能够有效地提供高性能的并发访问。
  4. 不保证元素顺序:与HashMap一样,ConcurrentHashMap不保证元素的插入顺序,因为它是基于哈希表实现的。

需要注意的是,虽然ConcurrentHashMap提供了线程安全的操作,但在某些情况下,仍然需要额外的同步措施来确保数据的一致性。例如,如果多个线程依赖于先检查再执行的原子性操作,就需要使用额外的同步手段,比如使用putIfAbsent()方法。

5.IO流

字节流

以字节为单位进行读取和写入操作,适用于处理二进制数据或字节编码的数据。字节流主要由InputStreamOutputStream两个抽象类及其子类实现。常用的字节流类有FileInputStreamFileOutputStream等。

字符流

以字符为单位进行读取和写入操作,适用于处理文本数据。字符流主要由ReaderWriter两个抽象类及其子类实现。常用的字符流类有FileReaderFileWriter等。

对于图片上传的实现方式

  1. 本地上传:将用户上传的图片保存在服务器本地的文件系统中。可以使用字节流来读取上传的图片数据,并将其写入到服务器指定的目录中。这种方式简单直接,适用于小型应用或需要快速实现的情况。
  2. 本地图片服务器:搭建一个专门用于存储图片的本地服务器,用户上传的图片会保存在该服务器中。可以使用字节流读取上传的图片数据,并将其写入到本地图片服务器指定的目录中。在应用中使用图片时,可以通过访问本地图片服务器的URL来获取图片。
  3. 图片服务器(如阿里云 OSS):使用第三方的图片存储服务,如阿里云 OSS(Object Storage Service)。用户上传的图片会直接传输到图片服务器,并由图片服务器来管理和存储。一般情况下,这种方式会使用字节流来将上传的图片数据发送到图片服务器。

_ 图片后端处理上传逻辑_

  • 验证上传的文件:检查上传的文件是否为图片文件,可通过判断文件的 MIME 类型或文件扩展名进行验证。确保只接受有效的图片文件。
  • 生成文件名:为了避免重复文件名和命名冲突,可以在后端生成一个唯一的文件名,可以使用时间戳、UUID 或其他唯一标识符来命名。
  • 存储图片文件:将接收到的图片文件保存到服务器上的指定位置。可以使用字节流或文件流将图片数据写入磁盘中,同时根据需要创建文件夹结构进行组织。
  • 返回上传结果:将上传结果返回给前端,通常是一个包含上传成功或失败信息的响应。

6.线程

使用线程的主要原因(实现并发执行,提高程序的性能和响应性)

  1. 并发执行:线程使得程序能够同时执行多个任务,从而实现并发性。通过将程序拆分为多个线程,可以同时处理多个任务,提高程序的运行效率。例如,在一个Web服务器中,每个客户端请求可以由一个独立的线程来处理,从而实现并发处理多个请求。
  2. _ 提高响应性_:使用线程可以使程序更加灵活和响应性更好。通过将耗时的任务放在后台线程中执行,可以避免主线程被阻塞,使得程序在执行耗时操作时仍能响应用户的其他操作,提供更好的用户体验。
  3. 充分利用多核处理器:现代计算机通常具有多个处理核心,使用多线程可以充分利用多核处理器的优势,提高系统的处理能力和吞吐量。
  4. 异步编程:线程可以用于实现异步编程模型,将耗时的操作放在后台线程中执行,使得主线程能够继续执行其他任务,不需要等待耗时操作完成。这对于需要进行网络通信、数据库查询或IO操作等耗时操作的程序非常有用。
  5. 任务分离和模块化:使用线程可以将程序的不同部分分离出来,每个部分在独立的线程中执行,使得程序结构更加清晰和模块化。不同的线程可以专注于执行特定的任务,提高代码的可读性和维护性。

线程的状态图

+-------------------+
|     NEW (新建)     |
+-------------------+
         |
         v
+-------------------+
|   RUNNABLE (可运行) |
+-------------------+
         |
         v
+-------------------+
|    BLOCKED (阻塞)  |
+-------------------+
         |
         v
+-------------------+
|    WAITING (等待)  |
+-------------------+
         |
         v
+-------------------+
|   TIMED_WAITING   |
|  (计时等待/定时等待) |
+-------------------+
         |
         v
+-------------------+
|     TERMINATED    |
+-------------------+

MySQL

基本的SQL语句和简单的多表查询

CREATE TABLE students (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    age INT
);

CREATE TABLE courses (
    id INT PRIMARY KEY,
    course_name VARCHAR(50),
    student_id INT,
    FOREIGN KEY (student_id) REFERENCES students(id)
);
INSERT INTO students (id, name, age)
VALUES (1, 'John', 20);

INSERT INTO courses (id, course_name, student_id)
VALUES (1, 'Math', 1);
SELECT * FROM students;

SELECT * FROM courses;
UPDATE students SET age = 21 WHERE id = 1;
DELETE FROM students WHERE id = 1;
SELECT students.name, courses.course_name
FROM students
INNER JOIN courses ON students.id = courses.student_id;
SELECT students.name, orders.order_number
FROM students
LEFT JOIN orders ON students.id = orders.student_id;
SELECT students.name, orders.order_number
FROM students
RIGHT JOIN orders ON students.id = orders.student_id;

两种常见的存储引擎

InnoDB MyISAM
是否支持事务 支持 不支持
是否支持行级锁 支持 不支持
是否支持外键 支持 不支持
是否支持数据压缩 支持 不支持
是否有自己的缓冲池 没有

MyISAM适用于读操作远多余写操作的场景,而InnoDB适用于进行复制的数据操作、高并发、事务处理等场景。

数据库的优化

1.索引

索引是对数据库表的一列或者多列的值进行排序的一种结构,使用索引可以快速访问数据表中的特定信息。但可能所有失效。

索引失效原因:
  • 没有正确选择索引列:选择合适的索引列是关键,应考虑查询的频率和过滤条件。
  • 索引列上有函数操作:如果在索引列上使用函数操作,索引可能不会被使用。
  • 数据量过小:对于较小的数据集,全表扫描可能比使用索引更高效。
B树和B+树是常见的索引数据结构
  • B树:B树是一种自平衡的搜索树,每个节点可以包含多个子节点。B树适用于随机读写的场景,比如数据库中的索引。
  • B+树:B+树是在B树基础上进行了优化的数据结构。B+树的所有叶子节点形成了一个有序链表,可以加快范围查询和顺序访问的速度。B+树适用于范围查询和顺序访问较多的场景。

2. 分库分表 读写分离 (Mycat 别人写好的直接用)

分库分表:

分库分表是将一个大型数据库拆分成多个小型数据库或表的过程。以减轻单一数据库的负载压力,提高数据库的扩展性和性能。分库分表的主要好处包括:

  • 并发处理能力提升:通过将数据分散存储在多个数据库或表中,可以实现并行处理,提高数据库的并发处理能力。
  • 减少单表数据量:将大表拆分成多个小表,可以降低单表的数据量,提高查询性能。
  • 提供更好的数据隔离:不同的数据库或表可以用于存储不同的业务数据,从而实现更好的数据隔离和安全性。
读写分离:

主要目的是通过并行处理和负载均衡来提高数据库的读取性能和扩展性。常见的读写分离架构包括:

  • 主从复制:主数据库负责处理写操作,将写操作的日志传输给从数据库,从数据库则用于处理读操作。
  • 分布式数据库:将数据分散在多个数据库节点上,每个节点都可以处理读写操作。

读写分离的优点包括:

  • 提高读取性能:读操作可以分担到多个从数据库节点上,从而提高读取性能。
  • 负载均衡:通过将读操作分配到不同的从数据库节点,可以实现负载均衡,减轻主数据库的负载压力。
  • 改善并发处理能力:读写分离可以提高数据库的并发处理能力,增加系统的吞吐量。

_Mycat是一个开源的数据库中间件,提供了分库分表和读写分离等功能,可以简化分布式数据库架构的搭建和管理过程。 _

数据库的备份和恢复

全量导出备份+日志备份和回复+ 定时备份

mysql事务

事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务的四大特性(ACID)
原子性:事务的最小执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用
一致性:执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的
隔离性:并发访问数据时,事务之间互不干扰,各并发事务之间数据库是独立的
持久性:一个事务被提交后,它对数据库的改变是持久的,即使数据库发生故障也不应该对其产生影响

并发事务可能带来的问题

脏读:某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个回滚了操作,则后一个事务所读取的数据就会是不正确的
幻读:在一个事务的两次查询的数据不一致(发生在新增或删除)
不可重复读:一个事务内多次读同一个数据,这个事务未结束时,另一个事务在中间更新了这个数据(发生在修改)

事务的隔离级别

READ_UNCOMMITTED(读未提交):最低的隔离级别,允许读取没有提交的数据变更,可能造成脏读、幻读、不可重复读
RDAD_COMMITTED(读已提交):允许读取并发事务已经提交的数据,可以防止脏读
REPEATABLE_READ(可重复读):对同一字段的多次读取结果都是一致的触发数据被本身事务所修改,可以防止脏读和不可重复读
SERIALIZABLE(可串行化):最高隔离级别,所有事务依次执行,事务之间相互不干扰

当MySQL单表数据量过大时,常见的优化措施

1.限定数据的范围:禁止不带任何限制数据范围的查询语句
2.读、写分离:拆分数据库,主库负责写,从库负责读
3.垂直分表:将一张表按照字段分成多个表,每个表存储一部分字段
4.水平分表:在同一个数据库内,将一张表数据按照一定规则拆分到多个表
5.增加缓存

posted @ 2023-07-01 22:28  陈步汀  阅读(58)  评论(0编辑  收藏  举报