面试

反射的概述

Java反射的原理:java类的执行需要经历以下过程,

编译:.java文件编译后生成.class字节码文件
加载:类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例
连接:细分三步
验证:格式(class文件规范) 语义(final类是否有子类) 操作
准备:静态变量赋初值和内存空间,final修饰的内存空间直接赋原值,此处不是用户指定的初值。
解析:符号引用转化为直接引用,分配地址
初始化:有父类先初始化父类,然后初始化自己;将static修饰代码执行一遍,如果是静态变量,则用用户指定值覆盖原有初值;如果是代码块,则执行一遍操作。

Java的反射就是利用上面第二步加载到jvm中的.class文件来进行操作的。.class文件中包含java类的所有信息,当你不知道某个类具体信息时,可以使用反射获取class,然后进行各种操作。

Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。总结说:反射就是把java类中的各种成分映射成一个个的Java对象,并且可以进行操作。

线程的创建方式?你喜欢哪种?为什么

方式一:继承Thread类

方式二:实现Runnable接口

方式三:实现Callable接口 --JDK5.0新增

@PathVariable @RequestParam 和 @RequestBody 的区别

@RequestParam

1.用于将请求参数区数据映射到功能处理方法的参数上,接受的参数是来自requestHeader中,即请求头,通常用于GET请求,也可以用于post,delete等请求。
2.用来处理Content-Type: 为 application/x-www-form-urlencoded编码的内容。
3.@RequestParam 有三个属性:
(1)value:请求参数名(必须配置)
(2)required:是否必需,默认 true,即请求中必须包含该参数,如果没有包含,将会抛出异常(可选配置)
(3)defaultValue:默认值,如果设置了该值,required 将自动设为 false,无论你是否配置了required,配置了什么值,都是 false(可选配置)

@RequestBody

1.@RequestBody 映射请求到方法体上,接受的参数是来自requestBody中,即请求体。
2.用来处理非Content-Type: 为 application/x-www-form-urlencoded编码的内容。例如:application/json、application/xml等类型的数据。
3.@RequestBody注解常用于接收json格式的数据,并将其转换成对应的数据类型。

@PathVariable

1.@PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中,获取请求路径中的变量作为参数

  1. 带占位符的 URL 是 Spring3.0 新增的功能

为什么HashMap线程不安全?(jdk7版本)

HashMap会进行resize操作,在resize操作的时候会造成线程不安全。下面将举两个可能出现线程不安全的地方。

1、put的时候导致的多线程数据不一致。 这个问题比较好想象,比如有两个线程A和B,首先A希望插入一个key-value对到HashMap中,首先计算记录所要落到的桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程A的时间片用完了,而此时线程B被调度得以执行,和线程A一样执行,只不过线程B成功将记录插到了桶里面,假设线程A插入的记录计算出来的桶索引和线程B要插入的记录计算出来的桶索引是一样的,那么当线程B成功插入之后,线程A再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程B插入的记录,这样线程B插入的记录就凭空消失了,造成了数据不一致的行为。

下面的代码是resize的核心内容:

这是jdk7的实现方式,jdk8不是这样的。

// 这个方法的功能是将原来的记录重新计算在新桶的位置,然后迁移过去。
void transfer(Entry[] newTable, boolean rehash) {  
        int newCapacity = newTable.length;  
        for (Entry e : table) {  
            while(null != e) {  
                Entry next = e.next;           
                if (rehash) {  
                    e.hash = null == e.key ? 0 : hash(e.key);  
                }  
                int i = indexFor(e.hash, newCapacity);   
                e.next = newTable[i];  
                newTable[i] = e;  
                e = next;  
            } 
        }  
    }  

为什么HashMap是不安全的?(jdk8版本)

根据上面JDK1.7出现的问题,在JDK1.8中已经得到了很好的解决,如果你去阅读1.8的源码会发现找不到transfer函数,因为JDK1.8直接在resize函数中完成了数据迁移。另外说一句,JDK1.8在进行元素插入时使用的是尾插法。

为什么说JDK1.8会出现数据覆盖的情况喃,我们来看一下下面这段JDK1.8中的put操作代码:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node[] tab; Node p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有hash碰撞则直接插入元素
            tab[i] = newNode(hash, key, value, null);
        else {
            Node e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

其中第六行代码是判断是否出现hash碰撞,假设两个线程A、B都在进行put操作,并且hash函数计算出的插入下标是相同的,当线程A执行完第六行代码后由于时间片耗尽导致被挂起,而线程B得到时间片后在该下标处插入了元素,完成了正常的插入,然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程B插入的数据被线程A覆盖了,从而线程不安全。

总结: HashMap的线程不安全主要体现在下面两个方面:

1.在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况。

2.在JDK1.8中,在并发执行put操作时会发生数据覆盖的情况。

单点登录业务实现原理

实现步骤:
1.用户输入用户名和密码之后点击登录按钮开始进行登录操作.
2.JT-WEB向JT-SSO发送请求,完成数据校验
3.当JT-SSO获取数据信息之后,完成用户的校验,如果校验通过则将用户信息转化为json.并且动态生成UUID.将数据保存到redis中. 并且返回值uuid.
如果校验不存在时,直接返回"不存在"即可.
4.JT-SSO将数据返回给JT-WEB服务器.
5.如果登录成功,则将用户UUID保存到客户端的cookie中.

索引是什么?有什么作用以及优缺点?以及常见的几种索引

1、索引是对数据库表中一或多个列的值进行排序的结构,是帮助MySQL高效获取数据的数据结构

2、索引就是加快检索表中数据的方法。数据库的索引类似于书籍的索引。在书籍中,索引允许用户不必翻阅完整个书就能迅速地找到所需要的信息。在数据库中,索引也允许数据库程序迅速地找到表中的数据,而不必扫描整个数据库。

MySQL数据库几个基本的索引类型:普通索引、唯一索引、主键索引、全文索引

1、索引加快数据库的检索速度
2、索引降低了插入、删除、修改等维护任务的速度
3、唯一索引可以确保每一行数据的唯一性
4、通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能、索引需要占物理和数据空间

主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。
唯一索引: 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。
普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值。
全文索引: 是目前搜索引擎使用的一种关键技术。

为什么redis这么快

  1. Redis是完全基于内存的数据库
  2. 处理网络请求使用的是单线程,避免了不必要的上下文切换和锁的竞争维护。
  3. 使用了I/O多路复用模型。

完全基于内存

为什么要用完全呢。因为像mysql这样的传统关系型数据库是存储在硬盘的,那么硬盘的性能和瓶颈将会影响到数据库。

单线程

需要注意的是,这里的单线程指的是,Redis处理网络请求的时候只有一个线程,而不是整个Redis服务是单线程的。

I/O多路复用模型

  • 传统多进程并发模型: 每监听到一个Socket连接就会分配一个线程处理
  • 多路复用模型: 单个线程,通过记录跟踪每一个Socket连接的I/O的状态来同时管理多个I/O流。
    这里的I/O指的是网络I/O,多路指的是多个网络连接,复用指的是复用一个线程

结合Redis:

  1. 在Redis中的I/O多路复用程序会监听多个客户端连接的Socket
  2. 每当有客户端通过Socket流向Redis发送请求进行操作时,I/O多路复用程序会将其放入一个队列中。
  3. 同时I/O多路复用程序会同步有序、每次传送一个任务给处理器处理。
  4. I/O多路复用程序会在上一个请求处理完毕后再继续分派下一个任务。(同步)

你可能感兴趣的:(java)