Java中==和equals的区别

  • ==判断的是两个值是否相等,八大基本数据类型(byte(8) short(16) int(32)
  • long(64) float(32) double(64) char(16) boolean),相当于直接比较值;对于对象或者包装类类,比较的是对象的地址。
    equals是Object中声明的方法,不重写的情况下,比较的是地址。因此可以重写来方便一些比较的进行。比如String中,重写了equals,比较两个字符串是否一样,而不是比较是否为同一对象。
  • 对于Integer,还有一些坑,在整数类型转为包装类型的自动转换时候,会有一个缓存。因此会出现
      public static void main(String[] args) {
          Integer a = 127, b = 127, c = 128, d = 128;
          System.out.println(a == b); // true
          System.out.println(c == d); // false
      }
    // Integer类中有
      public static Integer valueOf(int i) {
          if (i >= IntegerCache.low && i <= IntegerCache.high)
              return IntegerCache.cache[i + (-IntegerCache.low)];
          return new Integer(i);
      }
  • 这种情况很好避免,直接使用new Integer()就好,然后比较记得用equals()。
  • 更具体地来说,栈内存保存的是引用名称,而具体的对象放到堆内存中。==比较的就是堆内存中的地址值。

HashMap和HashTable的区别,为什么用ConcurrentHashMapt而不是使用Hashtable。

  • 对于HashMap,在JDK1.7中使用数组+链b表实现的。JDK1.8中是由数组+链表/红黑树实现的。
  • HashMap可以有且最多有一个null作为key,value随意;元素不存在的时候,get()方法返回null,因此用constainsKey()判断元素是否存在;线程不安全。Hashtable键值都不能为null。
  • 每次扩容,数组中的元素一次重新计算存放位置并重新插入。1.7是先扩容在插入新值,1.8是先插值再扩容。
  • 线程不安全的点在于,接近零界点的时候,多个线程进行put,触发resize和rehash,其中rehash可能在并发的情况下出现链表环,进而导致死循环。再JDK1.8及以后,链表长度大于阈值,链表会改成红黑树,解决了链表成环的问题。具体解决原理的一个博客
  • HashMap继承自AbstractMap,HashTable继承自Dictionary,都实现了Map,Cloneable接口。TreeMap中的keySet()使用红黑树实现保证了有序(从小到大)
  • HashMap总结:基于哈希散列表实现,调用键对象的hashcode()方法,计算hashcode,找到对应的bucket位置(一个数组)来存储值对象。获取对象时,通过对象的equals方法,判断键是否为同一个。

JVM内存区域

  • Java程序执行流程如下
    图片说明
  • 可见Java源码======编译器==========>字节码文件========JVM类加载器=====>加载字节码文件。加载完毕后,有JVM执行引擎执行。执行过程中,需要一段空间存储程序执行期间用到的数据和相关信息,这段空间称为Runtime Data Area,也称JVM内存。因此内存管理通常指,这段空间进行的内存分配和内存回收。
  • 这段内存划分为以下几个部分:
    • 程序计数器,Java栈,本地方法栈,方法区,堆。
    • 程序计数器:用来只是当前执行的指令。每个线程都有自己的程序计数器,并且不能相互干扰。如果某线程当前执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果执行的是native方法,则程序计数器中的值是undefined。程序计数器中存储的数据占用空间不会随着程序执行发生改变,因此不会OOM。此内存区域是唯一一个在VM Spec中没有规定任何OOM情况的区域。
    • Java虚拟机栈(Java virtual machine stacks):Java栈是Java方法执行的内存模型。其中放着一个个栈帧,每个栈帧对应着一个被调用的方法,在栈帧中包括了局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池的引用。方法返回地址。以及一些额外的信息。当线程执行方法时,就会创建一个栈帧并压入栈中。方法执行完毕,该栈帧会弹出。因此可见,线程当前执行的方法所对应的栈帧一定位于Java栈的顶部。局部变量表顾名思义。方法返回地址,当一个方法执行完毕后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。
    • 本地方法栈:本地方法栈和Java栈的作用及原理相似。区别是Java栈是为执行Java方法服务的,而本地方法栈式执行本地方法服务的。JVM规范中,没有特别规定,HotSpot中直接将本地方法栈和Java栈合二为一。
    • 堆:C用malloc和free,C++用new和delete操纵堆内存。Java中堆用来存储对象本身和数组(数组的引用放在Java栈中),有垃圾回收机制对这块内存进行操作。JVM只有一个堆,堆是所有线程共享的。
    • 方法区:方法区也是各个线程共享的区域,在方法区中,存储了各个类的信息(类的名词,方法,字段)静态变量,常量,编译器优化后的代码。Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。 方法区有一个运行常量池,是每一个类或接口的常量池的运行时表现形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然非Class文件常量池的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中。不如String的intern方法。 JVM规范没有规定方法区必须进行GC,因此很多人将方法区称为永久代,(因为HotSpot使用永久代实现方法区)从而JVM的GC收集器可以管理这部分区域,不用专门涉及垃圾回收机制。JDK7后,Hotspot虚拟机就将常量池从永久代中移除了。

