基础概念:

    • JVM, Java Virtual Machine, 运行Java字节码的虚拟机,不同操作系统的JVM运行相同的字节码,得出的结果应该是相同的
    • JRE, Java Run Environment,Java运行时的环境,包含java虚拟机和java所需的核心类库,那么如果我想运行一个已经编译成class文件的java程序,只需要安装JRE就已经足够;解释器会将字节码翻译成特定系统上运行的机器码;通过不同系统上安装对应的虚拟机,java确保了相同代码在不同系统上运行能得到相同结果
    • JDK, Java Development Kit,包含JRE和编译器
      • Javac 和 Java 位于jdk 目录下的 bin 文件夹
      • 编写 java 文件,然后通过 javac 编译 java 文件得到 class 文件(也就是字节码)
    • 字节码:我们平时在idea上编写的都是java文件,当我们运行java的时候,jdk会先将java文件编译成class文件,让后再将class文件交由jvm去运行

    泛型

    • 泛型是将类型明确的这一步骤延迟到创建对象(泛型类/接口)或者调用方法(泛型方法)的时候才去明确的操作
    • 将泛型作为参数,在创建对象或者调用方法的时候传递,那么就明确下来的最终的类型是什么
    • 泛型使得在正式调用方法或者创建对象的时候能够把类型确定下来,使得代码运行更安全
    • 常见通配符
      • ?->
        • <? extends 类型> 指该类型为能使用类型的上限
        • <? super 类型> 指该类型为能使用类型的下限
      • T, type, 意为表示具体的一个java类型
      • K & V, key & value
      • E, element

    Question: 为什么在重写了 equals() 之后建议都要重写 hashcode()?

    • 原本当两个对象比较的时候,相同 hashcode 的对象也有可能是不相等的,即 equals() 返回 false
      • 因为 hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode。)- 《Head Fisrt Java》
    • 那么在重写了 equals() 之后,如果不重写 hashcode() 的话,根据新的 equals() 相同的两个对象,根据就得 hashcode() 也有可能等不到相同的 hashcode
    • 那就有必要重写 hashcode(),使得 hashcode() 能正确地处理对象

    Question: 为什么通过静态方法调用非静态成员是非法的

    • 静态方法是属于类的,那么在类被加载的时候,静态方法就可以通过类名直接访问
    • 非静态成员是属于对象的,只有在对象被创建出来之后,才可以通过对象的实例访问非静态成员
    • 那么通过静态方法访问非静态成员,此时对象还没有被创建出来,也就是说非静态成员还不存在,因此该访问非法
    • 静态成员非静态成员的区别

    Question: 为什么Java中只有值传递

    按值调用(call by value) 表示方法接收的是调用者提供的值,按引用调用(call by reference) 表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。

    • 当方法接受的是“按值调用”来传递的变量值时,该方法修改不了原变量值
    • Java中的是按值调用,当方法参数是对象时,传递的只是该对象引用地址的拷贝
    • 在方法中对参数的修改仅仅是对该拷贝的修改,并不会影响到原对象…?

    对于多态的解释?

    • 一个对象的编译类型和运行类型可以不一致
    • 编译类型在定义对象时就确定了,在之后不能改变
    • 而运行类型时可以改变的
    • 编译类型取决于定义时 = 号的左边,运行类型取决于定义时 = 号的右边
    • 可以调用父类中的所有成员,但是不可以调用子类的特有成员
    • 最终的实现效果取决于子类方法的定义

    Overload (重载) 与 Override (重写)
    引用:

    区别点 重载方法 重写方法
    发生范围 同一个类 子类
    参数列表 必须修改 一定不能修改
    返回类型 可修改 子类方法返回值类型应比父类方法返回值类型更小或相等
    异常 可修改 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
    访问修饰符 可修改 一定不能做更严格的限制(可以降低限制)
    严格的程度:private > default > protected > public
    发生阶段 编译期 运行期

    访问修饰符

    • default:默认值,如果不加任何修饰符,那么就默认为default,同一个包内可见
    • private:私有,类外不可见
      • 私有的成员属性和方法甚至不能被子类访问,所以私有方法同时也不能被重写
    • public:所有类可见
    • protected:同一个包内的类和所有子类可见

    main 方法:java 程序的入口,必须带 String[] 参数,返回值必须是 void,必须由 static 关键字修饰


    内存分配…?

    • 左栈右堆
    • 栈内存 -> 存储局部变量,定义在方法中的变量
    • 堆内存 -> 储存new出来的内容(实体、对象),数组在初始化的时候,会为储存空间添加默认值,不过普通数据再初始化的时候是要赋值;而new出来的东西都有一个地址值,使用完毕会由垃圾回收器在空闲时回收
    • 常量池:存放字符串常量和基本类型常量
    • 基础类型数据再栈内存的初始化:eg. int i = 3; int j = 3; 一开始编译器处理 int i = 3; 先在栈中创建一个变量为 i 的引用,然后查找有没有字面值为3的地址,没有的话则创建并将变量 i 指向该地址;处理 int j = 3 的时候一样,不过这时栈内存中已有字面值为3的地址,所以直接将变量 j 指向该地址
    • int i = 4; 这时将变量 i 重新赋值为 4,编译器会查找是否有字面值为 4 的地址,没有则创建并指向该地址,并不会影响到原地址 -> 存在栈中的数据可以共享
    • 但当堆内存中的对象被修改时,修改被保存在堆,所有指向该地址的引用都会被影响到
    • question:描述一下 String string1 = “hello”; & String string2 = new String(“hello”) 运行的区别
      • 首先这两个java语句都是会先在栈内存中创建一个String类对象的引用,语句1会直接在常量池中查找是否存在内容为 “hello” 的字符串,如果有的话就会直接引用该对象,没有的话就会创建之后再引用;而语句2会在堆内存中创建一个对象,将变量指向该地址,最后再去常量池中寻找是否存在 “hello” 字符串,有的话会直接引用,没有的话就会创建再引用
    • https://zhuanlan.zhihu.com/p/68794639
    • 常量池:https://www.cnblogs.com/syp172654682/p/8082625.html
    • https://blog.csdn.net/qq_41376740/article/details/80338158
    • 面试问题
      • 常量池的好处:常量池实现了对象的共享,避免了频繁的创建和销毁对象对系统的影响;节省内存空间,节省运行时间
      • 静态常量池:即*.class文件中的常量池,用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),我的理解是这一常量池是用来存放class文件的信息的,例如字段和方法名称等
      • 而我们平常说到的常量池则是指:运行时常量池
      • 除了String类之外,其他基础数据类型的封装类,例如Integer或者Character也都实现了常量池,这些封装类中包含了-128到127的缓存数据,一般都是命名为xxxCache的,那么当我们所赋值的时候会首先进行一个判断,如果所赋值是在这个范围之内的话,就会直接返回缓存中的数据
      • 栈内存和堆内存:一些基本类型的变量和对象的引用变量都在函数的栈内存中分配;当代码在定义一个变量时,java就会在栈中为这个变量分配内存空间,当该变量的作用域结束之后,java就会自动释放对应的内存空间;而堆内存用来存放由new创建的对象和数组, 不过即使该对象或者数组的作用域结束了之后,对应的内存空间也不会立刻被释放,而是在随后的一个不确定时间由垃圾回收器来处理(释放)

    基础知识大纲

    • 面向对象的三大特性:封装,继承,多态
    • JVM,JRE,JDK
      • JRE 包含 bin(其实就是jvm),lib(类库)
      • java工具,如javac,java,jconsole等
    • == 和 equals 之间的区别
      • == 如果比较的是基础数据类的话,那么比较的就是两者的变量值,但如果比较双方是对象的话,那么用于对比的就是两者的地址
      • 而equals方法本质上跟==是一样的,object类中的equals方法其实就是简单的==;不过如果派生类有重生equals方法的话,那么就说它们比较的重点发生了改变,例如字符串String类中除了看比较双方在==的条件下是否相等,还会去看两者字符串的内容是否一样,如果一样的话,还是会返回true值
    • final关键字
      • 修饰类:表示该类不可被继承
      • 修饰方法:表示该方法不可被子类覆盖,但可以被重载
      • 修饰变量:表示该变量一旦被赋值就不可以改变它的值
    • String,StringBuilder,StringBuffer
      • String对象一旦创建就不能被修改,每次操作都会产生新的String对象
      • StringBuilder和StringBuffer都是在原对象上进行操作,不过StringBuilder的性能更好
      • StringBuffer中的方法都是有synchronized关键字修饰的
    • 重载和重写之间的区别
      • 重写 - Override
        • 指子类对父类的允许访问的方法的实现过程进行重新编写,但是返回值和形参都不能改变
        • 重写方法不能抛出新的检查异常或者比被重写方法申明更宽泛的异常
      • https://www.runoob.com/java/java-override-overload.html
    • 使用泛型的好处
      • 在编译的时候,检查添加元素的类型,提高了安全性
      • 减少了类型转换的次数,提高效率
      • 可以在类声明时通过一个标识表示类中某个属性的类型
    • 接口和抽象类的区别
      • 接口中的所有方法都必须是抽象方法,抽象类中允许存在普通成员方法
      • 一个子类只可以继承一个抽象类,但是可以实现多个接口
      • 抽象类中可以存在各种各样的成员属性,而抽象类只能存在public static final类型的属性
    • List和Set的区别
      • 有序,元素按照进入的顺序进行保存,可重复,可保存多个Null对象,可以用iterator进行迭代,还可以使用get(int index)取出对应的元素
      • 无序,不可重复,只可以存在一个Null对象,只能用iterator取得所有元素
    • hashCode()和equals()
      • hashCode()方法会返回一个哈希值,也称作散列码,本质上是一个int整数,其作用是确定对象在哈希表中的索引位置。当对象被放入到HashSet中时,为确保HashSet中对象都是唯一的,系统会先调用hashCode()来查看在HashSet中是否已经有hashCode一样的对象。但即使两者的hashCode是一样的,也不能确保两者就是同一个对象,还要进一步调用equals来对比。那么通过调用对比两者的hashCode,就大大减少了调用equals()的次数。
    • ArrayList和LinkedList
      • ArrayList的底层本质是一个动态数组,当储存的数组长度超出原定范围的时候,系统创建一个新数组,并且将原数组中的内容和新添加的元素一并添加到新数组,而因为其本质是一个数组,所以支持通过下标读取其中的元素。
      • LinkedList则是基于链表实现的,其内容是分散存储在内存中,适合做数据的插入和删除,不适合做数据查询。因为数据的存储是分散的,当要访问某个特定的元素时,只能逐一遍历其元素。
    • 如何实现一个IOC容器
      • 配置文件的扫描路径,例如Spring Boot是将扫描路径配置为注解@SpringBootApplication所在的包
      • 在要实例化的类上标注注解,分别表示控制层,业务服务层,数据持久层
      • 递归包扫描获得.class文件
      • 遍历扫描得到的.class文件,进行反射获得对象实例,定义一个Map来储存这写对象
      • 遍历这个IOC容器,对其中有依赖其他类的对象进行递归注入
    • 什么是字节码?
      • 字节码就是java文件在编译之后得到的.class文件,这是java能够实现跨平台的关键。在不同的平台上,java虚拟机中的解释器会将字节码翻译成该系统可以运行的机器码,然后再运行。
    • java类加载器
      • java自带的有三个类加载器:BootstrapClassLoader、ExtClassLoader、AppClassLoader,其中AppClassLoader负责加载classpath中的类文件。
    • 双亲委派
    • 异常分类
      • Throwable -> Exception & Error
      • 其中Exception又分为RunTimeException和CheckedException,CheckedException发生在编译阶段,例如说赋值类型不对等,会导致编译不通过,而RunTimeException则是发生程序运行阶段,会导致当前线程执行失败。
    • 线程的生命周期?线程有多少种状态
      • 创建,就绪,运行,阻塞和死亡
      • 当一个线程对象被创建而且它的start()方法又被调用了之后,它就进入了就绪(Runnable)的状态,在获得了cpu的使用权就会开始运行;阻塞是因为某些原因放弃cpu的使用权,暂时停止运行;然后一直到线程执行完了或者因异常退出了run方法,线程的生命周期就算结束了
      • 阻塞:等待阻塞,运行的线程调用了wait方法,只能由其他线程的notify或者notifyAll方法来唤醒;同步阻塞,运行的线程在尝试获得对象的同步锁时,该同步锁被其他线程占用
    • sleep,wait,yield,join方法
      • 锁池:所有竞争同一个同步锁的线程都会被放到一个锁池里面,当目前使用同步锁的线程释放了同步锁之后,锁池中的线程就会去竞争同步锁的使用权
      • 等待池:所有调用了wait方法的线程都会存放到等待池中,在等待池中的线程不会去竞争同步锁的使用权,当等待池中的线程被notify或者notifyAll方法唤醒,它们就会进入到锁池去竞争锁
      • sleep和wait之间的区别
        • sleep是Thread类的静态本地方法,wait是Object类的方法
        • sleep不会释放lock,但wait会释放lock,同时进入到等待池中;所以如果一个正在使用锁的线程调用了sleep的话,意味着这个线程同时把锁也带入了冻结状态
        • sleep不需要被唤醒,wait需要被其他线程notify或者notifyAll
      • yield方法:指线程直接进入就绪状态
      • join方法:调用之后线程进入阻塞状态,例如线程B调用线程A的join方法,线程B进入阻塞状态直到线程A运行结束
    • 线程安全的理解
    • Thread & Runnable
    • 守护线程
    • ThreadLocal的原理和使用原理
      • 每一个Thread对象的内部都一个ThreadLocalMap的成员变量,这个Map存储的是ThreadLocal对象以及其对应的值。因为这个ThreadLocalMap是每个线程私有的,所以就不会互相影响,也就是说存储在其中的数据是线程安全的
    • ThreadLocal的内存泄露
    • 并发的三大特性:原子性,可见性,有序性
    • 为什么使用线程池?解释一下线程池参数?
      • 使用线程池能提到运行效率,有任务来了时可以直接运行,而不是先创建线程再执行,提高线程的利用率,减低资源消耗
      • 线程池参数:corePoolSize,maxinumPoolSize,keepAliveTime,workQueue,ThreadFactory,Handler
    • 线程池的工作流程
      • 是否已达到核心线程数?工作队列是否已满?是否已达到最大线程数?拒绝策略是什么?
    • 线程池阻塞队列的作用?为什么是先添加队列而不是直接创建最大线程?
      • 一般的队列会有一个缓冲长度,一旦队列中的任务个数超出长度就无法保留新任务了;而缓冲队列让任务进入阻塞就可以有效地保存任务
      • 创建新的线程需要获得全局锁,这样的话会导致其他原有的线程阻塞,影响了整体的效率
    • 线程池中线程复用的原理
      • 普通的线程是将业务逻辑写入到重写的run方法中,然后再通过start方法启动;而线程池的做法是将线程和方法解耦,线程中并没有业务逻辑,而是让所有的线程都去执行一个”循环任务”,不断地去检查队列中是否有任务要执行,如果有就直接调用任务中的run方法执行

    • Spring是什么?
      • 轻量级开源的J2EE框架,它的核心思想是通过工厂模式来管理所有在开发过程中所用到的 bean 对象。我们只需要将用到的工具通过注解或者在 xml 中注册到 IoC 容器中即可,在用到的时候再从容器中调用即可。
    • 对Aop的理解
      • 系统常常有着很多不同的板块,除了实现核心功能的板块之外,有些板块会负责日志、事务管理等这种辅助类型的功能;这些功能通常都不是独立运行的,而是要在核心功能运行的时候同时调用,所以它们被称为横切关注点,因为它们会跨越多个板块。这就需要我们在板块中合适的地方将这些功能切入进去,所以被称为面对切面编程
      • 弥补了OOP的代码重复问题,将不同类不同方法中重复的部分抽取成切面,通过自动注入的方式执行
    • 对IoC容器的理解
      • IoC容器本质上是一个Map,里面存放着各种的对象实例;项目启动的时候系统会扫描文件夹下的包,读取里面的bean节点,通过反射创建实例放入到Map中去。而这整个过程对象的创建是由IoC来完成的,不想平时那样由程序员定义创建对象的代码,这里的控制权从程序员过渡到了IoC容器手上,所以这个过程也被称作是控制饭庄
    • BeanFactory和ApplicationContext之间的区别
    • Spring Bean的生命周期
    • Spring框架都用到了什么设计模式
    • Spring的事务传播机制
      • 当方法A调用方法B时,方法B上的事务传播propogation的值
      • REQUIRED:方法A有事务,则加入当前事务,如果当前没有事务,则自己新建一个事务
      • SUPPORTS:方法A有事务,则加入,无事务则以非事务的方法执行
      • REQURES_NEW:自己创建一个新事务,如果方法A有事务,则挂起该事务
      • NOT_SUPPORTED:以非事务方式执行,如果方法A有事务,则挂起该事务
      • NEVER:不使用事务,如果方法A有事务,则抛出异常
      • NESTED:如果方法A有事务,则嵌套在其中执行,否则自己创建事务执行
      • @Transactional所在依赖:org.springframework.spring-tx

    • 索引的基本原理
      • 索引是用于来快速地寻找某个特定值,其原理是将无序的数据按照一定的规律排序
      • 步骤:先将创建了索引的数据进行按倒序排列,排序完成之后将列中的内容拼上数据地址链;然后在查询的时候先根据索引找到记录,然后根据其中的地址链拿到具体数据
    • MySQL中锁的种类
      • 共享锁(读锁)和排他锁(写锁)
      • 行级锁、表级锁、页级锁、记录锁、间隙锁、临建锁
      • 意向共享锁、意向排他锁
    • 怎么处理慢查询
      • 处理慢查询首先要搞清楚查询执行缓慢的原因,一般的原因有可能是查询的时候加载了多余的列或者行最后也没有排上用场,或者是索引没有命中。
      • 如果是加载了多余的数据的话,那么就是通过重写sql语句来实现更高效的查询
      • 如果是索引没有命中的话,那么就是修改语句或者修改索引,来提高索引的命中率
      • 那么如果以上两点都没有太大的提升空间的话,就有可能是数据库存量太大了,解决方案是横向或者纵向分表,减少表中的数据量
    • ACID是靠什么保证的
      • 原子性:undo log;一致性;隔离性:MVCC;持久性:redo log