JAVA基础
1. JAVA 中的几种基本数据类型是什么,各自占用多少字节。
数据类型 |
关键字 |
内置类 |
内存占用字节数 |
布尔型 |
boolean |
Boolean |
1字节 |
字符型 |
char |
Character |
2字节 |
字节型 |
byte |
Byte |
1字节 |
短整型 |
short |
Short |
2字节 |
整形 |
int |
Integer |
4字节 |
长整型 |
long |
Long |
8字节 |
单精度型 |
float |
Float |
4字节 |
双精度型 |
double |
Double |
8字节 |
2. String类为什么是final的?
1.final方法比非final快一些 2.final关键字提高了性能。JVM和Java应用都会缓存final变量。 3.final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。 4.使用final关键字,JVM会对方法、变量及类进行优化。 5.字符串是不可变的,所以在它创建的时候HashCode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键
3. String,Stringbuffer,StringBuilder的区别。
1.可变与不可变 String类中使用字符数组保存字符串,如下就是,因为有“final”修饰符,所以可以知道string对象是不可变的。 private final char value[]; StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也 是使用字符数组保存字符串,如下就是,可知这两种对象都是可变的。 char[] value; 2.是否多线程安全 String中的对象是不可变的,也就可以理解为常量,显然线程安全。 AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操 作,如expandCapacity、append、insert、indexOf等公共方法。 StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的 StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
4. ArrayList 和 LinkedList 有什么区别。
1. 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
2. 底层数据结构: Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构(注
意双向链表和双向循环链表的区别);
3. 插入和删除是否受元素位置的影响:① ArrayList 采用数组存储,② LinkedList 采用链表存储
4. 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问
就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。
5. 内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而
LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和
直接前驱以及数据)。
补充内容:RandomAccess接口
public interface RandomAccess{
}
查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过
是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。
在binarySearch()方法中,它要判断传入的list 是否RamdomAccess的实例,如果是,调用
indexedBinarySearch()方法,如果不是,那么调用iteratorBinarySearch()方法
ArraysList 实现了 RandomAccess 接口, 而 LinkedList 没有实现。为什么呢?我觉得还是和底层数据结
构有关!ArraysList 底层是数组,而 LinkedList 底层是链表。数组天然支持随机访问,时间复杂度为
O(1),所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n),
所以不支持快速随机访问。ArraysList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能。
RandomAccess 接口只是标识,并不是说 ArraysList 实现 RandomAccess 接口才具有快速随机访问功能
的!
ArrayList:默认长度是10 每次扩容是原来的1.5倍。如果在添加的时候远数组是空的,就直接给一个10的长度,否则的话就加一,当需要的长度大于原来数组长度的时候就需要扩容了
下面再总结一下 list 的遍历方式选择:
实现了RadmoAcces接口的list,优先选择普通for循环 ,其次foreach,
未实现RadmoAcces接口的ist, 优先选择iterator遍历(foreach遍历底层也是通过iterator实现的),大
size的数据,千万不要使用普通for循环
5. 讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当 new 的时候,他们的执行顺序。
父类静态数据 > 子类静态数据 > 父构造函数> 父字段 > 子构造函数 > 子字段
测试代码可以见:
6. 用过哪些 Map 类,都有什么区别,HashMap 是线程安全的吗,并发下使用的 Map 是什么,他们内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。
JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列。HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的时数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。 所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。 JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。 当 HashMap 中的元素个数超过数组大小 loadFactor时,就会进行数组扩容,loadFactor的默认值为 0.75,这 是一个折中的取值。也就是说,默认情况下,数组大小为 16,那么当 HashMap 中元素个数超过 16*0.75=12 的 时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常 消耗性能的操作,所以如果我们已经预知 HashMap 中元素的个数,那么预设元素的个数能够有效的提高 HashMap 的性能。 HashMap 包含如下几个构造器: HashMap():构建一个初始容量为 16,负载因子为 0.75 的 HashMap。 HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 的 HashMap
7. JAVA8 的 ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。
从Java 8开始,HashMap,ConcurrentHashMap和LinkedHashMap在处理频繁冲突时将使用平衡树来代替链表, 当同一hash桶中的元素数量超过特定的值便会由链表切换到平衡树,这会将get()方法的性能从O(n)提高到O(logn)。 ConcurrentHashMap适用于读者数量超过写者时,当写者数量大于等于读者时,CHM的性能是低于Hashtable和 synchronized Map的。这是因为当锁住了整个Map时,读操作要等待对同一部分执行写操作的线程结束。CHM适用 于做cache,在程序启动时初始化,之后可以被多个请求线程访问。正如Javadoc说明的那样,CHM是HashTable一 个很好的替代,但要记住,CHM的比HashTable的同步性稍弱
8. 有没有有顺序的Map 实现类,如果有,他们是怎么保证有序的。
在JAVA中,LRU的原生实现是JDK中LinkedHashMap。LinkedHashMap继承自HashMap 【实现原理】 简单说就是HashMap的每个节点做一个双向链表。 每次访问这个节点,就把该节点移动到双向链表的头部。满了以后,就从链表的尾部删除。 但是LinkedHashMap并是非线程安全(其实现中,双向链表的操作是没有任何线程安全的措施的)。 对于线程安全的HashMap,在JDK中有ConcurrentHashMap原生支持。
9. 抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,类可以实现多个接口么。
10.你能用Java覆盖静态方法吗?如果我在子类中创建相同的方法是否会编译时错误?
不能,但在子类中声明一个完全相同的方法不会编译错误,这称为隐藏在Java中的方法。
11. 讲讲你理解的 nio。他和 bio 的区别是啥,谈谈 reactor 模型。
Reactor模式首先是事件驱动的(基于NIO实现的),有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers;这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的Request Handle
12. 反射的原理,反射创建类实例的三种方式是什么。
//第一种表示方式--》实际在告诉我们任何一个类都有一个隐含的静态成员变量class Class class1 = Foo.class; //第二种表示方式 已经知道该类的对象通过getClass方法 Class class2 = foo1.getClass(); //第三种表达方式 class3 = Class.forName("com.imooc.reflect.Foo");
13. 反射中,Class.forName 和 ClassLoader 区别。
解释 在java中Class.forName()和ClassLoader都可以对类进行加载。ClassLoader就是遵循双亲委派模型最终调用 启动类加载器的类加载器,实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”,获取到二进制 流后放到JVM中。Class.forName()方法实际上也是调用的CLassLoader来实现的。 最后调用的方法是forName0这个方法,在这个forName0方法中的第二个参数被默认设置为了true,这个参数代 表是否对加载的类进行初始化,设置为true时会类进行初始化,代表会执行类中的静态代码块,以及对静态变 量的赋值等操作。 Class.forName(String className);这个方法的源码是 @CallerSensitive public static Class<?> forName(String className) throws ClassNotFoundException { Class<?> caller = Reflection.getCallerClass(); return forName0(className, true, ClassLoader.getClassLoader(caller), caller); } 应用场景 在我们熟悉的Spring框架中的IOC的实现就是使用的ClassLoader。 而在我们使用JDBC时通常是使用Class.forName()方法来加载数据库连接驱动。这是因为在JDBC规范中明确要 求Driver(数据库驱动)类必须向DriverManager注册自己。
14. 描述动态代理的几种实现方式,分别说出相应的优缺点。
Jdk cglib jdk底层是利用反射机制,需要基于接口方式,这是由于 Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); Cglib则是基于asm框架,实现了无反射机制进行代理,利用空间来换取了时间,代理效率高于jdk
15. jdk代理与cglib 实现的区别。
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理, cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类, 并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。 采用非常底层的字节码生成技术
16. 为什么CGlib 方式可以对接口实现代理。
可以,效率低
17. final的用途。
final类不能被继承,没有子类,final类中的方法默认是final的。 final方法不能被子类的方法覆盖,但可以被继承。 final成员变量表示常量,只能被赋值一次,赋值后值不再改变。 final不能用于修饰构造方法
18. 写出三种单例模式实现。
饿汉单例
public class EagerSingleton { //饿汉单例模式 //在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快 private static EagerSingleton instance = new EagerSingleton();//静态私有成员,已初始化 private EagerSingleton() { //私有构造函数 } public static EagerSingleton getInstance() //静态,不用同步(类加载时已初始化,不会有多线程的问题) { return instance; } }
懒汉单例
public class LazySingleton { private LazySingleton() { System.out.println("LazySingleton is create"); } private static LazySingleton instance = null; public static synchronized LazySingleton getInstance() { if (instance == null) instance = new LazySingleton(); return instance; } }
双检锁单例
/** 不可否认,synchronized关键字是可以保证单例,但是程序的性能却不容乐观, 原因在于getInstance()整个方法体都是同步的,这就限定了访问速度。 其实我们需要的仅仅是在首次初始化对象的时候需要同步, 对于之后的获取不需要同步锁。因此,可以做进一步的改进 **/ public class DoubleCheckedLock { private static volatile DoubleCheckedLock instance; public static DoubleCheckedLock getInstance() { if (instance == null) { //step1 synchronized (DoubleCheckedLock.class) { //step2 if (instance == null) { //step3 是不是多余? System.out.println("new DoubleCheckedLock"); instance=new DoubleCheckedLock(); //step4 } } } return instance; } }
静态内部类模式
/** * INSTANCE在创建过程中是线程安全的,所以说静态内部类形式的单例可保证线程安全,也能保证单例的唯一 * 性,同时也延迟了单例的实例化。 * **/ public class SingleTon{ private SingleTon(){} private static class SingleTonHoler{ private static SingleTon INSTANCE = new SingleTon(); } public static SingleTon getInstance(){ return SingleTonHoler.INSTANCE; } }
19. 如何在父类中为子类自动完成所有的 hashcode 和 equals 实现?这么做有何优劣。
20. 请结合 OO 设计理念,谈谈访问修饰符 public、private、protected、default 在应用设计中的作用。
21. 深拷贝和浅拷贝区别。
浅拷贝:①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新 的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的 数据。②对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会 进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成 员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。 深拷贝:不仅要复制对象的所有基本数据类型的成员变量值,还要为所有引用数据类型的成员变量申请存储空间, 并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整 个对象图进行拷贝! 如果某个属性被transient修饰,那么该属性就无法被拷贝了
22. 数组和链表数据结构描述,各自的时间复杂度。
23. error 和 exception 的区别,CheckedException,RuntimeException 的区别。
1.Error(错误)是系统中的错误,程序员是不能改变的和处理的,是在程序编译时出现的错误,只能通过修改程序 才能修正。一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类 错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。 2.Exception(异常)表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程 序恢复运行,而不应该随意终止异常。 Exception又分为两类 CheckedException:(编译时异常) 需要用try——catch显示的捕获,对于可恢复的异常使用CheckedException。 UnCheckedException(RuntimeException):(运行时异常)不需要捕获,对于程序错误(不可恢 复)的异常使用RuntimeException。
24. 请列出 5 个运行时异常。
NullPointerException - 空指针引用异常
ClassCastException - 类型强制转换异常。
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException - 下标越界异常
NegativeArraySizeException - 创建一个大小为负数的数组错误异常
NumberFormatException - 数字格式异常
SecurityException - 安全异常
UnsupportedOperationException - 不支持的操作异常
25. 在自己的代码中,如果创建一个 java.lang.String 对象,这个对象是否可以被类加载器加载?为什么。
当Java程序请求加载器loader1加载Sample类时,loader1首先委托自己的父加载器去加载Sample类,若父加载 器能加载,则由父加载器完成加载任务,否则才由加载器loader1本身加载Sample类。 加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一 次。而加载的顺序是自顶向下,也就是说当发现这个类没有的时候会先去让自己的父类去加载,父类没有再让儿子 去加载 那么在这个例子中我们自己写的String应该是被Bootstrap ClassLoader加载了,所以App ClassLoader就不会再去加载我们写的String类了,导致我们写的String类是没有被加载的。
https://blog.csdn.net/u013206465/article/details/47170253
26. 说一说你对 java.lang.Object 对象中 hashCode 和 equals 方法的理解。在什么场景下需要重新实现这两个方法。
27. 在 jdk1.5 中,引入了泛型,泛型的存在是用来解决什么问题。
28. 这样的 a.hashcode() 有什么用,与 a.equals(b)有什么关系。
将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相 等,如果不相等直接将该对象放入集合中。 如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判 断不相等,直接将该元素放入到集合中,否则不放入。 equals与hashcode的关系 equals相等两个对象,则hashcode一定要相等。但是hashcode相等的两个对象不一定equals相等。
详情原理请看
29. 有没有可能 2 个不相等的对象,有相同的 hashcode。
有
30. Java 中的 HashMap和 HashSet的区别,HashSet内部是如何工作的。
对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,我 们应该为保存到 HashSet 中的对象覆盖 hashCode() 和 equals()
http://wiki.jikexueyuan.com/project/java-collection/hashset.html
31. 什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决。
32.Java中按值传递与按引用传递的区别
值传递:(形式参数类型是基本数据类型):方法调用时,实际参数把它的值传递给对应的形式参数,形式参数只是 用实际参数的值初始化自己的存储单元内容,是两个不同的存储单元,所以方法执行中形式参数值的改变不影响实 际参数的值。 引用传递:(形式参数类型是引用数据类型参数):也称为传地址。方法调用时,实际参数是对象(或数组),这时实 际参数与形式参数指向同一个地址,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,这个结果在 方法结束后被保留了下来,所以方法执行中形式参数的改变将会影响实际参数。
34创建String的特性
字符串类(Java.lang.String)是Java中使用最多的类,也是最为特殊的一个类,很多时候,我们对它既熟悉 又陌生。在很多面试题中经常用String大做文章,只要掌握了String特性,对付它们就不再是困难了。 1、从根本上认识java.lang.String类和String池 首先,我建议先看看String类的源码实现,这是从本质上认识String类的根本出发点。 从源码中可以看到: String类是final的,不可被继承。public final class String。 String类是的本质是字符数组char[], 并且其值不可改变。private final char value[]; 然后打开String类的API文档,从API中可以发现: String类对象有个特殊的创建的方式,就是直接指定比如String x = "abc","abc"就表示一个字符串对象。 而x是"abc"对象的地址,也叫做"abc"对象的引用。 String对象可以通过“+”串联。串联后会生成新的字符串。也可以通过concat()来串联,这个后面会讲述。 Java运行时会维护一个String Pool(String池),JavaDoc翻译很模糊“字符串缓冲区”。String池用来存放 运行时中产生的各种字符串,并且池中的字符串的内容不重复。而一般对象不存在这个缓冲池,并且创建的对象 仅仅存在于方法的堆栈区。 2、String对象的创建的特性 String对象的创建也很讲究,关键是要明白其原理。 特性1: 当使用任何方式来创建一个字符串对象s时,Java运行时(运行中JVM)会拿着这个字符串的内容在String池中 找是否存在内容相同的字符串对象,如果不存在,则在池中创建一个字符串s,否则,不在池中添加。 特性2: Java中,只要使用new关键字来创建对象,则一定会(在堆区或栈区)创建一个新的对象。 特性3: 使用直接指定、使用纯字符串串联或者在编译期间可以确定结果的变量表达式来创建String对象,则仅仅会检 查维护String池中的字符串,池中没有就在池中创建一个,有则罢了!但绝不会在堆栈区再去创建该String对 象; 特性4: 使用包含编译期间无法确定结果的变量的表达式来创建String对象,则不仅会检查维护String池,而且还会在堆栈区创建一个String对象 示例: 1、 直接指定,例如:下面代码运行结果为true; String str1 = "abc"; String str2 = "abc"; System.out.println(str1 == str2); 2、 使用纯字符串串联,例如:下面代码运行结果为true; String str1 = "abc"; String str2 = "ab" + "c"; System.out.println(str1 == str2); 3、 在编译期间可以确定结果的变量表达式,例如:下面代码运行结果为true。 final String str1 = "c"; //final类型的变量在编译时当常量处理 String str2 = "ab" + "c"; String str3 = "ab" + str1; System.out.println(str2==str3); 4、普通变量表达式进行创建字符串,例如:下面代码运行结果为false; String str1 = "c"; String str2 = "ab" + "c"; String str3 = "ab" + str1; System.out.println(str2==str3);
示例代码:
33 String的Intern方法有哪些应用
Jdk6 以及以前的版本中,字符串的常量池是放在堆的Perm区的,Perm区是一个类静态的区域(方法区),主要存储 一些加载类的信息,常量池,方法片段等内容,默认大小只有4m jdk7 主要对 intern 操作和常量池做了以下改动 1.将String常量池从Perm区移动到了Java Heap区 2.String#intern方法时,如果存在常量池的对象,会直接保存对象的引用,而不会重新创建对象。 String对象的实例调用intern方法后,可以让JVM检查常量池,如果没有实例的value属性对应的字符串序列比 如"123"(注意是检查字符串序列而不是检查实例本身),就将本实例放入常量池,如果有当前实例的value属性对 应的字符串序列"123"在常量池中存在,则返回常量池中"123"对应的实例的引用而不是当前实例的引用,即使当 前实例的value也是"123"。 可以使用string的intern方法来对同一个用户id(字符串)进行加锁,从而确同一个用户的操作的同步的