进程和线程的区别

  • 进程是一个具有一定独立功能的程序在一个数据集上的而一次动态执行。是OS资源分配和调度的一个独立单位。是一个抽象的概念,有程序,数据集合和进程控制块三部分组成。具有
    • 动态性:程序一次执行,是有生命期的
    • 并发性
    • 独立性:OS资源分配和调度的一个独立单位
    • 结构性:有程序,数据,进程控制块组成。
    • 资源分配的最小单位是进程(应该吧),CPU调度和时间片分配的最小单位是线程。

输入一个URL后发生了什么

  • DNS解析:
    • 浏览器根据URL寻找IP,首先查找浏览器缓存(浏览器会保存一些DNS信息)
    • 没找到,则浏览器调用操作系统缓存继续查找DNS信息。
    • 没找到,则浏览器发送一个请求到路由器上,路由器在路由器缓存中查找记录的DNS信息。
    • 没找到,则请求会被发送到本地DNS服务器,本地DNS服务器缓存了一张域名和IP地址对应的表格。
    • 没找到,则ISP(网络供应商)的DNS服务器会发送请求到更域名服务器,然后到顶级域名服务器,再到权威DNS。权威DNS是域名解析结果的原出处,它查询到对应的IP地址后,告诉本地DNS。
    • 还未找到,域名错误。
    • 浏览器缓存->OS缓存—>路由器缓存->本地DNS缓存->根DNS->顶级DNS->权威DNS
  • TCP连接
    • 浏览器得到IP后,向该IP地址发送TCP连接,TCP三次握手,目前HTTP协议大多数是1.1,1.1的协议里开启了keep-alive。这样建立的TCP连接,可以在多次请求中复用。
  • 浏览器发送HTTP请求:
    • 浏览器和服务器建立连接后,浏览器就可以给这个IP地址的服务器发送一个HTTP请求,方式为Get。本质就是在建立起来的TCP连接中,按照HTTP协议标准发送了一个网页索要请求。
  • 服务器处理请求
    • 服务器收到浏览器的请求后,解析这个请求,生成一个响应头和具体内容,响应头中有一个状态码:2开始表示正常,3表示重定向,4表示客户端错误(404,请求资源不存在)5开头表示服务端错误。
  • 浏览器渲染页面
  • 关闭TCP连接:根据Connection的keep-alive属性,选择是否断开TCP连接,HTTP/1.1一般支持同一个TCP多个请求,1.9版本以下完成一个请求就断开。

主键可以用UUID吗

  • 自增长优点:
    • 存储空间小
    • 性能高
    • 容易记忆理解
  • 自增长缺点
    • 数据量大,超出范围
    • 难以处理分布式和表合并的情况
    • 安全性低(有规律易于被非法获取)
  • UUID优点:(GUID有时指微软对UUID标准的实现)
    • 独一无二,重复机会少
    • 适合大量数据的插入和更新,适合高并发和分布式情况
    • 跨服务器数据合并非常方便
    • 安全性高
  • UUID缺点:
    • 存储空间大
    • 降低性能
    • 可读性差

