JAVA面试知识点整理

作者: chuan_bai | 来源:发表于2019-09-29 09:53 被阅读0次

    JAVA面向对象特性

    • 继承:子类继承父类(遗产的继承)
    • 封装:将对象封装成一个高度自治和相对封闭的个体
    • 抽象:把现实生活中的对象抽象为类
    • 多态:通过父类的引用指向子类或者具体实现类的实例对象(在运行的时候决定)

    事务的四大特性

    • 原子性:事务不可分割,要么都成功要么都失败
    • 一致性:要么都成功,要么都失败,后面失败了,需要对前面的操作进行回滚
    • 持久性:修改数据后,在数据库中生效是永久的:
    • 隔离性:一个事务开始后,不能被其他事务干扰,举例:对于A对B进行转账,A没把这个交易完成的时候,B是不知道A要给他转钱。

    数据库隔离级别

    • 读未提交:
      事务中发生了修改,即使没有提交,其它事务也是可见的
      举例:一个数A原来50修改为100,但是我还没有提交修改,另一个事务看到这个修改,而这个时候原事务发生了回滚,这时候A还是50,但是另一个事务看到的A是100

    • 读已提交:
      对于一个事务从开始直到提交之前,所做的任何修改是其它事务不可见的
      举例:对于一个数A原来是50,然后提交修改成100,这个时候另一个事务在A提交修改之前,读取到了A是50,刚读取完,A就被修改成100了,这个时候另一个事务再进行读取发现A就突然变成100了

    • 可重复读:
      对于一个记录读取多次的记录是相同的,
      举例:就是对于一个数A读取的话一直是A,前后两次读取到的A是一致的

    • 串行化:
      在并发情况下,和串行化的读取的结果是一致的,没有什么不同
      举例:不会发生脏读和幻读

    • Mysql 默认:可重复读

    • Oracle 默认:读已提交

    事务的传播特性

    • 保证同一个事务中
      propagion_required: 支持当前事务,如果不存在 就新建一个(默认)
      propagion_supports: 支持当前事务,如果不存在,就不使用事务
      propagion_mandatory: 支持当前事务,如果不存在,抛出异常

    • 保证没有在同一个事务中
      propagion_requires_new: 如果有事务存在,挂起当前事务,创建一个新的事务
      propagion_not_supported: 以非事务方式运行,如果有事务存在,挂起当前事务
      propagion_never: 以非事务方式运行,如果有事务存在,抛出异常
      propagion_nested: 如果当前事务存在,则嵌套事务执行

    计算机网络分了哪七层

    • 物理层
    • 数据链路层
    • 网络层
    • 传输层 (tcp,udp)
    • 会话层
    • 表示层
    • 应用层(http)

    数据库索引

    • 常规索引(INDEX)
    • 唯一索引(UNIQUE)
    • 主键索引(PRIMAY KEY)
    • 全文索引(FULLTEXT)

    数据库引擎

    • Innodb引擎,Innodb引擎提供了对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。
    • MyIASM引擎(原本Mysql的默认引擎),不提供事务的支持,也不支持行级锁和外键。
    • MEMORY引擎:所有的数据都在内存中,数据的处理速度快,但是安全性不高。

    JAVA中的集合

    Collection 是所有集合类的接口,List和Set都是继承自Collection接口,Collection 继承自Iterable接口(迭代器)

    1. List:有序(存储顺序和取出顺序一致),可重复
      • ArrayList
        • 底层数据结构是数组。
          线程不安全ArrayList的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍
      • LinkedList
        • 底层数据结构是链表。线程不安全
          底层实现是双向链表[双向链表方便实现往前遍历]
      • Vector
        • 底层数据结构是数组。线程安全,现在已少用,被ArrayList替代,原因有两个:
          Vector所有方法都是同步,有性能损失。
          Vector初始length是10 超过length时 以100%比率增长,相比于ArrayList更多消耗内存
    2. Set:无序,元素不可重复
      • HashSet集合
        • 底层数据结构是哈希表(是一个元素为链表的数组)
      • TreeSet集合
        • 底层数据结构是红黑树(是一个自平衡的二叉树)
        • 保证元素的排序方式
      • LinkedHashSet集合
        • 底层数据结构由哈希表和链表组成。
    3. Map:存储key-value形式的数据
      • hashMap
        • 数组+链表(散列表)+红黑树
      • LinkedHashMap
        • 遍历的是内部维护的双向链表,所以说初始容量对LinkedHashMap遍历是不受影响的
      • TreeMap
        • 实现了NavigableMap接口,而NavigableMap接口继承着SortedMap接口,致使我们的TreeMap是有序的
      • LinkedHashSet


        集合关系图

    LinkList 和 ArrayList的区别

    • ArrayList:底层是数组

      • 查询特定元素比较快,而插入,删除和修改比较慢(在内存中是连续的,每次做插入或删除都需要 移动内存
    • LinkList:底层是链表

      • 不要求内存是连续的,在当前元素存放下一个或上一个元素的地址,查询时头部开始,一个个找,所以查询效率低,插入时不需要移动内存,只需要改变引用地址,所以插入和删除的效率比较高

    HashMap和HashTable

    • HashMap:
      • 线程不安全的,效率较高,(多线程环境下,使用Hashmap进行put操作会引起死循环)
      • 允许null作为key
      • 是对map接口的实现
      • 初始容量为16,增长因子为0.75,扩容为2倍
      • hashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取模。
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    
    static int hash(int h) {
            // This function ensures that hashCodes that differ only by
            // constant multiples at each bit position have a bounded
            // number of collisions (approximately 8 at default load factor).
            h ^= (h >>> 20) ^ (h >>> 12);
            return h ^ (h >>> 7) ^ (h >>> 4);
        }
     
     static int indexFor(int h, int length) {
            return h & (length-1);
    
    • HashTable:
      • 线程安全,效率较低
      • 不允许null作为key
      • 实现的是map接口和Dictonary抽象类
      • 初始容量为11,增长因子为0.75,扩容时为容量翻倍+1
      • Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    

    想线程安全又想效率高,使用ConcurrentHashMap类,把一个大的map分为N个Segment(类似HashTable),每一段数据分别加锁,可以提供相同的线程安全,但效率提升n倍,默认为16倍

    HashMap

    • HashMap实现了Map接口,是链表和数组的数据结构,在进行put操作时通过内部定义算法(hash算法) 寻址找到数组的下标,放入到此数组元素中,若通过算法得到的该数组已经有了元素(Hash碰撞),将会把这个元素上的链表进行遍历,将新的数组插入到链表末尾(jdk1.8 之前是插入到链表头部)。
    • 在jdk1.6中,HashMap中有个内置Entry类,它实现了Map.Entry接口;而在jdk1.8中,这个Entry类不见了,变成了Node类,也实现了Map.Entry接口)
    • hashmap的增长因子默认为0.75,默认大小为16,相当于在16 * 0.75 (数组长度*增长因子)时会进行 扩容,新数组长度 为原始数组长度两倍大小
      为了减少在hash碰撞冲突过多下,链表的get效率过低的情况,jdk1.8中新引入了红黑树数据结构,当链表元素长度 >8时会重组链表结构,形成红黑树结构,当链表元素=6时退回链表(泊松分布)

    装箱和拆箱,为什么要使用拆装箱

    Java是一个面向对象的语言,而基本类型,不具备面向对象的特性,没有封装一写好用的方法(如最大值、最小值、缓存值等)

    • 装箱:把基本数据类型转换为对应包装类型,在编译时调用Integer.valueOf() 方法装箱
    • 拆箱:把包装类型转换为基本数据类型,在编译时调用intValue() 方法拆箱

    多线程实现方式

    1. 继承Thread类,重写run方法
    2. 实现runable接口,重写run方法

    启动方式
    Thread thread = new Thread(继承了 Thread的对象/实现了runnable的对象)
    thread.setName("设置线程名称")
    thread.start();

    线程池作用

    JAVA通过Executors提供四个静态方法创建四种线程池

    1. 限定线程个数,不会导致由于线程过多导致系统运行缓慢或崩溃
    2. 线程池不需要每次都去创建或销毁,节约了资源
    3. 线程池不需要每次都去创建,响应时间更快

    Jsp四大作用域

    • request:用户端请求,此请求包含来自GET/POST的请求
    • response:网页回传用户端的回应
    • pageContext:网页属性在这里管理
    • session:与请求有关的会话期

    SpringMVC

    1. 用户发送请求 被前端控制器DispatcherServlet捕获请求, 解析url
    2. 解析url找到 handler对象
    3. 根据获得的Handler,选择合适的HandlerAdapter,填充Handler入参(执行handler)
    4. 选择ViewResovler,通过ViewResovler渲染视图并返回

    String,StringBuffer,StringBuilder

    String:是不可变的字符串,因为底层使用了不可变的字符数组(用final修饰)
    StringBuffer和StringBuilder:是内容可变的字符串,底层是内容可变的字符数组
    StringBuffer:是线程安全的,但是效率较低
    StringBuilder:是线程不安全的,但是效率高

    对JDBC的理解

    java只定义接口,让厂商自己实现接口,我们只需要导入对应厂商开发的实现即可,然后以接口方式调用(普通开发者只要 下载驱动,然后 导入即可使用)

    触发器

    需要有触发条件,当条件满足以后做什么操作
    关键字是trigger

    1. 创建语法
      create trigger trigger_name after/before insert/update on 表名 for each row begin sql语句 end;
    2. 删除
      drop trigger trigger_name
    3. 查询
      show triggers;
      show trigger like “%abc%”

    数据库分表技术

    1. 垂直分表
      • 把不常用的大字段分离出去,通过外键关联
    2. 水平分表
      • 按时间分表
      • 按区间范围进行分表
    3. hash分表
      • 通过一个原始目标的ID或者名称通过一定的hash算法计算出数据库存储表的表名,然后访问相应的表

    依赖注入和控制反转

    IOC和DI其实是同一个概念的不同角度的描述(依赖注入是从程序的层面来描述的,控制反转是从容器的角度描述的)

    • 作用:如A类需要调用B类但是B类需要获取外部资源C类,如果没有控制反转那么我们就需要在A类中同时创建B类和C类,当有了IOC/DI容器后就不需要在A类中创建C类了,而是被动等待,等待容器获取C类的实例,然后反向注入到A类中
    • 控制反转:由spring来负责对象的生命周期和对象间的关系(相当于通过中介买房子,所有的房子都登记在中介处,而你问Spring这个中介你需要哪个房子,在你需要买房的时候,可以直接找中介,中介会给你推荐房子,而不是由自己直接去找房)
    • 依赖注入:组件之间的依赖关系由容器在运行时决定,动态的向某个对象提供它所需要的其他对象

    JAVA内存区域

    • 堆(线程公有)
      所有对象实例及数组都要在堆上分配内存
      分为新生代(1/3堆空间)、老年代(2/3 堆空间)、元空间(jdk1.8以前的永久代,是方法区的实现,之前放在java虚拟机中jdk1.8以后用的是本地内存)
      • 新生代:分为两部分伊甸区和幸存者区,所有的类都是在伊甸区被new出来的,幸存区有两个0区和1区当伊甸区空间满了垃圾回收器会对伊甸区进行Minor GC,将伊甸区中不再被其他对象所引用的对象进行销毁,然后将伊甸园区的剩余对象移到幸存0区,若幸存0区也满了,会再对该去进行Minor GC,然后移到1区
      • 老年代:新生代进行多次Minor GC仍然存活的对象会移动到老年区,若老年区也满了,会产生FullGC,进行老年区的内存清理。若老年区执行了Full GC之后依然无法进行对象保存,就会产生内存溢出异常
        新生代中的eden->from->to 每熬过一次Minor GC,年龄会加1,当它的年龄增加到一定程度(默认为15岁),就会晋升为老年代
        -Xmx和-Xms参数表示最大堆和最小堆
    • 方法区(线程公有)
      用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中
    • 程序计数器(线程私有)
      程序计数器是一块很小的内存空间,它是线程私有的,可以认作为当前线程的行号指示器。(用来存储指向下一条指令的地址,也即是即将要执行的指令代码)
    • 虚拟机栈(线程私有)
      栈的算法是先进后出的
      每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
    • 本地方法栈(线程私有)
      本地方法栈是与虚拟机栈发挥的作用十分相似,区别是虚拟机栈执行的是Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的native方法服务,可能底层调用的c或者c++,我们打开jdk安装目录可以看到也有很多用c编写的文件,可能就是native方法所调用的c代码。

    类加载机制

    • 全盘负责委托机制
      当一个ClassLoader加载一个类时,除非显示的使用另一个ClassLoader,该类所依赖和引用的类也由这个ClassLoader载入
    • 双亲委派机制
      指先委托父类加载器寻找目标类,在找不到的情况下在自己的路径中查找并载入目标类
      优势:
      • 沙箱安全机制:自己写的String.class类不会被加载,这样可以防止核心API库被随意篡改
      • 避免类的被重复加载:当父亲已经加载了该类时,子加载器就没必要再加载一次
    JVM调优工具
    • jps:查看jvm进程
    • jstat:
      • jstat -gc 28485 垃圾回收统计
      • jstat -class 28485
    • jinfo:查看内存信息
      • jinfo -flags 28485 (jvm运行时的参数)
    • jmap:实例个数即占用内存大小
      • jmap -histo 28485 查看运行时内存相关信息
      • jmap -heap 28485 查看堆的概况
    • jstack:死锁排查
    • jvisualvm JDK自带的可视化工具

    垃圾回收算法

    1. 引用计数法
      给对象中添加一个引用计数器,没当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0的对象就认为可以被回收。(这个方法实现简单,效率高,但是目前主流虚拟机没有选择这个算法管理内存,主要原因是很难解决对象之间相互循环引用的问题。)
    2. 可达性分析算法
      以“GC Roots”的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的话,则证明此对象时不可能用的
    3. finalize()方法最终判定对象是否存活
      即使在可达性分析算法中不可达的对象,需要经历再次标记过程才真正宣告一个对象死亡

    垃圾收集算法

    1. 标记清除算法
      • 效率问题(会遍历内存)
      • 空间问题(标记清除后会产生大量不连续的碎片)
    2. 复制算法
    3. 标记整理算法
    4. 分代收集算法
      • 新生代适合复制算法
      • 老年代适合标记清除算法和标记整理算法

    JVM调优指标

    • 停顿时间:垃圾收集器做垃圾回收中断应用执行的时间
    • 吞吐量: 垃圾收集的时间和总时间的占比:1/(1+n),吞吐量为1-1/(1+n)

    调优步骤

    • 打印GC日志
    • 分析日志得到关键性指标
    • 分析GC原因,调优JVM参数

    REDIS数据结构

    • String(字符串)
    • Hash(哈希)
    • List(列表)
    • set(集合)
    • zset(有序集合)

    REDIS单线程为什么这么快

    所有数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题

    REDIS单线程如何处理这么多的并发客户端连接

    Redis的IO多路复用,redis利用epoll来实现IO多路复用,将连接信息和事件放在队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器

    REDIS持久化

    • RDB(快照)

    在默认情况下,Redis将数据库快照保存在名字为dump.rdb的二进制文件中。可以对Redis进行设置,让它在N秒内数据至少有M个改动这一条件满足时快照备份,一下设置会让Redis在满足“60秒内有至少有1000个键被改动”这一条件时进行快照备份 #save 60 1000

    • AOF

    记录的是操作命令,每次有新命令追加到AOF文件时就执行一次fsync,就执行当Redis重启时,程序会重新执行AOF文件中的命令,可以通过修改配置文件来打开AOF功能 #appendonly yes

    • Redis4.0混合持久化

    本质也是一种AOF方式所以必须打开AOF配置,整合了RDB和AOF格式,对AOF文件进行重写
    开启混合持久化方式:#aof-use-rdb-preamble yes
    AOF手动重写命令:#bgrewriteafo
    AOF重写配置:#auto-aof-rewrite-percentage 100 #auto-of-rewrite-min-size 64mb

    REDIS缓存淘汰策略

    当Redis内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换。交换会让Redis的性能急剧下降,为了限制最大使用内存,Redis提供了配置参数maxmemory来限制内存超出期望大小

    • noeviction:不会继续服务写请求,读请求可以继续进行,这样可以保证不会丢失数据,但会让线上的内存不能持续进行,这是默认的淘汰策略
    • volatile lru 尝试淘汰设置了过期时间的key,最少使用的key优先被淘汰。没有设置过期时间的key不会被淘汰(一般情况下会使用LRU策略)
    • 其他见redis.conf中的配置

    相关文章

      网友评论

        本文标题:JAVA面试知识点整理

        本文链接:https://www.haomeiwen.com/subject/rwdguctx.html