41、Mybatis都有哪些Executor执行器?它们之间的区别是什么?
Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String,Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
42、Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;毕竟namespace不是必须的,只是最佳实践而已。
原因就是namespace+id是作为Map<String, MappedStatement>的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
43、查询及删除重复记录的SQL语句
1、查找表中多余的重复记录,重复记录是根据单个字段(Id)来判断
select * from 表 where Idin (select Id from 表group byId having count(Id) > 1)
2、删除表中多余的重复记录,重复记录是根据单个字段(Id)来判断,只留有rowid最小的记录
DELETE from 表 WHERE (id)IN ( SELECT id FROM 表GROUP BY id HAVING COUNT(id) > 1) AND ROWID NOT IN (SELECT MIN(ROWID) FROM 表 GROUP BYid HAVING COUNT(*) > 1);
3、查找表中多余的重复记录(多个字段)
select * from 表 a where(a.Id,a.seq) in(select Id,seq from 表 group by Id,seq having count(*)> 1)
4、删除表中多余的重复记录(多个字段),只留有rowid最小的记录
delete from 表 a where(a.Id,a.seq) in (select Id,seq from 表 group by Id,seq having count(*)> 1) and rowid not in (select min(rowid) from 表 group byId,seq having count(*)>1)
5、查找表中多余的重复记录(多个字段),不包含rowid最小的记录
select * from 表 a where(a.Id,a.seq) in (select Id,seq from 表 group by Id,seq having count(*)> 1) and rowid not in (select min(rowid) from 表 group byId,seq having count(*)>1)
1.Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的
类装载器所做的工作实质是把类文件从硬盘读取到内存中
2.java中的类大致分为三种:
1.系统类
2.扩展类
3.由程序员自定义的类
3.类装载方式,有两种
1.隐式装载, 程序在运行过程中当碰到通过new等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,
2.显式装载, 通过class.forname()等方法,显式加载需要的类
隐式加载与显式加载的区别: 两者本质是一样的!都是加载到JVM虚拟机中。
4.类加载的动态性体现
一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再
运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现
5.java类装载器
Java中的类装载器实质上也是类,功能是把类载入jvm中,值得注意的是jvm的类装载器并不是一个,而是三个,层次结构如下:
Bootstrap Loader - 负责加载系统类
|
- - ExtClassLoader - 负责加载扩展类
|
- -AppClassLoader - 负责加载应用类
为什么要有三个类加载器,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型,下面会谈到该模型
另一种
JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。
由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。
类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。 从Java2(JDK1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。下面是关于几个类加载器的说明:
Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。
6.类加载器之间是如何协调工作的
前面说了,java中有三个类加载器,问题就来了,碰到一个类需要加载时,它们之间是如何协调工作的,即java是如何区分一个类该由哪个类加载器来完成呢。
在这里java采用了委托模型机制,这个机制简单来讲,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent找不到,那么才由自己依照自己的搜索路径搜索类”,注意喔,这句话具有递归性
45、Char型变量能否存储一个中文字?为什么?
char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个char类型占2个字节(16比特),所以放一个中文是没问题的。
46、简述synchronized和java.util.concurent.locks.Lock的异同点
主要相同点:Lock 能完成 synchronized 所实现的所有功能;
主要不同点:Lock 有比 synchronized 更精确的线程语义和更好的性能。
synchronized 会自动释放锁,而 Lock 一定要求程序员手工释放,并且必须在
finally 从句中释放。
1.synchronized用在方法和代码块的区别?
a. 可以只对需要同步的使用
b.与wait(),notify()和notifyall()方法使用比较方便
2.wait()
a。释放持有的对象锁,线程进入等待池,释放cpu,其他正在等待的线程可以获得锁,而sleep方法,线程会休眠一段时间,线程不会释放锁。
3.ReentrantLock
还包括了中断锁等待和定时锁等待,
在并发量小的时候,用synchronize是比较好的选择,并发量大的时候用Lock。
synchronize是自动释放锁,Lock是主动释放锁
Lock可以设定所等待的时间,
有些操作不会发生冲突现象,需要用Lock解决,比如同时读文件。
1.clone()
a.是一个native方法,效率比非native高
b.是protected修饰的,要用他必须继承object,默认都是继承object的
c.返回是一个object对象,需要强制转换为需要的对象。
2.getClass()
a.可以获得一个类的定义信息,根据这对象的方法可以得到类名,方法名,字段,方法参数,方法返回类型
3.finalize
a.有对象并显示的调用system.gc()就会被调用
4.equals
a. 在object中是比较指针的地址,
b.两个对象如果equals相等,那么hashcode一定相等,如果hashcode相等,equals不一定相等
5.hashcode
a. 返回对象的地址值
6.toString
a。打印字符串的方法,一般重写这个方法
7.notify
a. 从对象的等待池中移走任意一个线程到锁标志等待池中,只有锁标志等待池中的线程获取锁标志,如果锁标志等待池中没有线程,则notify不起作用。
8.notifyall
a。从对象等待池中移走所有等待那个对象的线程
9.wait
a.使当前线程暂停执行并释放对象锁标示,让其他线程进入同步块,当前线程进入对象等待池中。
1.i=i+1
a。读取右i的地址
b,i=1
c.读取左i的地址
d. 值赋给左i
2.i+=1
a。读取左i的地址
b。i+1
c。值给i
3.i++
a。读取右i的地址
b。值加1
1主要是为了“效率” 和 “安全性” 的缘故。若 String允许被继承, 由于它的高度被使用率, 可能会降低程序的性能,所以String被定义成final。
2、不允许其他类继承。这个应该不是最终原因,但这里权且也当成是一个原因。
3、 String类中的成员属性也几乎都设计成了privatefinal的,这样String就被设计成一个不变类,这样有助于共享,提高性能。可以将字符串对象保存在字符串常量池中以供与字面值相同字符串对象共 享。如果String对象是可变的,那就不能这样共享,因为一旦对某一个String类型变量引用的对象值改变,将同时改变一起共享字符串对象的其他String类型变量所引用的对象的值。
4、String被设计为不变类,其中的offset,value[]都被设计成privatefinal的,这样在多线程时,对String对象的访问是可以保证安全的。java程序语言的许多特性依赖于不可变的String对象。
一、HashMap概述
HashMap基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap。
Map map = Collections.synchronizedMap(new HashMap());
二、HashMap的数据结构
HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。学过数据结构的同学都知道,解决hash冲突的方法有很多,HashMap底层是通过链表来解决hash冲突的。
图中,紫色部分即代表哈希表,也称为哈希数组,数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中。
我们看看HashMap中Entry类的代码:
/** Entry是单向链表。 * 它是 “HashMap链式存储法”对应的链表。 *它实现了Map.Entry 接口,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数 **/ static class Entry<K, V> implements Map.Entry<K, V> { final K key; V value; // 指向下一个节点 Entry<K, V> next; final int hash; // 构造函数。 // 输入参数包括"哈希值(h)", "键(k)", "值(v)", "下一节点(n)" Entry(int h, K k, V v, Entry<K, V> n) { value = v; next = n; key = k; hash = h; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } // 判断两个Entry是否相等 // 若两个Entry的“key”和“value”都相等,则返回true。 // 否则,返回false public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry) o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } // 实现hashCode() public final int hashCode() { return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); } public final String toString() { return getKey() + "=" + getValue(); } // 当向HashMap中添加元素时,绘调用recordAccess()。 // 这里不做任何处理 void recordAccess(HashMap<K, V> m) { } // 当从HashMap中删除元素时,绘调用recordRemoval()。 // 这里不做任何处理 void recordRemoval(HashMap<K, V> m) { } }
HashMap其实就是一个Entry数组,Entry对象中包含了键和值,其中next也是一个Entry对象,它就是用来处理hash冲突的,形成一个链表。
三、HashMap源码分析
先看看HashMap类中的一些关键属性:
transient Entry[] table;//存储元素的实体数组
transient int size;//存放元素的个数
int threshold; //临界值 当实际大小超过临界值时,会进行扩容threshold = 加载因子*容量
final floatloadFactor; //加载因子
transient int modCount;//被修改的次数
其中loadFactor加载因子是表示Hsah表中元素的填满的程度.
若:加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.链表长度会越来越长,查找效率降低。
反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了.表中的数据将过于稀疏(很多空间还没用,就开始扩容了)
冲突的机会越大,则查找的成本越高.
因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷. 这种平衡与折衷本质上是数据结构中有名的"时-空"矛盾的平衡与折衷.
如果机器内存足够,并且想要提高查询速度的话可以将加载因子设置小一点;相反如果机器内存紧张,并且对查询速度没有什么要求的话可以将加载因子设置大一点。不过一般我们都不用去设置它,让它取默认值0.75就好了。
下面看看HashMap的几个构造方法:
public HashMap(int initialCapacity, float loadFactor) { //确保数字合法 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // Find a power of 2 >= initialCapacity int capacity = 1; //初始容量 while (capacity < initialCapacity) //确保容量为2的n次幂,使capacity为大于initialCapacity的最小的2的n次幂 capacity <<= 1; this.loadFactor = loadFactor; threshold = (int) (capacity * loadFactor); table = new Entry[capacity]; init(); } public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; threshold = (int) (DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); table = new Entry[DEFAULT_INITIAL_CAPACITY]; init(); }
我们可以看到在构造HashMap的时候如果我们指定了加载因子和初始容量的话就调用第一个构造方法,否则的话就是用默认的。默认初始容量为16,默认加载因子为0.75。我们可以看到上面代码中13-15行,这段代码的作用是确保容量为2的n次幂,使capacity为大于initialCapacity的最小的2的n次幂,至于为什么要把容量设置为2的n次幂,我们等下再看。
重点分析下HashMap中用的最多的两个方法put和get
下面看看HashMap存储数据的过程是怎样的,首先看看HashMap的put方法:
public V put (K key , V value) {// 若 “key 为 null” ,则将该键值对添加到 table[0] 中。
if (key == null )
return putForNullKey(value) ;
// 若 “key 不为 null” ,则计算该 key 的哈希值,然后将其添加到该哈希值对应的链表中。
int hash = hash(key.hashCode()) ;
// 搜索指定 hash 值在对应 table 中的索引
int i = indexFor(hash , table.length) ;
// 循环遍历 Entry 数组 , 若 “ 该 key” 对应的键值对已经存在,则用新的 value 取代旧的 value 。然后退出!
for (Entry<K , V> e = table[i] ; e != null; e = e.next) {
Object k ;
if (e.hash == hash &&((k = e.key) == key || key.equals(k))) { // 如果 key 相同则覆盖并返回旧值
V oldValue = e.value ;
e.value = value ;
e.recordAccess( this ) ;
return oldValue ;
}
}
// 修改次数 +1
modCount++ ;
// 将 key-value 添加到 table[i] 处
addEntry(hash , key , value , i) ;
return null;