动手写一个阻塞队列:todo

写一个OOM代码

import java.util.*;
public class Main {
    public static void main(String[] args) {
        List<Main> list = new ArrayList<>();
        while (true) {
            list.add(new Main());
        }
    }
}
  • MySQL的索引,乐观锁和悲观锁

    • todo
  • Spring的反向代理

    • 反向代理指以代理服务器来接受Internet上的请求,然后将该请求转发给内部网络上的服务器。将服务器上得到的结果返回给Internet请求的主机。正向代理是访问者使用的代理,反向代理是被访问者使用代理。代理本身不处理请求,只是添加了验证缓存等步骤,只是个中间人。
    • 使用springboot实现反向代理:主要用到到了org.mitre.dsmiley.httpproxy和com.google.guava两个库。前者是一个反向代理的servlet。后者用到了ImmutableMap,是一个不可变的Map实现类,其实用HashMap也行。
      import com.google.common.collect.ImmutableMap;
      import org.mitre.dsmiley.httpproxy.ProxyServlet;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.boot.web.servlet.ServletRegistrationBean;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import javax.servlet.Servlet;
      import java.util.HashMap;
      import java.util.Map;
      @Configuration
      public class SolrProxyServletConfiguration {
      // 配置两个地址
      // 读取配置中的路由地址 /api/*
      @Value("${proxy.servlet_url}")
      private String servletUrl;
      // 读取配置中的代理目标地址 http://www.baidu.com
      @Value("${proxy.target_url}")
      private String targetUrl;
      @Bean
      public Servlet createProxyServlet() {
        return new ProxyServlet();
      }
      @Bean
      public ServletRegistrationBean proxyServletRegistration() {
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(createProxyServlet(), servletUrl);
        Map<String, String> params = ImmutableMap.of("targetUri", targetUrl, "log", "true");
        Map<String, String> params1 = new HashMap<>();
        params1.put("targetUri", targetUrl);
        params1.put("log", "true");
        registrationBean.setInitParameters(params1);
        return registrationBean;
      }
      }
  • Spring如何解决循环依赖的问题

    • 循环依赖指两个或以上的bean互相持有对方,形成闭环。
    • 循环依赖种类:
      • 构造器的循环依赖。
      • setter的循环依赖
        @Component
        public class StudentA {
        public StudentB studentB;
        @Autowired
        public StudentA(StudentB studentB) {
        this.studentB = studentB;
        }
        }
        @Component
        public class StudentB {
        private StudentA studentA;
        @Autowired
        public StudentB(StudentA studentA) {
        this.studentA = studentA;
        }
        }
        // 两个component已经形成了循环依赖
        // 具体报错为:The dependencies of some of the beans in the application context form a cycle:
  • 解决方案:

    • 造成循环的一点中,注入的时候加一个Lazy:public StudentA(@Lazy StudentB studentB)
    • Spring文档建议的是,将构造函数进行依赖注入,改为使用setter方式注入。当依赖最终被使用的时候,才进行注入。这样就很少会产生循环依赖了。
      @Autowired
      public void setStudentB(StudentB studentB) {
      this.studentB = studentB;
      }
    • 使用PostConstruct,这是Java的注解。该注解用来修饰一个非静态的void的方法。被其修饰的方***在对象加载完成依赖注入后,执行。(The PostConstruct annotation is used on a method that needs to be execeted after dependency injection is done to perform any initialization. This method must be invoked before the class is put into service... 有很多注意事项,具体看源码注释)
    • 实现ApplicationContextAware和InitializingBean(这个在学了SpringBean生命周期后应该了解。)ApplicationContextAware:当一个类实现了这个接口,则Aware接口的bean在被初始化后,会获得一些相应的资源,这个类可以直接获取Spring配置文件中所有引用到的bean对象。如果bean类型实现了ApplicationContextAware,则调用setApplicationContext
      • 加载Spring配置文件时,如果Spring配置文件中所定义的Bean类实现了ApplicationContextAware 接口,那么在加载Spring配置文件时,会自动调用ApplicationContextAware 接口中的setApplicationContext,自动的将ApplicationContext注入进来

        Spring生命周期:

  • bean实例化与DI

    • 扫描XML/注释类/Java配置类中的bean定义
    • 创建bean实例
    • 注入bean依赖项(调用setter,为自动装配字段设置值)
  • 检查Spring Awareness

    • 如果bean类型实现了BeanNameAware,则调用setBeanName()
    • 如果bean类型实现了BeanClassLoaderAware,则调用setBeanClassLoader()
    • 如果bean类型实现了ApplicationContextAware,则调用setApplicationContext.
  • 创建bean生命周期的回调

    • 如果存在@PostConstruct注释,则调用它注释的方法
    • 如果bean类型实现了InitializingBean,则调用afterPropertiesSet()
    • 如果bean定义了init-method或@Bean(initMethod="..."),则调用该方法
  • 销毁bean生命周期的回调

    • 如果存在@PreDestroy注释,则使用它注释掉用方法
    • 如果bean类型实现了DispossableBean,则调用destroy()
    • 如果bean定义了(destroyMethod="...")则调用销毁方法。

