Java面经记录

  1. 面经
    1. 1. Java基础知识
      1. 1.1. 为什么说Java是编译和解释的语言
      2. 1.2. 静态方法中为什么不能调用非静态方法
      3. 1.3. 重载和重写
      4. 1.4. 可变长参数public void test(String …args)
      5. 1.5. 对象实体和对象引用
      6. 1.6. 对象默认有无参构造,但创建有参构造后要手动创建无参构造。
      7. 1.7. 多态
      8. 1.8. 接口和抽象类的区别
      9. 1.9. 深拷贝和浅拷贝
      10. 1.10. Object 的常用方法
      11. 1.11. String StringBuffer StringBuilder 的区别
      12. 1.12. String拼接、赋值和intern方法
      13. 1.13. 字符串常量池
    2. 2. 集合
      1. 2.1. List
      2. 2.2. Set
        1. 2.2.1. 类别:
        2. 2.2.2. Set排序接口
        3. 2.2.3. 无序性和不可重复性
      3. 2.3. Queue
        1. 2.3.1. 类型
        2. 2.3.2. Queue和Deque的区别
        3. 2.3.3. ArrayDeque和LinkedList的区别
        4. 2.3.4. PriorityQueue
        5. 2.3.5. BlockingQueue(了解)
      4. 2.4. Map
        1. 2.4.1. 类别
        2. 2.4.2. HashMap:
        3. 2.4.3. HashTable(接近淘汰):
        4. 2.4.4. HashMap和HashSet的区别
        5. 2.4.5. TreeMap
      5. 2.5. Collections工具类方法
      6. 2.6. 集合的建议
    3. 3. 常用数据结构源码解析
      1. 3.1. HashMap源码
      2. 3.2. ArrayList
      3. 3.3. LinkedList
      4. 3.4. ConcurrentHashMap
        1. 3.4.1. 实现线程安全的方法:
        2. 3.4.2. 不允许存放null和nullkey
        3. 3.4.3. 不能保证复合操作的原子性
    4. 4. 并发编程
      1. 4.1. synchronized关键字的底层原理是什么
      2. 4.2. CAS的理解和底层实现原理
        1. 4.2.1. 线程操作AtomicInterger基本流程:
        2. 4.2.2. CAS
      3. 4.3. JDK中的AQS的实现原理
      4. 4.4. 线程池的底层工作原理
        1. 4.4.1. 线程池:
        2. 4.4.2. 线程池的核心配置参数
        3. 4.4.3. 如果在线程池中使用无界阻塞队列会发生什么问题
        4. 4.4.4. 线程池队列满了之后,会发生什么
        5. 4.4.5. 如果机器突然宕机,线程池的阻塞队列的任务怎么办
      5. 4.5. ThreadLocal
        1. 4.5.1. 内存泄漏问题
      6. 4.6. Future类
    5. 5. Java IO
      1. 5.1. InputStream
        1. 5.1.1. 常用方法
        2. 5.1.2. FileInputStream
        3. 5.1.3. DataInputStream
        4. 5.1.4. ObjectInputStream
      2. 5.2. OutPutStream
        1. 5.2.1. 常用方法
        2. 5.2.2. FileOutputStream
        3. 5.2.3. DataOutputStream
        4. 5.2.4. ObjectInputStream
      3. 5.3. Reader
        1. 5.3.1. 常用方法
        2. 5.3.2. InputStreamReader FileReader
      4. 5.4. Writer
        1. 5.4.1. 常用方法
        2. 5.4.2. OutputStreamWriter FileWriter
      5. 5.5. 字符缓冲流BufferedInputStream BufferedOutputStream
      6. 5.6. 打印流 PrintStream
      7. 5.7. 随机访问流 RandomAccessFile
    6. 6. 谈谈对Java内存模型的理解
      1. 6.1. 可见性、原子性、有序性
      2. 6.2. 从底层角度聊volatile关键字原理
      3. 6.3. 指令重排和happens-before原则
      4. 6.4. volatile底层如何基于内存屏障保证可见性和有序性
    7. 7. Spring
      1. 7.1. Spring的IOC
      2. 7.2. Spring的AOP
      3. 7.3. 了解过cglib动态代理吗,他和jdk动态代理的区别是什么
      4. 7.4. spring事务的实现原理是什么,事务传播机制是什么
      5. 7.5. Springboot 的核心架构
      6. 7.6. Spring 核心源码
      7. 7.7. Spring中的设计模式
      8. 7.8. SpringMVC架构
      9. 7.9. SpringCloud核心架构
    8. 8. JVM
      1. 8.1. JVM中有哪几块内存区域,Java8之后对内存分代做了什么改进
      2. 8.2. JVM如何运行起来的,如何创建各种对象
      3. 8.3. JVM什么时候会触发垃圾回收
      4. 8.4. 什么时候对象会转移到老年代
      5. 8.5. 常用的垃圾回收器,老年代如何回收
      6. 8.6. 生产环境如何设置jvm参数的,如何检查jvm的运行情况
      7. 8.7. JVM GC优化
      8. 8.8. 发生OOM之后,应该如何排查和处理线上系统的OOM问题
    9. 9. 网络
      1. 9.1. TCP/IP的四层模型和七层网络模型
      2. 9.2. 浏览器访问baidu.com会发生什么
      3. 9.3. TCP三次握手和四次挥手的流程,为什么不是五次或者两次?
      4. 9.4. 说一下http长连接的原理
      5. 9.5. https http+ssl/tsl
    10. 10. MySQL
      1. 10.1. 引擎:mysiam innodb
      2. 10.2. Mysql索引原理和数据结构。
      3. 10.3. 索引的使用规则
      4. 10.4. 事务的几个特点
      5. 10.5. 隔离级别
      6. 10.6. 数据库锁
      7. 10.7. MySQL调优的常用手段
      8. 10.8. E-R图
    11. 11. socket
    12. 12. 进程通信和线程切换
      1. 12.1. 进程通信
      2. 12.2. 线程如何切换
    13. 13. nio,bio,aio都是什么,有什么区别。nio的原理是什么
      1. 13.1. bio通信原理
      2. 13.2. nio通信原理
      3. 13.3. aio
      4. 13.4. 同步阻塞、同步非阻塞、异步非阻塞
      5. 13.5. BIO NIO AIO demo代码
    14. 14. 线上服务器问题
      1. 14.1. 线上CPU占用100%,排查步骤:
      2. 14.2. 如果线上进程kill不掉怎么办
      3. 14.3. 磁盘马上占满了怎么办
    15. 15. Java语言特性
      1. 15.1. 参数传递
      2. 15.2. 序列化
      3. 15.3. 泛型和通配符
      4. 15.4. 反射
        1. 15.4.1.1. 基本操作
    16. 15.5. 代理模式
      1. 15.5.1.1. 1.静态代理:
      2. 15.5.1.2. 2.动态代理
  2. 15.6. BigDecimal常见方法
  3. 15.7. Unsafe类
  • 16. 算法
    1. 16.1. 字符串算法
      1. 16.1.1. 字符串最长匹配串(KMP)
      2. 16.1.2. 替换字符
      3. 16.1.3. 最长前缀匹配
      4. 16.1.4. 构建回文串
      5. 16.1.5. 验证回文串
      6. 16.1.6. 最长回文子序列
      7. 16.1.7. 括号的匹配深度
      8. 16.1.8. 字符串转换为整数
    2. 16.2. 链表算法
      1. 16.2.1. 两数相加
      2. 16.2.2. 反转链表
      3. 16.2.3. 链表中倒数第K个数
      4. 16.2.4. 合并两个排序的链表
    3. 16.3. 部分常见题目
      1. 16.3.1. 斐波那契数列
      2. 16.3.2. 跳台阶:也是斐波那契数列
      3. 16.3.3. 变态跳台阶:一个可以跳n个
      4. 16.3.4. 二维数组查找
      5. 16.3.5. 调整数组顺序使奇数位于偶数前面
      6. 16.3.6. 两个栈实现队列
      7. 16.3.7. 栈的压入弹出序列
    4. 16.4. 排序算法
  • 面经

    1. Java基础知识

    1.1. 为什么说Java是编译和解释的语言

    Java需要把代码编译成.class的字节码文件,然后再解释成机器码。

    1.2. 静态方法中为什么不能调用非静态方法

    因为静态方法在类创建的时候生成,非静态方法在实例化对象之后才生成。

    1.3. 重载和重写

    重载:同方法名,不同参数

    重写:子类重写父类的方法,方法名参数相同,构造方法无法重写

    1.4. 可变长参数public void test(String …args)

    可以接收不同长度的参数,重载的时候优先匹配固定长度参数的方法。

    1.5. 对象实体和对象引用

    new 来创建一个对象实体,一个对象实体可以有多个对象引用。一个对象引用可以指向一个对象实体。

    对象实体存在堆内存中(类和对象都存放在堆内存中)

    对象引用存放在栈内存中

    1.6. 对象默认有无参构造,但创建有参构造后要手动创建无参构造。

    1.7. 多态

    子类在调用父类的方法的时候,只有在运行的时候,才知道调用的是哪个方法(父类的还是子类的),这个方法具有多态性

    1.8. 接口和抽象类的区别

    • 相同点:
      • 都不能被实例化
      • 都可以包含抽象方法
      • 都可以有默认实现的方法,接口中可以定义default方法来实现
    • 不同点
      • 接口主要是对对象的约束,约束对象的行为(实现接口的方法)
      • 抽象类主要是代码的复用性的规定
      • 一个类只可以继承一个类,但可以实现多个接口
      • 接口中 的成员变量只能是public static final 类型,有初值,不可变
      • 抽象类中的成员变量默认为default,可在子类中重新定义和赋值

    1.9. 深拷贝和浅拷贝

    浅拷贝:在对上创建一个新对象,如果被拷贝的是引用类型,则会直接复制它引用的地址

    深拷贝:复制整个对象,包含内部对象

    1.10. Object 的常用方法

    /**
     * native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
     */
    public final native Class<?> getClass()
    /**
     * native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。
     */
    public native int hashCode()
    /**
     * 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。
     */
    public boolean equals(Object obj)
    /**
     * native 方法,用于创建并返回当前对象的一份拷贝。
     */
    protected native Object clone() throws CloneNotSupportedException
    /**
     * 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
     */
    public String toString()
    /**
     * native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
     */
    public final native void notify()
    /**
     * native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
     */
    public final native void notifyAll()
    /**
     * native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。
     */
    public final native void wait(long timeout) throws InterruptedException
    /**
     * 多了 nanos 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒。。
     */
    public final void wait(long timeout, int nanos) throws InterruptedException
    /**
     * 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
     */
    public final void wait() throws InterruptedException
    /**
     * 实例被垃圾回收器回收的时候触发的操作
     */
    protected void finalize() throws Throwable { }
    

    hashcode 和 equals 都是判断对象是否相同的方法

    hashcode 可能会碰撞,equals 是比较地址但效率较低

    重写equals也应当重写hashcode方法。

    1.11. String StringBuffer StringBuilder 的区别

    • String 对象不可变,String a = “test”; a = “test1” 。更改a的值的时候,是创建了一个新的String对象,将地址赋给a。是线程安全的

    • StringBuffer 和 StringBuilder 更改值,不用新建对象。

    • StringBuffer 使用同步锁保证线程安全

    • StringBuilder 是线程不安全的,效率比StringBUffer快10%-15%

    1.12. String拼接、赋值和intern方法

    • 在Java9之前,String 用加号连接的方法是使用StringBuilder.append().toString()方法构建的。在之后更新了方法,可以放心使用+连接,性能也不错
    • String s = new String(“abc”); 这段代码可能会创建 1-2 个字符串对象。 先在字符串常量池创建一个字符串对象abc(如果已存在则不需要再创建),然后复制一份到堆内存中。
    • String intern 方法:将引用保存到字符串常量池中(如果已有则不再保存),并且返回这个对象

    1.13. 字符串常量池

    • 字符串常量池位于堆内存中,与堆中的对象实例是分开的。
    • 字符串常量池中的字符串是不可变的,一旦创建就不能修改。
    • 字符串常量池中的字符串可以通过调用 intern() 方法进行显式的添加。该方法将返回常量池中字符串的引用,如果常量池中已存在相同内容的字符串,则返回已存在的引用。
    • 字符串常量池的位置发生了变化,在 Java 7 及之前的版本中,字符串常量池位于永久代(PermGen),而在 Java 8 及以后的版本中,它被转移到了堆内存中。

    2. 集合

    2.1. List

    顺序存储

    • ArrayList

      • ArrayList和Array的区别
    ArrayList Array
    长度 可变 固定
    用泛型 不可
    存放类型 仅对象 对象和基本类型
    内置方法
    创建指定大小 无需 必须
    存放null 不可
    ArrayList和LinkedList操作的时间复杂度。
    
    ArrayList LinkedList
    头插 O(n) O(1)
    尾插 O(1)或O(n) (扩容) O(1)
    中间插入 O(n)后面的要向后移动 O(n)找到插入的位置
    头删 O(n) O(1)
    尾删 O(1) O(1)
    中间删除 O(n) O(n)
    • Vector: VectorList 的古老实现类,底层使用Object[] 存储,线程安全。

    • LinkedList

    2.2. Set

    2.2.1. 类别:

    • HashSet:无序,唯一,使用HashMap实现
    • LinkedHashSet:使用LinkedHashMap实现
    • TreeSet:有序,唯一 也叫:红黑树 (自平衡的排序二叉树)

    2.2.2. Set排序接口

    • Comparable:实现方法obj.compareTo(obj1);
    • Comparator:实现方法compare(obj1,obj2);

    2.2.3. 无序性和不可重复性

    无序性:存储元素的顺序是按照key散列之后存储在对应位置,不是按顺序一个一个存储

    不可重复性:equals不能相同,同时需要重写hashcode和equals方法

    2.3. Queue

    2.3.1. 类型

    • PriorityQueue:数组实现的二叉堆
    • ArrayQueue:数组和双指针实现

    2.3.2. Queue和Deque的区别

    Queue是单端队列,Deque是双端队列

    Queue实现了Collection接口

    Deque扩展了Queue接口,增加了队首删除和加入的方法,根据错误处理方式有两种不同操作方法

    Deque 接口 抛出异常 返回特殊值
    插入队首 addFirst(E e) offerFirst(E e)
    插入队尾 addLast(E e) offerLast(E e)
    删除队首 removeFirst() pollFirst()
    删除队尾 removeLast() pollLast()
    查询队首元素 getFirst() peekFirst()
    查询队尾元素 getLast() peekLast()

    事实上,Deque 还提供有 push()pop() 等其他方法来模拟栈

    2.3.3. ArrayDeque和LinkedList的区别

    ArrayQueue LinkedList
    底层 数组+双指针 链表
    可存储null
    需要扩容 可能需要,但插入均摊性能高 否,但插入元素需要申请内存,均摊性能稍低

    ArrayQueue的性能比LinkedList好,并且可以实现栈。

    2.3.4. PriorityQueue

    PriorityQueue和Queue的区别是:PriorityQueue是根据优先级出队的

    细节TODO

    2.3.5. BlockingQueue(了解)

    没有元素会阻塞,直到有元素。如果队列已满,会阻塞插入操作。

    用来实现生产者消费者等部分。

    2.4. Map

    2.4.1. 类别

    • HashMap:不保证FIFO,不是先来先存储,通过散列,选择一个位置存放
    • LinkedHashMap:保证FIFO
    • HashTable:
    • TreeMap:

    2.4.2. HashMap:

    [hashMap](# HashMap源码)

    2.4.3. HashTable(接近淘汰):

    线程安全,效率稍低,不支持存储null或nullkey,容量默认为11,之后扩容为2n+1

    2.4.4. HashMap和HashSet的区别

    HashSet是HashMap实现的

    HashSet 的源码非常非常少,除了 clone()writeObject()readObject()HashSet 自己实现之外,其他方法都是直接调用 HashMap 中的方法。

    HashMap HashSet
    实现了 Map 接口 实现 Set 接口
    存储键值对 仅存储对象
    调用 put()向 map 中添加元素 调用 add()方法向 Set 中添加元素
    HashMap 使用键(Key)计算 hashcode HashSet 使用成员对象来计算 hashcode 值,对于两个对象来说 hashcode 可能相同,所以equals()方法用来判断对象的相等性
    • HashSet的去重:调用HashMap的add方法,根据返回值判断是否重复
    • HashMap的判断重复:计算hash值->相同equals->再相同则重复

    2.4.5. TreeMap

    实现 NavigableMap 接口让 TreeMap 有了对集合内元素的搜索的能力。

    实现SortedMap接口让 TreeMap 有了对集合中的元素根据键排序的能力。默认是按 key 的升序排序

    想要重写排序

    TreeMap<Person, String> treeMap = new TreeMap<>(new Comparator<Person>() {
                @Override
                public int compare(Person person1, Person person2) {
                    int num = person1.getAge() - person2.getAge();
                    return Integer.compare(num, 0);
                }
    });
    

    比HashMap多了元素根据键排序和对集合内元素的搜索能力

    2.5. Collections工具类方法

    //排序
    void reverse(List list)//反转
    void shuffle(List list)//随机排序
    void sort(List list)//按自然排序的升序排序
    void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
    void swap(List list, int i , int j)//交换两个索引位置的元素
    void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面
    
    //查找 替换
    int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
    int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
    int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
    void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素
    int frequency(Collection c, Object o)//统计元素出现次数
    int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target)
    boolean replaceAll(List list, Object oldVal, Object newVal)//用新元素替换旧元素
    

    同步操作(不推荐,建议使用JUC)

    2.6. 集合的建议

    • 判空使用isEmpty()方法

    • 在使用 java.util.stream.Collectors 类的 toMap() 方法转为 Map 集合时,一定要注意当 value 为 null 时会抛 NPE 异常。

    • 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。Collection#removeIf()方法删除满足特定条件的元素

    • 可以利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 Listcontains() 进行遍历去重或者判断包含操作。

    • 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。

    • 使用工具类 Arrays.asList() 把数组转换成集合时,不能使用其修改集合相关的方法, 它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。

      • 推荐解决方法

        Integer [] myArray = { 1, 2, 3 };
        List myList = Arrays.stream(myArray).collect(Collectors.toList());
        //基本类型也可以实现转换(依赖boxed的装箱操作)
        int [] myArray2 = { 1, 2, 3 };
        List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
        
      • 最简单的解决办法

        Integer [] myArray = { 1, 2, 3 };
        List myList = Arrays.stream(myArray).collect(Collectors.toList());
        //基本类型也可以实现转换(依赖boxed的装箱操作)
        int [] myArray2 = { 1, 2, 3 };
        List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
        

    3. 常用数据结构源码解析

    3.1. HashMap源码

    • 底层是数组+链表+红黑树,非线程安全

      • 为什么非线程安全
        • 线程一进行完hash碰撞判断之后,时间片用尽,线程二完成了hash计算和插入操作,此时线程一直接插入,会覆盖线程二插入的值

        • 同时put导致size值不对

    • 可存储null的key和value

    • 初始大小16,扩容到当前的二倍(一定是2的幂次方,便于取余计算)

    • 获取Key的方法:

      • static final int hash(Object key) {
          int h;
          // key.hashCode():返回散列值也就是hashcode
          // ^:按位异或
          // >>>:无符号右移,忽略符号位,空位都以0补齐
          return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }
        
    • 元素添加:

      • 获取数组的index:key对hashmap的length取模(将key和length-1与,比%取模的性能好)
      • 如果这个index没有元素,直接添加
      • 如果有元素则比较key,如果key相同则覆盖。
      • 如果是hash碰撞,则判断是否是树节点,是就调用putTreeVal加入树节点,否则加入链表尾部
    • hash碰撞使用拉链法

      • 在hash碰撞的节点创建一个链表,把碰撞的值都放在链表中
      • 链表元素超过8个,将链表切换成红黑树
    • 扩容条件是存放的元素数量超过容量*负载因子

      • 负载因子loadFactor 是控制数组存放数据的疏密程度
        • loadFactor越趋近1,数组中存储的元素就越多,碰撞的元素在链表的长度就越多,查询性能下降
        • loadFactor越趋近0,hashMap扩容次数增加,rehash消耗性能
        • 官方给出的默认为0.75
      • resize就是扩容之后重新计算index和hash
        • 底层就是新开一个数组,将元素重新放入新数组

    3.2. ArrayList

    • ArrayList 继承于 AbstractList ,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。线程不安全。

      • List : 表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。
      • RandomAccess :表明它可以快速进行随机访问,get(index)
      • Cloneable :表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作。
      • Serializable : 表明它可以进行序列化操作,也就是可以将对象转换为字节流进行持久化存储或网络传输,非常方便。
    • 可以添加null值。

    • 底层是Object数组。

    • 时间复杂度:add() O(1) ; add(index,val) O(n) ;

    • 空间占用主要是末尾预留的空间。

    • 在添加大量元素的时候,可以提前使用ensureCapacity(N)方法预留空间,减少空间分配次数,节约性能。

    • 当添加元素大于容量的时候,触发扩容

      • 扩容:每次newSize= oldSize+oldSize/2 相当于1.5倍

    3.3. LinkedList

    • LinkedList 继承了 AbstractSequentialList ,而 AbstractSequentialList 又继承于 AbstractList
    • LinkedList 实现了List,Deque,Cloneable,Seralizable
      • Deque:表明它具有双端队列特性,便于两端插入和删除
    • 底层是双向链表
    • 时间复杂度:add() O(1); add(index,val) O(n)
    • 空间占用主要是除了data之外的索引等数据
    • 遍历常用for-each
    • 基本上不用LinkedList,都用ArrayList,性能一般更好一些

    3.4. ConcurrentHashMap

    3.4.1. 实现线程安全的方法:

    • JDK1.7的时候:使用Segment数组(锁的个数,默认16,不可扩容)+HashEntry数组+链表实现。使用分段锁,将数据分段保护。最多支持16个线程并发(默认)
    • JDK1.8的时候:使用(node数组+链表)/(红黑树)实现。主要使用CAS和synchronized操作,保留一些分段锁用来兼容旧版本。synchronized锁定链表节点或者红黑树首节点。因此只要hash不碰撞都可以进行并发操作。

    3.4.2. 不允许存放null和nullkey

    避免二义性,不同线程使用containsKey来判断是否存在元素,如果存储null就不知道到底是有还是没有值。

    3.4.3. 不能保证复合操作的原子性

    // 线程 A
    if (!map.containsKey(key)) {
    map.put(key, value);
    }
    // 线程 B
    if (!map.containsKey(key)) {
    map.put(key, anotherValue);
    }
    

    不能保证上面的代码正常执行

    concurrentHashMap提供了原子性的符合操作方法:putIfAbsent()computeIfAbsent()

    4. 并发编程

    4.1. synchronized关键字的底层原理是什么

    2.1.1synchronized是做什么的

    给线程加锁,加锁目标是一个类或一个对象。

    2.1.2实现原理

    加锁指令:monitorenter(加锁) monitorexit(释放锁)

    一个对象或类关联有一个monitor(计数器:正在使用的线程数,类似信号量

    4.2. CAS的理解和底层实现原理

    多个线程要访问同一个数据会出现并发安全问题。

    AtomicInterger并发包的原子类,使用CAS实现。

    4.2.1. 线程操作AtomicInterger基本流程:

    线程1想要修改值,会

    • 先读取旧值
    • 在修改前再次读取这个值
    • 如果没人修改,则使用CAS进行修改这个值。
    • 如果第二次读取的值和旧值不同,则CAS失败。

    4.2.2. CAS

    CAS:compare and set

    在硬件级别保是原子操作,同一时间只有一个线程可以执行CAS。

    ABA问题:线程1读取值A,线程2读取值A修改成B之后再修改成A,这样线程1不知道是不是被修改了。

    解决:加上时间戳(版本号)

    4.3. JDK中的AQS的实现原理

    ReentrantLock 类底层是AQS(Abstract Queue Synchronizer)

    可以使用这个类生成一个锁lock,可以进行lock.lock() lock.unlock()实现互斥。

    2.4.1 AQS会有一个等待队列,存储没有得到锁的线程,待锁释放后,按顺序为等待队列的线程提供锁

    image-20230731160209376

    2.4.2如果在线程1执行完毕,唤醒线程2的过程中,如果有线程3想要加锁

    • 非公平锁:ReentrantLock lock = new ReentrantLock();
      • 线程3可能会成功得到锁,达到插队。
    • 公平锁:ReentrantLock lock = new ReentrantLock(true);
      • 如果等待队列有线程,线程3会进入等待队列。

    4.4. 线程池的底层工作原理

    线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险(阿里巴巴Java规范)

    4.4.1. 线程池:

    提供一定量的线程,线程执行完成任务后,不销毁自己,等待下一次任务:

    避免重复创建和销毁线程,造成性能浪费

    创建线程池:

    ExecutorService threadPool = Executor.newFixedThreadPool(10)  //(corePoolSize = 10)
    threadPool.submit(new Callable() {
        public void run(){}
    });
    

    有新任务的时候

    • 如果线程池的线程数量小于容量,则直接创建一个新的线程执行任务。
    • 如果满了,则放在任务队列中。

    当线程完成自己的任务的时候,会去任务队列中获取任务,如果没有任务,会阻塞,不会销毁。

    4.4.2. 线程池的核心配置参数

    代表线程池的类是ThreadPoolExecutor

    return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())
    
    • corePoolSize:3
      • 一般最大线程数只有3
    • maximumPoolSize:200
      • 当任务队列满了,可以最多额外创建到200个,执行任务并从任务队列获取任务
    • keepAliveTime:60s
      • 任务队列空了60s后,额外线程自动会销毁掉
    • new ArrayBlockingQueue<Runnable>(200)
      • 任务队列

    如果额外线程也满了,会报异常。可以自定义RejectedExecutionHandler策略来应对这种情况:持久化被reject的任务,等负载低了再加载执行。

    4.4.3. 如果在线程池中使用无界阻塞队列会发生什么问题

    面试题1:如果使用无界阻塞队列调用远程服务,远程服务异常,会不会导致内存异常飙升

    调用超时,队列变得越来越大,内存会飙升,可能会导致OOM。

    4.4.4. 线程池队列满了之后,会发生什么

    • 如果给maximumPoolSize设置太大,可能会导致系统崩溃。因为线程会占用一定内存,也会增加cpu负载。
    • 如果给maximumPoolSize设置太小,可能会导致任务reject。

    4.4.5. 如果机器突然宕机,线程池的阻塞队列的任务怎么办

    都会丢失

    解决办法:在数据库对任务信息进行持久化。

    4.5. ThreadLocal

    主要解决的问题:让每个线程绑定自己的值,防止竞争

    最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。 ThrealLocal 类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。

    每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。

    4.5.1. 内存泄漏问题

    ThreadLocalMap 使用的key是对ThreadLocal的弱引用,value是强引用。垃圾回收的时候会回收弱引用对象,那么key可能会被回收,导致Map的key为null。如果我们不操作,value则无法被回收

    ThreadLocalMap提供的方法:set get remove 都会清楚key为null 的记录。用完ThreadLocal之后最好进行remove()

    4.6. Future类

    异步思想的典型运用。

    耗时的任务交给future类实现,先执行其他步骤,等到我们需要的时候再通过future类获取。

    // V 代表了Future执行的任务返回值的类型
    public interface Future<V> {
        // 取消任务执行
        // 成功取消返回 true,否则返回 false
        boolean cancel(boolean mayInterruptIfRunning);
        // 判断任务是否被取消
        boolean isCancelled();
        // 判断任务是否已经执行完成
        boolean isDone();
        // 获取任务执行结果
        V get() throws InterruptedException, ExecutionException;
        // 指定时间内没有返回计算结果就抛出 TimeOutException 异常
        V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutExceptio
    }
    

    5. Java IO

    InputStream/Reader:输入流基类,前者是字节输入流,后者是字符输入流

    OutputStream/Writer:输出流基类,前者是字节输出流,后者是字符输出流

    5.1. InputStream

    用于从源头(通常是文件)读取数据(字节信息)到内存中,java.io.InputStream抽象类是所有字节输入流的父类。

    5.1.1. 常用方法

    • read():返回输入流中下一个字节的数据。返回的值介于 0 到 255 之间。如果未读取任何字节,则代码返回 -1 ,表示文件结束。
    • read(byte b[ ]) : 从输入流中读取一些字节存储到数组 b 中。如果数组 b 的长度为零,则不读取。如果没有可用字节读取,返回 -1。如果有可用字节读取,则最多读取的字节数最多等于 b.length , 返回读取的字节数。这个方法等价于 read(b, 0, b.length)
    • read(byte b[], int off, int len):在read(byte b[ ]) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字节数)。
    • skip(long n):忽略输入流中的 n 个字节 ,返回实际忽略的字节数。
    • available():返回输入流中可以读取的字节数。
    • close():关闭输入流释放相关的系统资源。

    JDK1.9之后添加了:

    • readAllBytes():读取输入流中的所有字节,返回字节数组。(实用)
    • readNBytes(byte[] b, int off, int len):阻塞直到读取 len 个字节。
    • transferTo(OutputStream out):将所有字节从一个输入流传递到一个输出流。

    5.1.2. FileInputStream

    FileInputStream 是一个比较常用的字节输入流对象,可直接指定文件路径,可以直接读取单字节数据,也可以读取至字节数组中。

    try (InputStream fis = new FileInputStream("input.txt")) {
        System.out.println("Number of remaining bytes:"
                + fis.available());
        int content;
        long skip = fis.skip(2);
        System.out.println("The actual number of bytes skipped:" + skip);
        System.out.print("The content read from file:");
        while ((content = fis.read()) != -1) {
            System.out.print((char) content);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    一般我们是不会直接单独使用 FileInputStream ,通常会配合 BufferedInputStream(字节缓冲输入流,后文会讲到)来使用。

    // 新建一个 BufferedInputStream 对象
    BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("input.txt"));
    // 读取文件的内容并复制到 String 对象中
    String result = new String(bufferedInputStream.readAllBytes());
    System.out.println(result);
    

    5.1.3. DataInputStream

    DataInputStream 用于读取指定类型数据,不能单独使用,必须结合其它流,比如 FileInputStream

    FileInputStream fileInputStream = new FileInputStream("input.txt");
    //必须将fileInputStream作为构造参数才能使用
    DataInputStream dataInputStream = new DataInputStream(fileInputStream);
    //可以读取任意具体的类型数据
    dataInputStream.readBoolean();
    dataInputStream.readInt();
    dataInputStream.readUTF();
    

    5.1.4. ObjectInputStream

    ObjectInputStream 用于从输入流中读取 Java 对象(反序列化),ObjectOutputStream 用于将对象写入到输出流(序列化)。

    ObjectInputStream input = new ObjectInputStream(new FileInputStream("object.data"));
    MyClass object = (MyClass) input.readObject();
    input.close();
    

    另外,用于序列化和反序列化的类必须实现 Serializable 接口,对象中如果有属性不想被序列化,使用 transient 修饰。

    5.2. OutPutStream

    OutputStream用于将数据(字节信息)写入到目的地(通常是文件),java.io.OutputStream抽象类是所有字节输出流的父类

    5.2.1. 常用方法

    • write(int b):将特定字节写入输出流。
    • write(byte b[ ]) : 将数组b 写入到输出流,等价于 write(b, 0, b.length)
    • write(byte[] b, int off, int len) : 在write(byte b[ ]) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字节数)。
    • flush():刷新此输出流并强制写出所有缓冲的输出字节。
    • close():关闭输出流释放相关的系统资源。

    5.2.2. FileOutputStream

    FileOutputStream 是最常用的字节输出流对象,可直接指定文件路径,可以直接输出单字节数据,也可以输出指定的字节数组。

    try (FileOutputStream output = new FileOutputStream("output.txt")) {
        byte[] array = "JavaGuide".getBytes();
        output.write(array);
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    类似于 FileInputStreamFileOutputStream 通常也会配合 BufferedOutputStream

    FileOutputStream fileOutputStream = new FileOutputStream("output.txt");
    BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream)
    

    5.2.3. DataOutputStream

    DataOutputStream 用于写入指定类型数据,不能单独使用,必须结合其它流,比如 FileOutputStream

    // 输出流
    FileOutputStream fileOutputStream = new FileOutputStream("out.txt");
    DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
    // 输出任意数据类型
    dataOutputStream.writeBoolean(true);
    dataOutputStream.writeByte(1);
    

    5.2.4. ObjectInputStream

    ObjectInputStream 用于从输入流中读取 Java 对象(ObjectInputStream,反序列化),ObjectOutputStream将对象写入到输出流(ObjectOutputStream,序列化)

    ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("file.txt")
    Person person = new Person("Guide哥", "JavaGuide作者");
    output.writeObject(person);
    

    5.3. Reader

    Reader用于从源头(通常是文件)读取数据(字符信息)到内存中,java.io.Reader抽象类是所有字符输入流的父类

    Reader 用于读取文本, InputStream 用于读取原始字节。

    5.3.1. 常用方法

    • read() : 从输入流读取一个字符。
    • read(char[] cbuf) : 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中,等价于 read(cbuf, 0, cbuf.length)
    • read(char[] cbuf, int off, int len):在read(char[] cbuf) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字符数)。
    • skip(long n):忽略输入流中的 n 个字符 ,返回实际忽略的字符数。
    • close() : 关闭输入流并释放相关的系统资源。

    5.3.2. InputStreamReader FileReader

    InputStreamReader 是字节流转换为字符流的桥梁,其子类 FileReader 是基于该基础上的封装,可以直接操作字符文件。

    try (FileReader fileReader = new FileReader("input.txt");) {
        int content;
        long skip = fileReader.skip(3);
        System.out.println("The actual number of bytes skipped:" + skip);
        System.out.print("The content read from file:");
        while ((content = fileReader.read()) != -1) {
            System.out.print((char) content);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    5.4. Writer

    Writer用于将数据(字符信息)写入到目的地(通常是文件),java.io.Writer抽象类是所有字符输出流的父类。

    5.4.1. 常用方法

    • write(int c) : 写入单个字符。
    • write(char[] cbuf):写入字符数组 cbuf,等价于write(cbuf, 0, cbuf.length)
    • write(char[] cbuf, int off, int len):在write(char[] cbuf) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字符数)。
    • write(String str):写入字符串,等价于 write(str, 0, str.length())
    • write(String str, int off, int len):在write(String str) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字符数)。
    • append(CharSequence csq):将指定的字符序列附加到指定的 Writer 对象并返回该 Writer 对象。
    • append(char c):将指定的字符附加到指定的 Writer 对象并返回该 Writer 对象。
    • flush():刷新此输出流并强制写出所有缓冲的输出字符。
    • close():关闭输出流释放相关的系统资源。

    5.4.2. OutputStreamWriter FileWriter

    OutputStreamWriter 是字符流转换为字节流的桥梁,其子类 FileWriter 是基于该基础上的封装,可以直接将字符写入到文件。

    try (Writer output = new FileWriter("output.txt")) {
        output.write("你好,我是Guide。");
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    5.5. 字符缓冲流BufferedInputStream BufferedOutputStream

    IO 操作是很消耗性能的,缓冲流将数据加载至缓冲区,一次性读取/写入多个字节,从而避免频繁的 IO 操作,提高流的传输效率。

    默认大小8M,可显式定义

    5.6. 打印流 PrintStream

    System.out就是一个PrintStream对象,print调用了write方法

    PrintStream 属于字节打印流,与之对应的是 PrintWriter (字符打印流)。PrintStreamOutputStream 的子类,PrintWriterWriter 的子类。

    5.7. 随机访问流 RandomAccessFile

    可以访问到文件的任意字节。

    适用于断点重传。

    6. 谈谈对Java内存模型的理解

    public class HelloWorld {
        private int data;
        public void increment(){
            data++;
        }
    }
    HelloWorld helloWorld = new HelloWorld(); //对象存放在堆内存,包含对象中的实例变量
    //线程1
    new Thread(){
        public void run(){
            helloWorld.increment();
        }
    }.start()
    //线程2
    new Thread(){
        public void run(){
            helloWorld.increment();
        }
    }.start()
       
    

    常量:主存(内存)

    线程的工作内存:cpu缓存

    常量操作:read load use assign store write

    image-20230731165920298

    6.1. 可见性、原子性、有序性

    • 可见性
      • 没有可见性:线程1更新了数据,但是线程2看到的还是工作内存中旧的数据
      • 有可见性:数据更新之后,线程1会强制使线程2重新读取修改后的数据。
    • 原子性
      • 一次只有一个线程进入临界区。data++必须是独立执行的。
    • 有序性
      • 在任务需要的资源准备完全之后,执行该线程任务。

    6.2. 从底层角度聊volatile关键字原理

    volatile:用来解决可见性和有序性,对原子性的保证很有限。(对64位的long型有一定原子性保证)

    • 实现可见性
      • 当加上volatile的变量改变时,会使其他线程工作内存的过期变量失效。
    • 保证有序性
      • 保证写在读之前

    6.3. 指令重排和happens-before原则

    指令重排有可能导致有序性失效。

    happens-before原则:

    • 线程内按照代码顺序,写在前面的代码先行发生在卸载后面的代码。
    • 锁定操作:对锁的unlock操作先行发生在lock操作
    • volatile变量原则:写操作在读操作之前
    • 传递原则:A先于B,B先于C,则A先于C
    • 线程启动原则:线程的启动thread.star()先于线程中的其他操作。还有interrupt
    • 线程终结原则:线程的所有操作都先于线程的终止检测,使用thread.jion()结束。
    • 对象终结原则:一个对象的初始化完成在finalize()方法之前。

    6.4. volatile底层如何基于内存屏障保证可见性和有序性

    对volatile的值的操作代码前后加上内存屏障。

    内存屏障:禁止重排序

    7. Spring

    7.1. Spring的IOC

    如果没有IOC:tomcat+servlet:tomcat 监听端口来将请求转发给servlet来处理,耦合严重,需要变动时,修改很麻烦。

    IOC:依赖注入,控制反转,容器根据xml配置或者注解来对bean对象之间的引用关系进行依赖注入

    底层核心技术:反射。根据类来自动构建对应的对象。

    类与类彻底解耦。

    image-20230801142412643

    7.2. Spring的AOP

    MySQL:事务:一次开启一个事务,其中进行多次增删改查。如果有一条失败了,会回滚事务,把这个事务中所有的sql语句都恢复。

    AOP:做一个切面Aspect,给所有类似servicexxx代码之前都会开启一个事务,在这些方法运行完毕之后,根据是否抛出异常,去回滚或者提交事务。

    核心技术:动态代理

    Spring会给正在运行的类生成动态代理类,包含我们写的类。然后在代理类中给逻辑前后加上事务。

    如何限定AOP。

    TODO

    7.3. 了解过cglib动态代理吗,他和jdk动态代理的区别是什么

    jdk动态代理,有接口的时候使用,生成一个实现这些同样接口的对象。

    没有接口会使用cglib来生成你的类的子类,覆盖你的类的方法,在方法中加入增强的代码。

    7.4. spring事务的实现原理是什么,事务传播机制是什么

    不同事务之间不互相影响

    @Transactional(propagation = Propagation.REQUIRED)会开启一个事务

    • Propagation_REQUIRED:如果当前没有事务,创建一个事务,多个调用加入到一个事务中。
    • Propagation_SUPPORT:之前有事务则加入,没有则不开启。
    • Propagation_MANDATORY:有事务加入,没有则报错。
    • Propagation_RESQUIRES_NEW:强制开启一个新事务。
    • Propagation_NOT_SUPPORTED:不使用事务,有事务会挂起
    • Propagation_NEVER:不允许使用事务,有事务会报错
    • Propagation_NESTED:嵌套事务,外层事务回滚会导致内存事务也回滚,内层不影响外层。

    7.5. Springboot 的核心架构

    自动装配依赖。不需要像spring一样自己配置xml文件,引入jar包。减少了配置。

    7.6. Spring 核心源码

    Spring bean 生命周期:

    • 创建bean
      • 实例化一个bean
      • 依赖注入
        • 把这个bean的依赖的bean实例化,也进行依赖注入。注入方法:构造函数,setter方法。
      • 处理Aware接口
        • 如果这个bean实现了Aware相关的接口,Spring容器会把自己的信息注入给bean中。
      • BeanPostProcesser
        • 在bean实例初始化之前和之后可以执行的方法。
      • init初始化方法
    • 销毁
      • DisposableBean接口,会调用这个接口实现的destroy方法
      • 最后,如果配置了destroy-method方法,会调用这个方法

    7.7. Spring中的设计模式

    工厂,单例,代理

    工厂模式:使用工厂类来创建类。

    单例模式:每个bean在系统运行期间只会创建一个实例对象。

    代理模式:AOP

    7.8. SpringMVC架构

    • tomcat 监听端口,将请求转发给SpringMVC的DispathcherServlet
    • 然后SpringMVC再根据url将请求转发给对应的controller
    • 返回json给前端,前端符合渲染

    7.9. SpringCloud核心架构

    这些框架

    8. JVM

    8.1. JVM中有哪几块内存区域,Java8之后对内存分代做了什么改进

    • 栈内存:每个线程独有
    • 堆内存:存放对象、实例
    • 永久代区域:我们写的类

    Java8以后永久代变成metaspace

    常量区放在了堆里面

    8.2. JVM如何运行起来的,如何创建各种对象

    线程执行main函数同时创建对象

    Spring容器创建一些bean对象

    把执行的方法和局部变量放在栈帧。

    8.3. JVM什么时候会触发垃圾回收

    内存分代:年轻代(eden:s1: s2, 8:1:1 )和老年代

    年轻代和老年代统称为堆

    新生成的对象实例存放在年轻代。

    • ygc:Eden区满了。进行youngGC
      • 没有引用的对象(类)被回收

    年轻代垃圾回收算法:

    • 复制算法:因为年轻代中大多数都是垃圾对象,所以把存活对象复制到s1中,一键全部清除Eden区。
      • 把s1和Eden中的存活对象复制到s2,把s1和Eden区全部清除

    8.4. 什么时候对象会转移到老年代

    如果存活了多次垃圾回收过程,就会转移到老年代

    如果s区放不下,会把一些存活的对象直接放到老年代中。

    对于大对象会直接放到老年代中,防止ygc反复复制大对象。

    8.5. 常用的垃圾回收器,老年代如何回收

    老年代中大多数是长期存活的对象,所以使用标记-清理方法:把所有存活的对象压缩到连续的位置,然后统一清理,可以防止内存碎片问题。

    常用的垃圾回收器:

    • CMS+parnew jdk8-jdk9
    • g1 jdk11
    • ZGC

    8.6. 生产环境如何设置jvm参数的,如何检查jvm的运行情况

    tomcat的配置脚本,catalina脚本设置。

    如果使用jar启动,再java命令后直接加上参数

    参数:

    • 内存区域大小的分配:
      • 栈大小
      • metaspace大小
      • eden survivor
      • 堆大小
      • 年轻代、老年代
    • 垃圾回收器
      • 年轻代和老年代用了什么回收器
      • 是否有特殊参数,作用是什么

    jstat压测,QPS,接口性能

    8.7. JVM GC优化

    自己动手进行压测,调试一下

    8.8. 发生OOM之后,应该如何排查和处理线上系统的OOM问题

    在jvm设置参数,发生oom之后保存快照。

    找出占用内存最大的对象和创建它的代码,进行调优。

    9. 网络

    9.1. TCP/IP的四层模型和七层网络模型

    TCP/IP四层:数据链路层、网络层、传输层、应用层

    • 物理层:硬件部分
    • 数据链路层:将0/1信号分组,确定来源去向
      • 以太网协议:一组信号是一个网络帧。每帧有两个部分:表头和数据,表头保存说明性的东西,比如发送者,接收者,数据类型等。通过网卡来发送接收数据,mac地址是网卡的id。
      • mac:前6个16进制是厂商编号,后6个编号是网卡流水号。
    • 网络层:
      • IP协议
      • 判断是不是一个子网:使用ip的二进制和子网掩码进行与运算,看结果前三个部分如果是一样的就是子网。
      • 不在一个子网需要一个路由器,路由器判断数据包的目标mac是不是自己的子网内的mac,是则转发。
      • ARP cache 会让每一个电脑都缓存到子网中所以电脑的ip和mac对应关系。
      • 路由器就可以看做是一个网关
    • 传输层TCP协议:仅仅规定了基于端口的点对点通信协议,包含如何建立连接,读取和发送信息。要基于TCP 开发,实际上是使用socket开发。
    • 应用层:最常见的是http

    OSI七层:物理层、会话层、表示层 + 四层模型

    DNS:domain name server ,先通过dns服务器把域名翻译成IP。

    9.2. 浏览器访问baidu.com会发生什么

    • 域名解析为IP
    • 把请求打包成http包
    • 把http数据包包装成tcp数据包,tcp数据头包含接收者和发送者的端口号
    • 然后把全部数据包包装到ip数据包,ip数据头中包含发送者和接收者的ip
    • 然后以太网会把这个数据包封装到以太网数据包中,加上以太网头,其中包含有发送者和接收者的网卡mac地址。
      • 以太网一次传输字节有限,可能需要切割为多个包。
      • 根据IP头序号来合成一个包。
    • 然后通过多个路由转发到百度的子网中。

    9.3. TCP三次握手和四次挥手的流程,为什么不是五次或者两次?

    • 建立连接的三次握手:
      • 客户端发送syn(同步),表示自己进入了syn_send状态
      • 服务端恢复syn+ack(确认)表示确认收到同步请求,并且自己进入syn_recevie状态
      • 客户端发送ack,表示确认建立连接。当服务端接收到这个包的时候,连接正式建立
    • 为什么不是两次握手
      • 如果网络问题,遇到不想要的连接,三次连接可以让客户端发送给服务器复位信息,释放资源
    • 结束连接的四次挥手:
      • 客户端发送FIN(结束)
      • 服务端发送ACK(收到),这段时间有可能传输还没有完全完毕,等待全部完毕后再发送FIN。
      • 服务端发送FIN(结束)
      • 客户端发送ACK(收到):服务端收到这个请求后立刻关闭,客户端会等待一段时间,保证服务端确实接收到这个包

    2 3次挥手好像在某些情况可以合并?

    9.4. 说一下http长连接的原理

    http本身没有长连接,都是tcp的长连接和短链接

    http协议规范:请求头,请求体什么的

    http1.0 都是短链接,一次请求后直接断开tcp连接,需要指定keep-alive才能建立长连接

    http1.1 默认是长连接

    http2.0支持多路复用,一个tcp可以并行发送多个请求以及接收响应。

    http3.0 QUIC 建立在udp之上。

    9.5. https http+ssl/tsl

    使用证书加密

    • 非对称加密:rsa
      • 网站给浏览器发送证书(由权威机构颁发),浏览器验证合法性
      • 浏览器生成随机密码,用随机密码加密随机密码的hash,并且用证书的公钥加密这个随机密码,
      • 网站用证书的私钥解密这个随机面膜,再用随机密码解密得到hash,计算自己得到的密码的hash进行对比,如果完全相同,则可以使用
      • 之后用这个随机密码来实现加密通信。

    10. MySQL

    10.1. 引擎:mysiam innodb

    • mysiam:不支持事务,不支持外键约束。索引和数据文件分开,可以在内存缓存更多索引,查询性能会更好,适用于少量插入,大量查询
      • hadoop报表系统,用mysql mysiam比较适合,但是数据量太大超过500w以上就也不能用mysql了。
    • innodb(默认):支持事务,外键约束,高并发,高可用,大数据量

    10.2. Mysql索引原理和数据结构。

    索引:默认b+树

    • b-树
      • 每个节点都存储对应的data
    • b+树
      • 只有叶子节点存储对应的data

    Mysiam的索引:叶子节点存储的是索引的物理地址。然后用物理地址去数据文件找数据。

    Innodb的索引:表要求必须要有主键,默认会为主键建立一个索引,节点data包含所有数据(一个记录,整行),叫做聚簇索引。如果你使用name来找数据,那么从name索引中找到的data是主键(id),再用id从聚簇索引找data。

    10.3. 索引的使用规则

    怎么建立索引?

    最左前缀匹配原则:

    创建联合索引:create index(shop_id,product_id,gmt_create)

    如果你使用 shop_id 和 gmt_create来查找,那么不会直接通过这个联合索引查找,而是通过使用shop_id筛选出来一些数据,之后扫描gmt_create字段符合要求过滤。(性能也还行)。

    但如果没有最左边的任何字段,就没法用这个索引,比如直接通过product_id查找,这个是没有用到这个索引的。

    范围列匹配,最左前缀范围查找会用索引,之后的不会用索引了。

    调用了函数的sql语句不使用索引

    建立尽量少的索引,10条以内为佳

    尽量选唯一字段进行建立索引。选择的字段 去重后数量/总数量 ,结果要是小,则说明这个索引用处不大。

    10.4. 事务的几个特点

    ACID

    • Atomic:原子性,同时执行的sql要么一起成功,要么一起失败
    • Consistency:一致性,事务之前前后数据都应该是正确的
    • Isolation:隔离性,多个事务之间不互相干扰
    • Durability:持久性

    10.5. 隔离级别

    • 读未提交:事务A读取到事务B还没有提交的数据
    • 读已提交:事务A读取到原来的数据,然后事务B提交修改,事务A再次读取,读到不一样的数据。(不可重复读)
    • 可重复读:事务A读取到原来的数据,然后事务B提交修改,事务A再次读取,读到的还是事务A最开始读取到的数据。(可重复读)
    • 幻读(不是隔离级别):事务A查询所有数据,准备插入一一条数据,事务B插入了一行数据,事务B提交插入。事务A想要插入数据,发现这个数据已经存在(被事务B插入)。
    • 串行化(为了解决幻读):事务A查询所有数据,事务B想要插入一行数据会被拒绝。事务A提交后,事务B才能进行插入数据。

    MySQL默认级别:可重复读。

    实现可重复读的机制:MVCC机制 multi-version concurrency control

    • 事务id是全局唯一且递增的,

    • 查询事务只会找比自己事务id小的 创建事务。

      • 创建事务id<=当前事务id

      • 当前事务id<删除事务id

    • 不同事务修改某行数据,会多出来一行,id相同。

    10.6. 数据库锁

    自动加锁

    表锁 行锁 页锁

    • myisam会加表锁。锁表的时候查询会报504

    行锁:innodb:共享锁(s)和排他锁(x)

    • 共享锁和排他锁不能同时加
    • select 不加锁因为MVCC有快照,增删改会加一个行锁排他锁。

    手动加锁

    加共享锁:select * from table where id = 1 lock in share mode

    加排他锁:select * from table where id =1 for update

    悲观锁:进行操作都加上排他锁

    乐观锁:加上版本号字段,在事务修改期间如果版本号不同,则这次修改失败,需要重新读取操作。

    死锁:dba查看死锁日志。

    10.7. MySQL调优的常用手段

    • 保持sql简单,建议使用单表查询
      • 优化索引
      • 查看sql的执行计划:explain select * from table

    10.8. E-R图

    entities-relationship图

    关系:操作数是关系,重复行的对应关系(投影)要去掉。剩下的是关系:一对多,一对一,多对多等

    11. socket

    直接使用tcp进行通信,就是socket编程

    可以认为socket处于传输层。或者是介于传输层和应用层直接。

    socket 就是封装了tcp的编程规范

    12. 进程通信和线程切换

    12.1. 进程通信

    9.1.1管道 pipe

    只有父子进程(fork得到的)才能使用这个管道进行通信。

    9.1.2命名管道

    无亲缘关系的管道可以使用命名管道通信

    9.1.3消息队列

    9.1.4共享内存

    12.2. 线程如何切换

    时间片轮换

    优先级调度等

    13. nio,bio,aio都是什么,有什么区别。nio的原理是什么

    13.1. bio通信原理

    服务端使用ServerSocket为每一个客户端建立一个线程用于通信。只要客户端还和服务端有连接,这个线程都要等待。

    问题:超过几千客户端就不能够正常运行了

    13.2. nio通信原理

    每有一个客户端和服务端建立连接,都会创建一个channel,这些channel都会注册在selector中,这个selector只有一个线程。会不断轮询这些channel。,如果有请求过来,会创建一个线程来处理这个请求。处理完成后这个线程会被销毁。

    可以对处理请求的线程创建一个线程池。

    在工作线程和channel 直接可以维护一个cache

    工作线程从channel 中读取数据,给channel写数据,是同步的

    13.3. aio

    对nio有优化:工作进程从channel读数据的时候,会绑定一个buffer,让操作系统来完成读操作,读完了来通知这个线程。

    写的时候也是把写的过程交给操作系统。

    13.4. 同步阻塞、同步非阻塞、异步非阻塞

    BIO是同步阻塞,针对的是对磁盘文件的io读写。读写过程中线程阻塞

    NIO是同步非阻塞,在操作系统读写数据的时候,线程可以做其他事情,但是也需要不断轮询判断读写完成了没有。

    AIO 是异步非阻塞,发起文件读写的操作之后,交给操作系统,操作系统执行完毕之后,会通知这个线程。

    13.5. BIO NIO AIO demo代码

    TOREAD

    14. 线上服务器问题

    14.1. 线上CPU占用100%,排查步骤:

    • top -c 输入P ,按照cpu进行排序
    • top -Hp pid ,可以看到这个进程的负载
    • 把线程pid换成16进制pidhex,如何使用jstack pid | grep pidhex -C5 –color 就可以定位到线程中哪行代码的cpu占用最高。

    14.2. 如果线上进程kill不掉怎么办

    ps aux 查看是否有僵尸进程 zombie

    ps -ef | grep 僵尸进程id ,得到父进程id

    然后kill 父进程之后kill子进程。

    14.3. 磁盘马上占满了怎么办

    是否是日志占满空间了?

    经历:安装程序的时候提示根目录空间占用100%,发现是pcp(性能监控软件)的日志占用了很大空间。解决方法是直接使用rm -rf删除了这些日志/var/log/pcp/pmlogger/openEuler1/。

    关于pcp:

    • Performance Co-Pilot (pcp) 提供了支持系统级性能监控和管理的框架和服务。它为系统中的所有性能数据提供了统一的抽象,以及用于询问、检索和处理该数据的许多工具。
    • 这些生成的log,在openeuler系统没有设置自动清理,导致了日志积累。

    find / -size+100M | xargs ls -lh 找大于100m的文件

    15. Java语言特性

    15.1. 参数传递

    • Java 中将实参传递给方法(或函数)的方式是 值传递
      • 如果参数是基本类型的话,很简单,传递的就是基本类型的字面量值的拷贝,会创建副本。
      • 如果参数是引用类型,传递的就是实参所引用的对象在堆中地址值的拷贝,同样也会创建副本
    • 想要通过传值来修改原来的值
      • 通过数组
      • 通过类
      • 或者其他可变的引用类型
    • 不可变的引用类型
      • String
      • Integer
      • BigDecimal
      • LocalDate、LocalTime、LocalDateTime、Duration,Period

    15.2. 序列化

    • 序列化协议属于应用层或者传输层

    • 序列化的对象:实现Serializable 接口的类、实例变量的值、非静态成员变量

    • serialVersionUID:用来判断对象版本。手动设置这个变量可以解决对象版本兼容问题。

    • Kryo用来序列化Java代码性能高。

      • import com.esotericsoftware.kryo.Kryo;
        import com.esotericsoftware.kryo.io.Input;
        import com.esotericsoftware.kryo.io.Output;
        
        import java.io.*;
        
        public class KryoSerializationExample {
            public static void main(String[] args) {
                // 创建 Kryo 对象
                Kryo kryo = new Kryo();
        
                // 创建要序列化的对象
                Person person = new Person("Alice", 30);
        
                // 序列化
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                Output output = new Output(outputStream);
                kryo.writeObject(output, person);
                output.close();
        
                // 将序列化的数据保存到文件
                try (FileOutputStream fileOutputStream = new FileOutputStream("person.dat")) {
                    outputStream.writeTo(fileOutputStream);
                } catch (IOException e) {
                    e.printStackTrace();
                }
        
                // 反序列化
                try (FileInputStream fileInputStream = new FileInputStream("person.dat")) {
                    // 创建 Kryo 输入流
                    Input input = new Input(fileInputStream);
        
                    // 从输入流中反序列化对象
                    Person deserializedPerson = kryo.readObject(input, Person.class);
                    input.close();
        
                    // 使用反序列化后的对象
                    System.out.println("姓名: " + deserializedPerson.getName());
                    System.out.println("年龄: " + deserializedPerson.getAge());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        

    15.3. 泛型和通配符

    1. 泛型(Generics):泛型允许在编译时指定类、接口或方法操作的数据类型,以提供类型安全和代码重用。通过使用泛型,可以在编译时捕获类型错误,并避免在运行时出现类型转换错误。
      • 定义泛型类:使用 <T> 来表示类型参数,可以在类名后面声明一个泛型类型。例如:class MyClass<T> { ... }
      • 定义泛型方法:使用 <T> 来表示类型参数,可以在方法返回类型前声明一个泛型类型。例如:<T> T myMethod(T obj) { ... }
      • 约定
        • E:表示集合中的元素类型。
        • K:表示映射中的键类型。
        • V:表示映射中的值类型。
        • T:表示任意类型。
        • SUV:用于表示第二、第三和第四类型参数。
    2. 类型通配符(Wildcard):类型通配符用问号 ? 表示,用于灵活处理不同类型的泛型对象。通配符可以用于泛型类、泛型方法和通配符限定。
      • 通配符限定上界:? extends Type,表示泛型参数是 Type 类型或其子类。例如:List<? extends Number> 表示一个只能接受 Number 及其子类的 List。
      • 通配符限定下界:? super Type,表示泛型参数是 Type 类型或其父类。例如:List<? super Integer> 表示一个只能接受 Integer 及其父类的 List。
      • 无限制通配符:?,表示可以是任意类型。例如:List<?> 表示一个可以接受任意类型的 List。

    15.4. 反射

    15.4.1.1. 基本操作
    //获取类
    Class<?> myClass = MyClass.class;
    Class<?> myClass = Class.forName("com.example.MyClass");
    //获取构造函数
    Constructor<?> constructor = myClass.getDeclaredConstructor(parameterTypes);
    //创建对象
    Object myObject = constructor.newInstance(arguments);
    //获取方法
    Method method = myClass.getDeclaredMethod("methodName", parameterTypes);
    //调用方法
    method.invoke(myObject, arguments);
    //获取字段
    Field field = myClass.getDeclaredField("fieldName");
    //获取字段的值
    Object fieldValue = field.get(myObject);
    //设置字段的值
    field.set(myObject, value);
    //对于私有方法或字段,可能需要使用setAccessible(true)来绕过访问限制。
    

    15.5. 代理模式

    15.5.1.1. 1.静态代理:

    就是把在调用类的前后在执行一些步骤。

    public class SmsProxy implements SmsService {
    
        private final SmsService smsService;
    
        public SmsProxy(SmsService smsService) {
            this.smsService = smsService;
        }
    
        @Override
        public String send(String message) {
            //调用方法之前,我们可以添加自己的操作
            System.out.println("before method send()");
            smsService.send(message);
            //调用方法之后,我们同样可以添加自己的操作
            System.out.println("after method send()");
            return null;
        }
    }
    
    15.5.1.2. 2.动态代理

    2.1JDK代理:在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。

    //调用proxy的方法newProxyInstance
    Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new DebugInvocationHandler(target));
    //DebugInvocationHandler 是自定义的proxy方法,需要实现InvocationHandler接口的invoke方法,实际上是调用了这里的invoke方法
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    public class DebugInvocationHandler implements InvocationHandler {
        private final Object target;
        public DebugInvocationHandler(Object target) {
            this.target = target;
        }
        @Override
        public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
            System.out.println("before Method"+ method.getName());
            Object result = method.invoke(target, objects);
            System.out.println("after Method"+ method.getName());
            return result;
        }
    }
    

    JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类

    为了解决这个问题,我们可以用 CGLIB 动态代理机制。

    2.2CGLIB代理

    • 在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。

    • maven依赖:

      • <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>3.3.0</version>
        </dependency>
        
    • //代理工厂中生成一个 enhancer对象,这个对象拥有下列属性,其中DebugMethodInterceptor拦截器是自定义的最终执行的方法
      public class CglibProxyFactory {
          public static Object getProxy(Class<?> clazz){
              Enhancer enhancer = new Enhancer();
              enhancer.setClassLoader(clazz.getClassLoader());
              enhancer.setSuperclass(clazz);
              enhancer.setCallback(new DebugMethodInterceptor());
              return enhancer.create();
          }
      }
      //DebugMethodInterceptor 实现MethodInterceptor接口,重写intercept方法,这个方法相当于前面的invoke
      public class DebugMethodInterceptor implements MethodInterceptor {
          @Override
          public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
              //调用方法之前,我们可以添加自己的操作
              System.out.println("before method " + method.getName());
              Object object = methodProxy.invokeSuper(o, objects);
              //调用方法之后,我们同样可以添加自己的操作
              System.out.println("after method " + method.getName());
              return object;
          }
      }
      

    2.3二者的区别

    jdk动态代理,有接口的时候使用,生成一个实现这些同样接口的对象。

    没有接口会使用cglib来生成你的类的子类,覆盖你的类的方法,在方法中加入增强的代码。

    15.6. BigDecimal常见方法

    我们在使用 BigDecimal 时,为了防止精度丢失,推荐使用它的BigDecimal(String val)构造方法或者 BigDecimal.valueOf(double val) 静态方法来创建对象。

    使用BigDecimal(double val)会丢失精度

    方法:

    • add:加
    • subtract:减
    • multiple:乘
    • divide:除
      • 除的时候尽量使用三个参数的版本:指定保留规则RoundingMode
    • compareTo:a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1 表示 a 大于 b
      • 比较不能使用equals,因为equals比较会同时比较精度,1.0和1.00不相同
    • setScale:保留小数

    工具类:

    
    import java.math.BigDecimal;
    import java.math.RoundingMode;
    /**
     * 简化BigDecimal计算的小工具类
     */
    public class BigDecimalUtil {
        /**
         * 默认除法运算精度
         */
        private static final int DEF_DIV_SCALE = 10;
        private BigDecimalUtil() {
        }
        /**
         * 提供精确的加法运算。
         *
         * @param v1 被加数
         * @param v2 加数
         * @return 两个参数的和
         */
        public static double add(double v1, double v2) {
            BigDecimal b1 = BigDecimal.valueOf(v1);
            BigDecimal b2 = BigDecimal.valueOf(v2);
            return b1.add(b2).doubleValue();
        }
        /**
         * 提供精确的减法运算。
         *
         * @param v1 被减数
         * @param v2 减数
         * @return 两个参数的差
         */
        public static double subtract(double v1, double v2) {
            BigDecimal b1 = BigDecimal.valueOf(v1);
            BigDecimal b2 = BigDecimal.valueOf(v2);
            return b1.subtract(b2).doubleValue();
        }
        /**
         * 提供精确的乘法运算。
         *
         * @param v1 被乘数
         * @param v2 乘数
         * @return 两个参数的积
         */
        public static double multiply(double v1, double v2) {
            BigDecimal b1 = BigDecimal.valueOf(v1);
            BigDecimal b2 = BigDecimal.valueOf(v2);
            return b1.multiply(b2).doubleValue();
        }
        /**
         * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
         * 小数点以后10位,以后的数字四舍五入。
         *
         * @param v1 被除数
         * @param v2 除数
         * @return 两个参数的商
         */
        public static double divide(double v1, double v2) {
            return divide(v1, v2, DEF_DIV_SCALE);
        }
        /**
         * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
         * 定精度,以后的数字四舍五入。
         *
         * @param v1    被除数
         * @param v2    除数
         * @param scale 表示表示需要精确到小数点以后几位。
         * @return 两个参数的商
         */
        public static double divide(double v1, double v2, int scale) {
            if (scale < 0) {
                throw new IllegalArgumentException(
                        "The scale must be a positive integer or zero");
            }
            BigDecimal b1 = BigDecimal.valueOf(v1);
            BigDecimal b2 = BigDecimal.valueOf(v2);
            return b1.divide(b2, scale, RoundingMode.HALF_EVEN).doubleValue();
        }
        /**
         * 提供精确的小数位四舍五入处理。
         *
         * @param v     需要四舍五入的数字
         * @param scale 小数点后保留几位
         * @return 四舍五入后的结果
         */
        public static double round(double v, int scale) {
            if (scale < 0) {
                throw new IllegalArgumentException(
                        "The scale must be a positive integer or zero");
            }
            BigDecimal b = BigDecimal.valueOf(v);
            BigDecimal one = new BigDecimal("1");
            return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
        }
        /**
         * 提供精确的类型转换(Float)
         *
         * @param v 需要被转换的数字
         * @return 返回转换结果
         */
        public static float convertToFloat(double v) {
            BigDecimal b = new BigDecimal(v);
            return b.floatValue();
        }
        /**
         * 提供精确的类型转换(Int)不进行四舍五入
         *
         * @param v 需要被转换的数字
         * @return 返回转换结果
         */
        public static int convertsToInt(double v) {
            BigDecimal b = new BigDecimal(v);
            return b.intValue();
        }
        /**
         * 提供精确的类型转换(Long)
         *
         * @param v 需要被转换的数字
         * @return 返回转换结果
         */
        public static long convertsToLong(double v) {
            BigDecimal b = new BigDecimal(v);
            return b.longValue();
        }
        /**
         * 返回两个数中大的一个值
         *
         * @param v1 需要被对比的第一个数
         * @param v2 需要被对比的第二个数
         * @return 返回两个数中大的一个值
         */
        public static double returnMax(double v1, double v2) {
            BigDecimal b1 = new BigDecimal(v1);
            BigDecimal b2 = new BigDecimal(v2);
            return b1.max(b2).doubleValue();
        }
        /**
         * 返回两个数中小的一个值
         *
         * @param v1 需要被对比的第一个数
         * @param v2 需要被对比的第二个数
         * @return 返回两个数中小的一个值
         */
        public static double returnMin(double v1, double v2) {
            BigDecimal b1 = new BigDecimal(v1);
            BigDecimal b2 = new BigDecimal(v2);
            return b1.min(b2).doubleValue();
        }
        /**
         * 精确对比两个数字
         *
         * @param v1 需要被对比的第一个数
         * @param v2 需要被对比的第二个数
         * @return 如果两个数一样则返回0,如果第一个数比第二个数大则返回1,反之返回-1
         */
        public static int compareTo(double v1, double v2) {
            BigDecimal b1 = BigDecimal.valueOf(v1);
            BigDecimal b2 = BigDecimal.valueOf(v2);
            return b1.compareTo(b2);
        }
    }
    

    15.7. Unsafe类

    在JUC高并发编程中主要使用,用来执行本地方法(native方法)。

    native方法的执行绕过了Java本身的界限。能直接接触到操作系统底层的某些功能,因此并不安全。

    可以实现的功能:

    1. 内存操作
    2. 内存屏障
    3. 对象操作
    4. 数据操作
    5. CAS 操作
    6. 线程调度
    7. Class 操作
    8. 系统信息

    因为其不安全性,并不推荐使用。

    16. 算法

    16.1. 字符串算法

    16.1.1. 字符串最长匹配串(KMP)

    核心:对于匹配串生成一个失配表,根据失配表进行匹配

    失配表:匹配串的第几位没有匹配成功,再从匹配串的某一位重新匹配

    16.1.2. 替换字符

    调用replace方法即可

    16.1.3. 最长前缀匹配

    先利用 Arrays.sort(strs)为数组排序,再将数组第一个元素和最后一个元素的字符从前往后对比即可(sort的时候数字排在最前面)

    16.1.4. 构建回文串

    • 出现的字符次数是偶数的情况
    • 出现的字符次数是偶数的组合+最长的奇数组合

    16.1.5. 验证回文串

    从两边向中间遍历

    16.1.6. 最长回文子序列

    **动态规划: **TOREAD

     dp[i][j] = dp[i+1][j-1] + 2 if s.charAt(i) == s.charAt(j) otherwise, dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1])
    

    16.1.7. 括号的匹配深度

    while(replace(“()”,””) != -1)

    16.1.8. 字符串转换为整数

    实现 Integer.valueOf(string)的功能,但是 string 不符合数字要求时返回 0

    //https://www.weiweiblog.cn/strtoint/
    public class Main {
    
      public static int StrToInt(String str) {
        if (str.length() == 0)
          return 0;
        char[] chars = str.toCharArray();
        // 判断是否存在符号位
        int flag = 0;
        if (chars[0] == '+')
          flag = 1;
        else if (chars[0] == '-')
          flag = 2;
        int start = flag > 0 ? 1 : 0;
        int res = 0;// 保存结果
        for (int i = start; i < chars.length; i++) {
          if (Character.isDigit(chars[i])) {// 调用Character.isDigit(char)方法判断是否是数字,是返回True,否则False
            int temp = chars[i] - '0';
            res = res * 10 + temp;
          } else {
            return 0;
          }
        }
       return flag != 2 ? res : -res;
    
      }
    
      public static void main(String[] args) {
        // TODO Auto-generated method stub
        String s = "-12312312";
        System.out.println("使用库函数转换:" + Integer.valueOf(s));
        int res = Main.StrToInt(s);
        System.out.println("使用自己写的方法转换:" + res);
    
      }
    
    }
    

    16.2. 链表算法

    16.2.1. 两数相加

    16.2.2. 反转链表

    16.2.3. 链表中倒数第K个数

    两个节点同时向后,第一个节点走到第k个,第二个再开始走。

    第一个节点到末尾,第二个节点就到倒数第k个

    16.2.4. 合并两个排序的链表

    16.3. 部分常见题目

    16.3.1. 斐波那契数列

    递归法:容易因为重复计算超时

    迭代法:

    int Fibonacci(int number) {
        if (number <= 0) {
            return 0;
        }
        if (number == 1 || number == 2) {
            return 1;
        }
        int first = 1, second = 1, third = 0;
        for (int i = 3; i <= number; i++) {
            third = first + second;
            first = second;
            second = third;
        }
        return third;
    }
    

    16.3.2. 跳台阶:也是斐波那契数列

    16.3.3. 变态跳台阶:一个可以跳n个

    假设 n>=2,第一步有 n 种跳法:跳 1 级、跳 2 级、到跳 n 级 跳 1 级,剩下 n-1 级,则剩下跳法是 f(n-1) 跳 2 级,剩下 n-2 级,则剩下跳法是 f(n-2) …… 跳 n-1 级,剩下 1 级,则剩下跳法是 f(1) 跳 n 级,剩下 0 级,则剩下跳法是 f(0) 所以在 n>=2 的情况下: f(n)=f(n-1)+f(n-2)+…+f(1) 因为 f(n-1)=f(n-2)+f(n-3)+…+f(1) 所以 f(n)=2f(n-1) 又 f(1)=1,所以可得*f(n)=2^(number-1)

    java 中有三种移位运算符:

    1. “<<” : 左移运算符,等同于乘 2 的 n 次方
    2. “>>”: 右移运算符,等同于除 2 的 n 次方
    3. “>>>” : 无符号右移运算符,不管移动前最高位是 0 还是 1,右移后左侧产生的空位部分都以 0 来填充。与>>类似。

    16.3.4. 二维数组查找

    16.3.5. 调整数组顺序使奇数位于偶数前面

    思路:统计奇数个数,假设为n

    遍历数组,奇数从0存,偶数从n存

    16.3.6. 两个栈实现队列

    16.3.7. 栈的压入弹出序列

    判断弹出序列是否合法。使用两个栈,第一个栈出的元素不是序列要的,就压入弹出序列,如果原栈为空,弹出栈的栈首不是要求序列则不对。

    16.4. 排序算法

    十大排序算法图解-JavaGuide

    菜鸟教程数据结构


    转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 2738430398@qq.com