1、简单介绍下Java集合框架
主要有三种:List列表、Set集合、Map映射。
List是有序可重复的;
Set是无序不可重复的;
Map是以一种键值对的方式存储元素。
List主要有ArrayList和LinkedList,
ArrayList是基于数组实现的,其底层实现为一个长度动态增长的Object[]数组,因此具有访问快,增删慢的特点;
LinkedList是基于链表实现的,除了List接口外还实现了Deque接口,List接口还提供了特殊的迭代器ListIterator,ListIterator支持双向移动访问元素,因此插入删除快,查询慢;
另外还有Vector,类似于ArrayList,因为加了synchronized锁,所以是线程安全的;
另外还有CopyOnWriteArrayList也是线程安全的,也是目前较常用的。
Set主要有HashSet和TreeSet,
HashSet是通过Map中的HashMap实现的,是无序不可重复的;
TreeSet是通过Map的TreeMap实现的,它实现了SortedSet接口,因此它是有序的集合;
另外还有LinkedHashSet
Map主要有HashMap、TreeMap、LinkedHashMap,
TreeMap存储key-value节点时,需要根据key对节点进行排序,可以保证所有的key-value对处于有序状态;
HashMap用于快速访问;
LinkedHashMap能够保持元素插入的顺序,也提供快速访问的能力。
List和Set对比
• 1. Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。
• 2. Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>。
• 3. List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector> 。
2、==与equals的区别
== 基本类型比数值,引用类型比地址;
equals实现object接口,和==类似,但String和Integer等重写了equals方法,把他变成了值比较。
一般意义上我问是这样认为的:
1、 ==是判断两个变量或实例是不是指向同一个内存空间,equals是判断两个变量或实例所指向的内存空间的值是不是相同 ;
2、==是指对内存地址进行比较 , equals()是对字符串的内容进行比较;
3、==指引用是否相同, equals()指的是值是否相同;
其实并不是这样,equals在没有重写前和==的功能是一样的,引用类型也是比较内存地址,如果类重写了equals方法,比较的就是引用类型的值了,像 Integer,String 等类,都重写了equals方法。
3、数据独立性最高 的是 关系数据模型
关系数据库系统提供三级模式与二级映像,可以实现数据库的逻辑独立性与物理独立性,因而具有最高的数据独立性。
关系模型由关系数据结构、关系操作集合和关系完整性约束三部分组成的。
4、面向对象的理解
相对于面向过程来说,就是把事情简便化,比如洗衣服,面向过程就是打开洗衣机,放入洗衣粉,放入衣服,开始洗衣服,晾衣服等步骤;面向对象就是把衣服放到洗衣店,洗好之后去拿。
面向对象三大特性,封装、继承、多态。
封装就是封装类,比如javaBean的封装,给出getter、setter方法调用,就是不需要知道内部如何实现,只需要知道如何调用就好。
继承就是子类继承父类,共用的方法可以放到父类,让子类去调用,只能单个继承,可以多重继承。
多态,就是一个事物有多种形态,比如一个小鸟可以飞,飞机也可以飞,小鸟和飞机都可以继承飞这个方法,和继承对比,就是like,相似的事物有同样的状态。
5、jdk、jre、jvm
Jdk是java编译器
Jre是java运行器
Jvm是java虚拟机,
Jdk包含jre,jre包含jvm
Java源文件是如何执行的:
1、使用编辑器或IDE(集成开发环境)编写Java源文件.即Simple.java
2、程序必须编译为字节码文件,javac(Java编译器)编译源文件为Simple.class文件.
3、类文件可在任何平台/操作系统上由JVM(Java虚拟机)执行
4、JVM将字节码文件翻译为机器可以执行的机器码(0,1二进制)
什么是JVM:
JVM(JAVA虚拟机)是运行Java字节码的虚拟机,通过编译.java文件为.class文件得到字节码文件 . class文件包含JVM可以理解的字节码。
JVM使用许多高级技术为Java程序提供最佳性能,包括 先进的内存模型,GC(垃圾回收器)和adaptive optimizer(自适应优化器)。
JVM之所以称为虚拟机,是因为它提供了一个不依赖于底层操作系统和机器硬件体系结构的机器接口。这种与硬件和操作系统的独立性使得Java程序“写一次,到处运行”(write-once-run-anywhere)。
JVM内存区域被划分为多个部分来存储应用程序数据的特定部分。
Method Area(方法区): 存储像元数据,常量运行池,方法代码的结构。
Heap(堆区): 存储程序运行时被创建的所有对象
Stack(栈区): 存储本地变量(局部变量)和中间结果。所有的这些变量都是创建他们的线程的本地变量。每个线程有自己的Java Stack,在线程创建时该区被创建,所有这些本地变量也被称为:线程本地变量
PC register(程序计数器): 存储当前正在执行的的语句的物理内存地址。在Java中,每一个线程又有自己独立的PC register。
Java支持并可以使用本地代码。许多底层代码都是由C/C++编写。本地方法栈保存本地代码的指令。
6、双亲委派模型
就是当调用某个类方法时,会优先找该类的父节点,只有当上一级没有时,才使用其子类的方法。
类加载器
启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
扩展类加载器,开发者可以直接使用扩展类加载器。
应用程序类加载器,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
如果有必要,还可以加入自己定义的类加载器。
双亲委派模型工作过程:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。
因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
双亲委派模型好处:
使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。
避免了多份同样字节码的加载,内存是宝贵的。
双亲委派模型对于保证 Java 程序的稳定运作很重要,但它的实现却非常的简单,实现双亲委派的代码都集中在 java.lang.ClassLoader 的 loadClass() 方法中。
7、concurrentHashMap和hashtable
多线程一般用concurrentHashMap,不用HashTable,因为hashtable个个方法加锁,效率不够高。ConcurrentHashMap在1.7和1.8底层也不一样,1.7主要使用currentLock,1.8使用syconized锁和CAS。
乐观锁和悲观锁的概念:
比如线程A对某个变量进行修改,在这个修改期间,它持悲观心理,认为其他线程在这个期间,也有可能去修改这个变量,所以它就给变量加个锁,保证在它修改期间,别的线程没法去访问这个变量。这个锁就是悲观锁。悲观锁是重量级锁,代表对象synchronized关键字。
比如线程A对某个变量进行修改,在这个修改期间,它持乐观心理,认为其他线程在这个期间,不会去修改这个变量,所以它只在执行修改操作的时候,才会给变量加个锁。这个锁就是乐观锁。乐观锁是轻量级锁,代表对象CAS。
synchronized底层的原理:
每个对象内部都有一个monitor,monitor里面有一个计数器,从0开始的。
如果这个线程想获取monitor的锁,就先判断monitor的计数器是不是为0,如果为0,说明没人获取锁,这个线程就可以获取锁,然后对计数器加1;如果不为0,说明已经有其他线程已经获取了锁,这个线程就必须阻塞等待。
synchronized一般是对对象加锁,对类加锁也就是对类对象加锁。
如果使用了synchronized关键字,在底层编译后的jvm指令中,会有monitorenter和monitorexit两个指令。线程进入synchronized代码片段,执行monitorenter指令对monitor计数器加1,这样其他线程发现monitor的计数器不为0,就阻塞等待;
线程出synchronized代码片段,执行monitorexit指令就是对monitor计数器减1,这样其他线程发现monitor的计数器为0,就可以拿到锁,给monitor的计数器加1,然后执行业务逻辑了。
上面的是针对synchronized对对象、类加锁的底层原理。方法加锁不是通过monitor指令,而是通过ACC_SYNCHORNIZED关键字,判断方法是否同步。
CAS的原理:
CAS,Conmpare And Swap,英文翻译过来就是“比较和交换”。它的过程是3步,第一步是读值,第二步比较值,看值和自己刚刚读的一不一样,第3步是修改,如果值跟自己读的一样,就修改。
CAS最经典的实现类就是AtomicInteger。
比如2个线程同时要对AtomicInteger加1,A线程读旧值,是0,B线程也读旧值,是0,A这时执行CAS操作,比较值,发现值和刚刚自己读的一样,都是0,然后它修改值为1;B线程执行CAS操作,比较值,发现值和刚刚自己读的不一样,变成1了,它相当于又读了一遍旧值,将自己内存中的旧值改为1,然后继续执行CAS操作。
CAS在底层的硬件级别给你保证一定是原子的,同一时间只有一个线程可以执行CAS,先比较再设置,其他的线程的CAS同时间去执行此时会失败。
CAS的bug是会出现的问题 ABA 空循环。如果要解决ABA 问题,可以使用AtomicStampedReference类, 它内部用类似创建版本号的方式来解决 ABA 问题。
并发包下Lock锁和Synchronized对比:
我觉得二者的主要区别是以下四点;
1.首先synchronized是java内置关键字,是jvm层面,Lock是个java类,是jdk层面;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁,Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
8、如何实现一个ioc容器
配置文件配置包扫描路径
递归包扫描获取.class文件
反射、确定需要交给ioc管理的类
对需要注入的类进行依赖注入
一些其他的问题:
1、 springboot项目问题
拦截器,登录拦截,排除一些静态资源等
登录存session,退出删session
Model,往前台传数据,前台取数据通过${ }
Resultful风格 @PathVariable
重定向redirect 和请求转发 forward
Session和cookie
2、 单例模式
懒汉
饿汉
双重检查
线程安全与不安全问题
1、 HashMap和HashTable的区别
Hashtable是线程安全的,因为他的每个方法都加了锁,
Hashmap是线程不安全的,
但现在一般用currentHashMap,效率更高,运用了cas原理,另外加了锁。
Hashmap允许为空,hashtable不能为空
2、 抽象类和接口的区别
接口是更抽象的抽象类
接口不能有私有的变量
1、@postmapping、@getmapping、@requestMapping
@requestmapping是两者的父类
@postMapping 用于将post请求映射到特定处理程序方法 同 @requestMapping.POST
@getMapping 将get请求 还可以写成@requestMapping.GET
2、map遍历删除key或value为空的节点
1、 Git强拉公仓代码
Git fetch –all
Git reset –hard master(分支名)
Git pull
1、 多线程实现的三种方式
继承Thread类
实现runnable接口
实现callable接口
Thread 实现了runnable接口