准备春招

MySQL索引:BV1K64y1F76m

  1. 知识储备:
    • 局部性原理:程序和数据的访问都有聚集成群的倾向。包括空间和时间上的。
    • 磁盘预读:磁盘预读的长度为页的整数被,页是存储器的逻辑块,OS往往将主存和磁盘分为连续的,大小相等的块,每个存储块称为一页(页的大小通常为4K),主存和磁盘以页为单位交换数据。
  2. 索引是什么:
    • 索引是帮助MySQL高效获取数据的数据结构。
    • 索引存储在文件系统中
    • 索引的文件存储形式和存储引擎(innodb,myisam)有关
    • 索引文件的结构
      • hash
      • 二叉树
      • B树
      • B+树(实际使用)
      • 如果使用哈希表,需要将所有的数据文件添加到内存,耗费内存空间;实际工作中往往是非等值查询(比如范围查询),因此hash不太合适。
      • 无论是二叉树还是红黑树,都会因为树的深度过深而造成io次数变多,影响数据读取的效率。
      • 而使用B树(所有键值分布在整颗树中;搜索有可能在非叶子节点结束,在关键词全集内进行一次查找,性能逼近二分查找;每个节点最多有m个子树;根节点至少有2个子树;分支节点至少拥有m/2颗子树(除根节点和叶子节点外的都是分支节点;所有的叶子节点都在同一层,每个节点最多可以有m-1个key,且升序排列))
      • test.frm 和 test.ibd是InnoDB存储引擎。test.frm test.MYD(数据文件) test.MYI(索引文件)是MyISAM存储引擎。
      • B+Tree在BTree上进行了一定的优化:B+Tree每个节点可以包含更多的节点(可以降低树的高度,将数据范围变成多个区间)
    • MySQL的索引分类:
      1. 主键索引(主键是一种唯一性索引,必须指定primary key,每个表只能有一个主键)
      2. 唯一索引 (索引列的所有值只能出现一次,即必须唯一,值可以为空)
      3. 普通索引 基本的索引类型,值可以为空,没有唯一性的限制。
      4. 全文索引 全文索引的索引类型为FULLTEXT,全文索引可以在varchar,char,text类型的列上创建。
      5. 组合索引 多个列值组成一个索引,专门用于组合搜索。
        MyISAM:非聚簇索引,不支持事务,支持表锁,不支持行锁,支持外键,支持全文索引,适合大量select查询操作
        InnoDB:聚簇索引,支持事务,支持表锁,行锁和外键。5.6之后支持全文索引,适合袋狼insert,delete,update等修改操作。

