12、Class.forName和ClassLoader.loadClass的区别
答:(1)class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块和静态变量。
Class.forName(name,initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象。
(2)classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容和和静态变量,只有在newInstance才会去执行static块和静态变量
public class Line {
static{
System.out.println("执行静态块");
}
public static String aa = getString();
private static String getString() {
System.out.println("执行静态方法");
return "静态方法";
}
public Line() {
System.out.println("执行构造函数");
}
}
public class ClassloaderAndFornameTest {
/**
* Class.forName默认会执行static块和静态变量
*/
@Test
public void testClassForName1() {
try {
Class.forName(Line.class.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* Class.forName(name, initialize, loader)带参函数也可控制是否加载static块和静态变量。
*/
@Test
public void testClassForName2() {
try {
Class.forName(Line.class.getName(), false, ClassLoader.getSystemClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* ClassLoader不会执行static块和和静态变量
*/
@Test
public void testClassLoader() {
try {
ClassloaderAndFornameTest.class.getClassLoader().loadClass(Line.class.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
13、ClassLoader加载类的原理
双亲委派模型:在java.lang.ClassLoader的loadClass()方法中,先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,则抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。
为什么要使用这种双亲委托模式?(1)避免重复加载(2)考虑到安全因素
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{
// 首先检查该name指定的class是否有被加载
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//如果parent不为null,则调用parent的loadClass进行加载
c = parent.loadClass(name, false);
}else{
//parent为null,则调用BootstrapClassLoader进行加载
c = findBootstrapClass0(name);
}
}catch(ClassNotFoundException e) {
//如果仍然无法加载成功,则调用自身的findClass进行加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
1)首先当前线程的类加载器去加载线程中的第一个类(假设为类A)。
注:当前线程的类加载器可以通过Thread类的getContextClassLoader()获得,也可以通过setContextClassLoader()自己设置类加载器。
2)如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B。
3)还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
自定义ClassLoader需要继承ClassLoader,重写findClass方法,不鼓励重写loadClass
User user = new User();
System.out.println(user.getClass().getClassLoader().toString()); // AppClassLoader
System.out.println(Thread.currentThread().getContextClassLoader().toString()); // AppClassLoader
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("==》 " + Thread.currentThread().getContextClassLoader().toString());
// AppClassLoader
}
}).start();
MyClassLoader myLoader = new MyClassLoader();
Person p = (Person) myLoader.loadClass("com.sample.model.Person").newInstance();
System.out.println(p.getClass().getClassLoader().toString()); // AppClassLoader
补充:
类加载器与Web容器
对于运行在 Java EE容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。
绝大多数情况下,Web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则:
(1)每个 Web 应用自己的 Java 类文件和使用的库的 jar 包,分别放在 WEB-INF/classes和 WEB-INF/lib目录下面。
(2)多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下面。
(3)当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。
两个jar包中包含完全相同的包名和类名的加载问题
(1)自定义两个jar包,其中包含相同包名和类名
与export的导入顺序有关。只会加载第一个,并且运行正常。
(2)自定义jar和jdk包,其中包含相同的包名和类名
与export的导入顺序有关。同样是只会加载第一个,但是如果加载自定义的jar运行会报错。加载 jdk正常。
Tomcat类加载机制,参考:https://www.cnblogs.com/xing901022/p/4574961.html
当应用需要到某个类时,则会按照下面的顺序进行类加载:
1 使用bootstrap引导类加载器加载
2 使用system系统类加载器加载
3 使用应用类加载器在WEB-INF/classes中加载
4 使用应用类加载器在WEB-INF/lib中加载
5 使用common类加载器在CATALINA_HOME/lib中加载
14、TreeMap和LinkedHashMap如何保证顺序?
答:(1)LinkedHashMap
继承HashMap、底层使用哈希表与双向链表来保存所有元素
LinkedHashMap采用的hash算法和HashMap相同,但是它重新定义了数组中保存的元素Entry,该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了双向链接列表。
/**
* LinkedHashMap的Entry元素。
* 继承HashMap的Entry元素,又保存了其上一个元素before和下一个元素after的引用。
*/
private static class Entry<K,V> extends HashMap.Entry<K,V> {
Entry<K,V> before, after;
……
}
(2)TreeMap
可参考:http://blog.youkuaiyun.com/chenssy/article/details/26668941
TreeMap的实现是红黑树算法的实现,是复杂而高效的,其检索效率O(log n)
红黑树可参考:http://www.cnblogs.com/yangecnu/p/Introduce-Red-Black-Tree.html
15、LinkedHashMap其他问题
答:(1)LinkedHashMap的accessOrder的作用
accessOrder false: 基于插入顺序(默认) true: 基于访问顺序
基于访问的顺序,get一个元素后,这个元素被加到最后(使用了LRU 最近最少被使用的调度算法)
内部实现:将访问过的元素指向tail。也就是before、after起作用
(2)LinkedHashMap如何利用LRU算法实现LRU缓存
accessOrder设置为true
//实现LRU的关键方法,如果map里面的元素个数大于了缓存最大容量,则删除链表的顶端元素
@Override
public boolean removeEldestEntry(Map.Entry<K, V> eldest){
return size()>capacity;
}
16、JDK7以上的Collections.sort(list, c)的Timsort排序算法
可参考:http://blog.sina.com.cn/s/blog_8e6f1b330101h7fa.html有具体算法过程。
答:Timsort算法是结合了合并排序(merge sort)和插入排序(insertion sort)而得出的排序算法,最大的特点就是充分利用数组中已经存在顺序。
Timsort的核心过程
TimSort 算法为了减少对升序部分的回溯和对降序部分的性能倒退,将输入按其升序和降序特点进行了分区。排序的输入的单位不是一个个单独的数字,而是一个个的块-分区。其中每一个分区叫一个run。针对这些 run 序列,每次拿一个 run 出来按规则进行合并。每次合并会将两个 run合并成一个 run。合并的结果保存到栈中。合并直到消耗掉所有的 run,这时将栈上剩余的 run合并到只剩一个 run 为止。这时这个仅剩的 run 便是排好序的结果。
综上述过程,Timsort算法的过程包括
(0)如何数组长度小于某个值,直接用二分插入排序算法
(1)找到各个run,并入栈
(2)按规则合并run
Java7对对象进行排序,没有采用快速排序,是因为快速排序是不稳定的,而Timsort是稳定的。
Timsort是稳定的算法,当待排序的数组中已经有排序好的数,它的时间复杂度会小于n logn。与其他合并排序一样,Timesrot是稳定的排序算法,最坏时间复杂度是O(n log n)。在最坏情况下,Timsort算法需要的临时空间是n/2,在最好情况下,它只需要一个很小的临时存储空间。
可参考:http://blog.sina.com.cn/s/blog_8e6f1b330101h7fa.html
17、Comparator使用需要注意什么?
答:Comparator用于列表排序
JDK6版本,使用的是合并排序;JDK7以上,默认使用的是Timsort排序,Comparator要满足三个条件(自反性、传递性、对称性)
JDK7 版本以上,Comparator 要满足自反性,传递性,对称性,不然 Arrays.sort,
Collections.sort 会报IllegalArgumentException 异常:Comparison method violates its general contract!
Comparator要满足三个条件:
1) 自反性:x,y 的比较结果和 y,x 的比较结果相反。
2) 传递性:x>y,y>z,则 x>z。
3) 对称性:x=y,则 x,z 比较结果和 y,z 比较结果相同。
// 报“Comparison method violates its general contract! ”的例子
//参考:http://blog.sina.com.cn/s/blog_8e6f1b330101h7fa.html
public class ReproJava7Exception {
public static void main(String[] args) {
int[] sample = new int[]
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,1,0,-2,0,0,0,0};
List list = new ArrayList();
for (int i : sample)
list.add(i);
// use the native TimSort in JDK 7
Collections.sort(list, new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
// miss the o1 = o2 case on purpose
return o1 > o2 ? 1 : -1;
}
});
}
}