java面向对象有哪些特征?
- 继承(复用)
- 封装(安全)
- 多态(三要素:继承、重写、父类引用指向子类对象)
静态方法为什么不能调用非静态成员
这个需要结合 JVM 的相关知识,主要原因如下:
静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。
在类的非静态成员不存在的时候静态成员就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。非静态成员?
包装类型和基本类型
- 包装类型不赋值就是 null ,而基本类型有默认值且不是 null。
- 包装类型可用于泛型,而基本类型不可以、
- 基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中
- 相比于对象类型, 基本数据类型占用的空间非常小
注意:基本数据类型存放在栈中是一个常见的误区! 基本数据类型的成员变量如果没有被 static 修饰的话(不建议这么使用,应该要使用基本数据类型对应的包装类型),就存放在堆中。
另外,包装类型有缓存机制。 - Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True or False。
超出对应范围才会去创建新的对象
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);false
#Integer i1=40 这一行代码会发生装箱,
#也就是说这行代码等价于 Integer i1=Integer.valueOf(40) 。
#因此,i1 直接使用的是缓存中的对象。而Integer i2 = new Integer(40) 会直接创建新的对象。
ArrayList和LinkedList区别?
都实现了List接口
不同点:
- ArrayList底层是用数组实现的,以O(1)的时间复杂度对元素进行随机访问。LinkedList是以元素列表的形式存储数据,每个元素都存储了它的前一个和后一个元素的地址引用,O(n)复杂度查找元素。
- 相比于ArrayList,LinkedList插入添加、删除数据更快,不需要重新计算大小、更新索引
高并发中的集合有哪些问题?
jdk1.8新特性
- 允许给接口添加一个非抽象的方法实现,只需要default关键字修饰即可
- Lambda表达式 函数式接口
抽象类和接口区别
语义上的区别:
- 抽象类 描述抽象的概念
- 接口 描述一系列事物共有的特征
- 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系
- 一个类只能继承一个类,但是可以实现多个接口
- 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值
浅拷贝、深拷贝、引用拷贝
hashCode()和equals()?
hashCode相同的对象不一定真的相等,因为hash算法可能会产生碰撞。
- 重写equals()方法时,也要重写hashCode()方法,因为equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
String、StringBuilder、StringBuffer
String 中的对象是不可变的,也就可以理解为常量,线程安全.StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
try-with-resources替代try-catch-finally
当遇到需要关闭的资源时,用第一个更好
try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
int b;
while ((b = bin.read()) != -1) {
bout.write(b);
}
}
catch (IOException e) {
e.printStackTrace();
}
泛型擦除
序列化与反序列化
为什么有了字节流,还有字符流呢?
字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好
进程与线程
总结:线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反
对象在内存中的存储布局
Object 0 = new Object()
1.对象的创建过程
- Markword 8字节(根据虚拟机位数决定),类型指针4字节表示该对象的类型,实例数据根据具体的类型计算大小,对齐指的是如果总的字节数不是8的倍数就要自动补齐,上面o占16字节
2.对象头具体包括什么? - Markword
-
类型指针
3.对象怎么定位
image.png
间接方式:
- 优点:对象小,垃圾回收的时候不用频繁改动t
- 缺点: 两次访问
直接方式: - 优点: 直接访问
-
缺点: GC时需要移动对象稍麻烦
4.对象怎么分配
image.png
5.对象的创建过程
1.new 申请空间
2.构造方法 成员属性赋值
3.引用与对象建立联系
6.DCL(双重检查)
DCL需要加volatile :保证线程的可见性,禁止指令重排
public class Single{
private static volatile Single INSTANCE;
private Single(){};
public static Single getInstance(){
if(INSTANCE==null){
//双重检查
synchronized(Single.class){
if(INSTANCE==null){
INSTANCE = new Single();
}
}
}
return INSTANCE;
}
}
多线程高并发问题
哲学家就餐问题
- 解决方法:合并锁、左撇子(高效率的话采用奇偶数交替左右)、lock.tryLock()
/**
* Description: 使用了ReentrantLock锁, 该类中有一个tryLock()方法, 在指定时间内获取不到锁对象, 就从阻塞队列移除,不用一直等待。
* 当获取了左手边的筷子之后, 尝试获取右手边的筷子, 如果该筷子被其他哲学家占用, 获取失败, 此时就先把自己左手边的筷子,
* 给释放掉. 这样就避免了死锁问题
*/
@Slf4j(topic = "z.PhilosopherEat")
public class PhilosopherEat {
public static void main(String[] args) {
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();
}
}
@Slf4j(topic = "z.Philosopher")
class Philosopher extends Thread {
final Chopstick left;
final Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
// 获得了左手边筷子 (针对五个哲学家, 它们刚开始肯定都可获得左筷子)
if (left.tryLock()) {
try {
// 此时发现它的right筷子被占用了, 使用tryLock(),
// 尝试获取失败, 此时它就会将自己左筷子也释放掉
// 临界区代码
if (right.tryLock()) { //尝试获取右手边筷子, 如果获取失败, 则会释放左边的筷子
try {
eat();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
right.unlock();
}
}
} finally {
left.unlock(); // 则会释放左边的筷子
}
}
}
}
private void eat() throws InterruptedException {
log.debug("eating...");
Thread.sleep(500);
}
}
// 继承ReentrantLock, 让筷子类称为锁
class Chopstick extends ReentrantLock {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
交替打印数字和字母
final Object 0 =new Object();
char[] a1= "1234567".toCharArray();
char[] a2 = "ABCDEFG".toCharArray();
new Thread(()->{
synchronized(o){
for(char c:a1){
System.out.println(c);
}
try(){
o.notify();//唤醒持有该锁的其他线程
o.wait();//持有该锁的当前线程等待,释放锁
}catch(){
}
}
o.notify();
},"1").start();
new Thread(()->{
synchronized(o){
for(char c:a2){
System.out.println(c);
}
try(){
o.notify();//唤醒持有该锁的其他线程
o.wait();//持有该锁的当前线程等待,释放锁
}catch(){
}
}
o.notify();
},"2").start();
生产者消费者问题 ReentrantLock Condition
ReentrantLock 可重入锁 一个锁可以有多个condition(阻塞队列)
package com.multithread.productandconsumer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author:wangjie
* @create:2022-07-07 10:14
* @Description:生产者消费者问题
*/
public class ProductAndConsumer {
int count=0,maxNum=3;
ReentrantLock lock = new ReentrantLock();
Condition product = lock.newCondition();
Condition consume = lock.newCondition();
public class Productor implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
lock.lock();//获取锁
try {
while(count > maxNum){//满了 不能生产
System.out.println("满了 不能生产");
product.await();//该线程进入product阻塞队列排队
}
count++;
System.out.println(Thread.currentThread().getName()+"生产者生产,目前总共有"+count+"个产品");
consume.signalAll();//唤醒consume队列中的所有等待线程
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();//释放锁
}
}
}
}
public class Consumer implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
lock.lock();//获取锁
try {
while(count <= 0){//满了 不能生产
System.out.println("满了 不能生产");
consume.await();//该线程进入product阻塞队列排队
}
count--;
System.out.println(Thread.currentThread().getName()+"生产者消费,目前总共有"+count+"个产品");
product.signalAll();//唤醒consume队列中的所有等待线程
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();//释放锁
}
}
}
}
public static void main(String[] args) {
ProductAndConsumer p = new ProductAndConsumer();
new Thread(p.new Productor(),"p1").start();
new Thread(p.new Productor(),"p2").start();
new Thread(p.new Consumer(),"c1").start();
new Thread(p.new Consumer(),"c2").start();
}
}
异步回调回滚问题
底层同步问题(乱序和屏障问题)
MySql
MySql隔离级别
- READ UNCOMMITTED 读取未提交内容 实际几乎不用 造成脏读、幻读、不可重复读
- READ COMMITTED 读取提交内容 造成不可重复读 常用 造成幻读、不可重复读
- REPEATABLE READ 可重复读 保证数据一致性 常用 造成幻读 可通过MVCC(多版本并发控制机制)解决
-
SERIALIZABLE 可串行化 强制事务排序 很少使用
image.png
MySQL复制原理
多台MySQL服务器之间的数据一致性
MySql聚簇和非聚簇索引的区别是什么?
索引是存在磁盘上的
索引的基本原理
目的:提高查询效率
聚集索引:指索引项的排序方式和表中数据记录排序方式一致的索引。它会根据聚集索引键的顺序来存储表中的数据,即对表 的数据按索引键的顺序进行排序,然后重新存储到磁盘上。因为数据在物理存放时只能有一种排列方式,所以一个表只能有一个聚集索引。比如字典中,用‘拼音’查汉字,就是聚集索引。因为正文中字都是按照拼音排序的。而用‘偏旁部首’查汉字,就是非聚集索引,因为正文中的字并不是按照偏旁部首排序的,我们通过检字表得到正文中的字在索引中的映射,然后通过映射找到所需要的字。
- 聚簇索引与非聚簇索引:【主键和数据】共存的索引被称之为【聚簇索引】,其他的,比如我们使用【姓名列+主键】建立的索引,可以称为【非聚簇索引】,或者【辅助索引】,或者【二级索引】,同时聚簇索引只有在innodb引擎中才存在,而在myIsam中是不存在的
-
B树和B+树的区别
image.png
image.png
MySQL索引结构有哪些?各自的优劣势是什么?
MySQL的锁的类型有哪些?
属性分类:共享锁(S锁 读锁)、排他锁(X锁 写锁)
- 共享锁(S锁 读锁):可以被多个事务共享,多个事务可以同时拥有读锁,但只能有一个事务修改数据
-
排他锁(X锁 写锁):只能被一个事务拥有,其他事务不能读取和写,除非之前的提交
-
锁的粒度分类:行级锁、表级锁、页级锁、记录锁、间隙锁、临键锁
行锁:行锁是加在索引上的,只有查询的条件是有索引的,才会行锁,不然就是表锁
间隙锁:对某一范围的数据上锁
临键锁:记录锁和间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间,临键锁的主要目的,也是为了避免幻读
MDL锁:元数据锁,开启任意一个事务之后,别的事务就不能使用DDL语句更改表的数据结构
死锁:事务互相等待对方持有的锁释放时,就会产生死锁。InnoDB使用的是行级锁,在某种情况下会产生死锁,如果发现死锁,就会自动回滚一个事务。
MYSQL避免死锁的方式:
image.png
乐观锁与悲观锁 - 乐观锁:严格意义上没有正在上锁,只是通过一些字段方法来模拟锁(例如version版本叠加),修改数据后version+1,其他数据就查询不到之间版本的数据,自然而然就不能进行对该记录的操作了
- 悲观锁:需要真正上锁的,例如之前的所有锁
mysql为什么需要主从复制?
Mysql的慢查询处理
索引的设计原则
为什么要有MYSQL索引
哈希索引的弊端:
1.哈希冲突会造成数据散列不一致,会产生大量的线性查询,比较浪费时间、空间
2.不支持范围查询,当进行范围查询的时候,必须要挨个遍历
3.对于内存的空间要求比较高
优点:
1.如果是等值查询 很快
memory存储引擎用的就是hash索引
innodb支持自适应哈希
注:建立索引时,key用vchar比int更好(保证key实际占用的空间越小越好,能够存储更多的数据,降低索引B+树的深度,提升效率)
myisam中叶子节点存储的是key和对应得数据存储地址,而innodb的叶子节点存储的是key和对应的数据。
知识点:
1.mysql表中有几个索引?
至少一个
2.回表
3.索引覆盖
4.最左匹配
组合索引时,你的查询语句会使用该索引的条件是你的查询条件(where后面)顺序和索引的顺序一致,mysql内部有优化器,会调整对应得顺序
5.索引下推
Sping
spring、springmvc、springboot区别?
Spring优势
@Autowired和@Resource的区别?
@Autowired默认按照byType注入匹配,优先根据接口类型匹配,如果存在多个实现类 ,按照名称匹配ByName
// 报错,byName 和 byType 都无法匹配到 bean
@Autowired
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Autowired
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean
// smsServiceImpl1 就是我们上面所说的名称
@Autowired
@Qualifier(value = "smsServiceImpl1")
private SmsService smsService;
@Resource属于JDK提供的注解,默认注入方式为byName。如果无法通过名称匹配的话,注入方式会变成byType。
// 报错,byName 和 byType 都无法匹配到 bean
@Resource
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Resource
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean(比较推荐这种方式)
@Resource(name = "smsServiceImpl1")
private SmsService smsService
Bean的生命周期
spring的事务传播机制?
springboot自动装配原理?
常量折叠
String s1 = "abc";
String s2 = "efg";
String s3 = "abc"+"efg";
String s4 = s1 + s2;
String s5 = "abcefg";
System.out.println(s3 == s4);//false
System.out.println(s3 == s5);//true
System.out.println(s4 == s5);//false
对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池。并且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化。对于 String str3 = "str" + "ing"; 编译器会给你优化成 String str3 = "string";
并不是所有的常量都会进行折叠,只有编译器在程序编译期就可以确定值的常量才可以:基本数据类型( byte、boolean、short、char、int、float、long、double)以及字符串常量。final 修饰的基本数据类型和字符串变量字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算(加减乘除)、基本数据类型的位运算(<<、>>、>>> )。被final修饰的也会视为常量,也会进行常量折叠
final String str1 = "str";
final String str2 = "ing";
// 下面两个表达式其实是等价的
String c = "str" + "ing";// 常量池中的对象
String d = str1 + str2; // 常量池中的对象
System.out.println(c == d);// true
try...catch...finally
try块:用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
catch块:用于处理 try 捕获到的异常。
finally块:无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。
注意:不要在finally语句块中使用return,如果try和finally中都有return时,try中的return会被忽略
finally中的代码不一定会执行,在finally之前,虚拟机被终止运行的话(System.exit(1))、程序所在的线程死亡、关闭CPU,finally中的代码块就不会被执行
在需要我们手动关闭的资源时,优先使用try...with...resource
try (Scanner scanner = new Scanner(new File("test.txt"))) {
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
}