索引优化

  • 各种排序(冒泡排序,选择排序,快排,堆排序,基数排序,桶排序)

  • Java中int的范围是-2^31到2^31-1. 因为Java中int是4个字节的,每一个字节是8位,总共32位,但是int是有符号的,符号位占用了一位,因此是2^31 -> 2^31-1

  • Java类加载机制。

    JVM类加载机制分为:加载,验证,准备,解析和初始化。
    加载:(查找并加载类的二进制数据),这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。获取的方式有Class文件中,jar包或者war包中。动态代理情况下还可以运行的时候计算生成。也可以有其他文件生成,比如JSP文件转化为Class类。
    验证阶段主要为了确保,Class文件的字节流中包含的信息是否符合当前虚拟机的要求。
    准备阶段(为类的静态变量进行初始化,分配空间并赋初值)。在方法区中分配这些变量所使用的内存空间。比如public static int v = 8080;实际上v在准备阶段后的初始值是0.v赋值为8080的put static指令是程序被编译后,存放在类构造器<client>方法中。如果再加上final关键字,则会再准备阶段,根据ConstantValue属性将v赋值为8080.
    解析阶段:将符号引用转化为直接引用。a="10",将"10"的地址放到a上。
    初始化:JVM对类进行初始化,对静态变量赋予正确值。静态代码块。
    类的生命周期(class文件->Java虚拟机内存->卸载)
    加载->验证->准备->解析->初始化->使用->卸载</client>

  • 类加载器:

    this.getClass.getClassLoader()就可以得到相应的类加载器。
    类加载器有三个,BootStrapClassLoader(C语言写的),ExtClassLoader,AppClassLoader。用户也可以继承,并自定义加载器。
    BootStrapClassLoader(C语言写的) JDK/JRE/LIB下的java.*
    ExtClassLoader 加载JDK/JRE/LIB/EXT javax.*
    AppClassLoader 用户自己写的类,不存在于用户自定义类下面。
    双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把请求委托给父加载器去完成,依次向上。如果加载过,则已经加载的类不会再加载。否则知道最顶端的BootStrapClassLoader.
    只有当父加载器再它的搜索范围中没有找到所需的类的时候,即无法完成该加载,才会依次向下,看看类加载器是否能够加载。能加载就加载。
    这样做是为了防止篡改系统级别的类。比如String类已经被BootStrapClassLoader加载过了,而最开始尝试加载的就是顶层的BootStrapClassLoader类。https://blog.csdn.net/codeyanbao/article/details/82875064

  • Cookie和Session辨析。

    • HTTP是无状态的,因此服务端需要记录用户的状态的时候,需要某种机制识别具体用户,这个机制称为session机制。服务器为用户创建特定的session,用于标识并跟踪这个用户,保存在服务器端,可以放在内存中,数据库中,文件中。
    • 创建了每个用户的唯一标识后,接下来是识别浏览器发送的请求是哪个用户的,这时候使用的是cookie机制,每次http请求,用户都会发送响应的cookie到服务器。大多数应用使用cookie 实现session跟踪功能。在创建session后,在cookie中记录一个session id。
    • 比如用户自动填写已经填过的表单
    • 总之,session是存储在服务器的一个数据结构,用来跟踪用户状态;cookie是客户端保存用户信息的一种机制,是session的一种实现方式。
  • Java中内存泄漏

    长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景

    Static Vector v = new Vector(10);
    for (int i = 1; i<100; i++)
    {
    Object o = new Object();
    v.add(o);
    o = null; // 尽管设置为null,但是对象还是可达的。
    }
    // 还有监听器,数据库连接,不正确的单例模式(单例生命周期长,持有外部引用),
  • C++菱形继承:

    两个子类继承一个父类,然后又有子类继承者两个子类,就会出现菱形继承的问题。baseClass会有两个,通过作用域调用,不是想要的结果。可以通过虚继承解决这个问题。