目录
1、Java语言有哪些特点
2、面向对象和面向过程的区别
3、八种基本数据类型的大小
4、标识符命名规则
5、Java 关键字
6、访问控制
7、instanceof 关键字的作用
8、final 关键字的作用
9、static 关键字作用
10、transient 关键字的作用
11、try catch finally return
12、Java 的四种引用(强弱软虚)
13、泛型常用特点
14、Java 自动装箱与拆箱
15、a=a+b与a+=b有什么区别
16、3*0.1 == 0.3返回值是什么
17、Object 常用方法
18、Java 创建对象方式
19、获取一个类 Class 对象的方式
20、Java反射
21、Java IO
22、Java IO 与 NIO的区别
23、重载与重写的区别
24、equals与==的区别
25、Hashcode 的作用
26、两个不相等的对象有相同的hashcode
27、String 与 StringBuffer 与 StringBuilder 区别
28、Collection 与 Collections 的区别
29、HashMap 和 HashTable 的区别
31、HashMap 中的 key 我们可以使用任何类作为 key 吗?
32、HashMap 的长度为什么是 2 的 N 次方呢?
33、HashMap 与 ConcurrentHashMap 的区别
34、红黑树有哪几个特征
35、ArrayList 和 LinkedList 的区别
36、Array 与 ArrayList
37、Excption 与 Error 的区别
38、OOM 与 SOF
39、什么是 fail-fast ?
40、说说你平时是怎么处理 Java 异常的
41、说说深拷贝和浅拷贝
42、线程、程序、进程
1、Java语言有哪些特点
- 简单易学,有丰富的类库。
- 面向对象(Java最重要的特性,让程序耦合度更低,内聚性更高)。
- 平台无关性(JVM是Java跨平台使用的根本)。
- 可靠安全。
- 支持多线程。
2、面向对象和面向过程的区别
- 面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步的实现,然后在使用的时候一一调用。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发。
- 面向对象:把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事务在解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特性,所以易维护、易复用、易扩展。可以设计出低耦合的系统,但是性能上比面向过程要低。
3、八种基本数据类型的大小
基本类型 | 大小(字节) | 默认值 | 封装类 |
byte | 1 | (byte)0 | Byte |
short | 2 | (short)0 | Short |
int | 4 | 0 | Integer |
long | 8 | 0l | Long |
float | 4 | 0.0f | Float |
double | 8 | 0.0d | Double |
boolean | - | false | Boolean |
char | 2 | \u0000(null) | Character |
- int是基本数据类型,Integer是int的封装类,是引用类型。int默认值是0,Integer默认值是null,所以Integer能区分0和null。一但Java看到null就知道这个引用还没有指向某个对象,在任何引用使用前,必须为其指定一个对象,否则会报错。
- 基本数据类型在声明时系统会自动给他分配空间,而引用类型声明只是分配了引用空间,必须通过实例化开辟数据空间后才可以赋值。数组对象也是一个引用对象,将一个数组赋值给另一个数组时只是复制了一个引用,所以通过一个数组所做的修改在另一个数组中也可以看见。
- 虽然定义boolean这种数据类型,但是只对他提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个boolean元素占8位。这样我们可以得出boolean类型单独使用是4个字节,在数组中又是1一个字节。使用int的原因是,对于当下32位处理器(CPU)来说,一次处理的数据是32位(这里不是指的32/64位系统,而是CPU硬件层面),具有高效存取的特点。
4、标识符命名规则
- 标识符含义:是指在程序中,我们自己定义的内容,譬如:类的名称、方法名称以及变量名称等,都是标识符。
- 命名规则(硬性要求):标识符可以包含英文字母,0-9数字,$以及_标识符,不能以数字开头,标识符不是关键字。
- 命名规范(非硬性要求):类名首字母大写,后面每个单词首字母大写(大驼峰式),方法名、变量名首字母小写,后面每个单词首字母大写(小驼峰式)。
5、Java 关键字
访问控制 | 类、接口、方法、变量、代码块修饰符 | 程序控制 | 错误处理 | 基本类型 | 变量引用 | 包相关 | 保留字 |
private | class | for | try | byte | new | import | goto |
protected | abstract | break | catch | short | super | package | const |
default(缺醒) | extends | continue | finally | int | this | ||
public | implements | do | throw | long | void | ||
interface | while | throws | float | null | |||
final | if | double | true | ||||
strictfp | else | boolean | false | ||||
static | switch | char | |||||
synchronized | case | ||||||
transient | default | ||||||
volitail | instanceof | ||||||
native | return | ||||||
enum | assert |
6、访问控制
作用域 | 当前类 | 当前包 | 子类 | 其它包 |
private | √ | × | × | × |
default(缺醒) | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
7、instanceof 关键字的作用
- instanceof严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例。
boolean res = obj instanceof Class
- obj为一个对象,Class为一个类或者一个接口,当obj为Class的对象,或者是其直接或间接子类,或者接口实现类,res为true,否则为false。
注意:编译器会检查obj是否能转换成右边的class类型,如果不能确定类型,则编译不通过。
int i = 0;
System.out.println(i instanceof Integer); // 编译不通过,i必须是引用类型,不能是基本类型
System.out.println(i instanceof Object); // 编译不通过Integer i = new Integer(1);
System.out.println(i instanceof Integer); // true
// 在JavaSE规范中对instanceof运算符的规定就是,如果obj为null,那么将返回false
System.out.println(null instanceof Integer); // false
8、final 关键字的作用
- 被final修饰的类不可以被继承。
- 被final修饰的方法不可以被重写。
- 被final修饰的变量不可以被改变。如果修饰引用,那么表示引用不可变,引用指向的内容可变。.
- 被final修饰的方法,JVM会尝试将其内联,以提高运行效率。
- 被final修饰的常量,在编译阶段会存入常量池中. 。
除此之外,编译器对final域要遵守的两个重排序规则更好:
在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序,初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
9、static 关键字作用
- static修饰的变量、方法都属于类的静态资源,类实例所共享。
- static修饰的内部类,称之为静态内部类。
- static修饰的代码块,称之为静态块,用于初始化操作。
public calss PreCache{static{//执行相关操作}
}
- 静态导包,即 import static .import static是在JDK 1.5之后引入的新特性,可以用来指定导入某个类中的静态资源,并且不需要使用类名,可以直接使用资源名。
import static java.lang.Math.*;
public class Test{public static void main(String[] args){//System.out.println(Math.sin(20));传统做法System.out.println(sin(20));}
}
10、transient 关键字的作用
- 阻止实例中那些用此关键字修饰的的变量序列化。当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。
11、try catch finally return
- 不管有木有出现异常,finally 块中代码都会执行。
- 当 try 和 catch 中有 return 时,finally 仍然会执行。
- finally 是在 return 后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管 finally 中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在 finally 执行前确定的。
- finally中最好不要包含 return,否则程序会提前退出,返回值不是try或catch中保存的返回值 。
12、Java 的四种引用(强弱软虚)
- 强引用:是平常中使用最多的引用,强引用在程序内存不足(OOM)的时候也不会被回收。
String str = new String("str");
System.out.println(str);
- 软引用:在程序内存不足时,会被回收。
// 注意:wrf这个引用也是强引用,它是指向SoftReference这个对象的,
// 这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中T
SoftReference<String> wrf = new SoftReference<String>(new String("str"));
可用场景:创建缓存的时候,创建的对象放进缓存中,当内存不足时,JVM就会回收早先创建的对象。
- 弱引用:就是只要JVM垃圾回收器发现了它,就会将之回收。
WeakReference<String> wrf = new WeakReference<String>(str);
可用场景:Java源码中的 java.util.WeakHashMap 中的 key 就是使用弱引用,我的理解就是,一旦我不需要某个引用,JVM会自动帮我处理它,这样我就不需要做其它操作。
- 虚引用:虚引用的回收机制跟弱引用差不多,但是它被回收之前,会被放入 ReferenceQueue 中。
注意:其它引用是被JVM回收后才被传入 ReferenceQueue 中的。由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。还有就是,虚引用创建的时候,必须带有 ReferenceQueue。
PhantomReference<String> prf = new PhantomReference<String>(new String("str"),
new ReferenceQueue<>());
可用场景:对象销毁前的一些操作,比如说资源释放等。Object.finalize() 虽然也可以做这
类动作,但是这个方式即不安全又低效
总结:上诉所说的几类引用,都是指对象本身的引用,而不是指Reference的四个子类的引用
(SoftReference等)。
13、泛型常用特点
List<Integer> iniData = new ArrayList<>();
- 泛型是Java SE 1.5之后的特性。
- 《Java 核心技术》中对泛型的定义是:“泛型” 意味着编写的代码可以被不同类型的对象所重用。
- “泛型”,顾名思义,“泛指的类型”。我们提供了泛指的概念,但具体执行的时候却可以有具体的规则来约束,比如我们用的非常多的ArrayList就是个泛型类,ArrayList作为集合可以存放各种元素,如Integer, String,自定义的各种类型等,但在我们使用的时候通过具体的规则来约束,如我们可以约束集合中只存放Integer类型的元素。
- 使用泛型的好处:以集合来举例,使用泛型的好处是我们不必因为添加元素类型的不同而定义不同类型的集合,如整型集合类,浮点型集合类,字符串集合类,我们可以定义一个集合来存放整型、浮点型,字符串型数据,而这并不是最重要的,因为我们只要把底层存储设置Object即可,添加的数据全部都可向上转型为Object。 更重要的是我们可以通过规则按照自己的想法控制存储的数据类型。
14、Java 自动装箱与拆箱
- 装箱就是自动将基本数据类型转换为包装器类型(int->Integer),调用Integer.valueOf(i)。
- 拆箱就是自动将包装器类型转换为基本数据类型(Integer->int),调用Integer.intValue()。
// 在Java SE5之前,如果要生成一个数值为10的Integer对象
Integer i = new Integer(10);// 从Java SE5开始提供自动装箱特性
Integer i = 10;
- 通过valueOf方法创建Integer对象的时候,如果数值在[-18,127]之间,便之间返回指定IntegerCache中已经存在的对象引用,否则创建一个新的Integer对象。
public class Main {public static void main(String[] args) {Integer i1 = 100;Integer i2 = 100;Integer i3 = 200;Integer i4 = 200;System.out.println(i1==i2); // trueSystem.out.println(i3==i4); // false}
}// Integer的valueOf具体实现方法
public static Integer valueOf(int i) {if(i >= -128 && i <= IntegerCache.high)return IntegerCache.cache[i + 128];elsereturn new Integer(i);
}
// IntegerCache类实现
private static class IntegerCache {static final int high;static final Integer cache[];static {final int low = -128;// high value may be configured by propertyint h = 127;if (integerCacheHighPropValue != null) {// Use Long.decode here to avoid invoking methods that// require Integer's autoboxing cache to be initializedint i = Long.decode(integerCacheHighPropValue).intValue();i = Math.max(i, 127);// Maximum array size is Integer.MAX_VALUEh = Math.min(i, Integer.MAX_VALUE - -low);}high = h;cache = new Integer[(high - low) + 1];int j = low;for(int k = 0; k < cache.length; k++)cache[k] = new Integer(j++);}private IntegerCache() {}
}
- 在某一个范围内整形数值个数是有限的,而浮点数不是。
public class Main {public static void main(String[] args) {Double i1 = 100.0;Double i2 = 100.0;Double i3 = 200.0;Double i4 = 200.0;System.out.println(i1==i2); // falseSystem.out.println(i3==i4); // false}
}
15、a=a+b与a+=b有什么区别
- += 操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换。
byte a = 127;
byte b = 127;
b = a + b; // 报编译错误:cannot convert from int to byte
b += a;short s1= 1;
s1 = s1 + 1; // s1+1 的运算结果是int类型,而s1是short类型,此时编译器会报错short s1= 1;
s1 += 1; // +=操作符会对右边的表达式结果强转匹配左边的数据类型,所以没错
16、3*0.1 == 0.3返回值是什么
System.out.println(3*0.1 == 0.3) // false,因为有些浮点数不能完全精确的表示出来
17、Object 常用方法
- clone():保护方法,实现对象的浅复制,只有实现了 Cloneable 接口才可以调用该方法,否则抛出CloneNotSupportedException 异常,深拷贝也需要实现 Cloneable,同时其成员变量为引用类型的也需要实现 Cloneable,然后重写 clone 方法。
- finalize():该方法和垃圾收集器有关系,判断一个对象是否可以被回收的最后一步就是判断是否重写了此方法。
- equals():该方法使用频率非常高。一般 equals 和 == 是不一样的,但是在 Object 中两者是一样的。子类一般都要重写这个方法。
- hashCode():该方法用于哈希查找,重写了 equals 方法一般都要重写 hashCode 方法,这个方法在一些具有哈希功能的 Collection 中用到。
一般必须满足 obj1.equals(obj2)==true 。可以推出 obj1.hashCode()==obj2.hashCode() ,但是
hashCode 相等不一定就满足 equals。不过为了提高效率,应该尽量使上面两个条件接近等价。
- JDK 1.6、1.7 默认是返回随机数。
- JDK 1.8 默认是通过和当前线程有关的一个随机数 + 三个确定值,运用 Marsaglia’s xorshift scheme 随机数算法得到的一个随机数。
- wait():配合 synchronized 使用,wait 方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait() 方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。
- 其他线程调用了该对象的 notify 方法;
- 其他线程调用了该对象的 notifyAll 方法;
- 其他线程调用了 interrupt 中断该线程;
- 时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个 InterruptedException 异常。
- notify():配合 synchronized 使用,该方法唤醒在该对象上等待队列中的某个线程(同步队列中的线程是给抢占 CPU 的线程,等待队列中的线程指的是等待唤醒的线程)。
- notifyAll():配合 synchronized 使用,该方法唤醒在该对象上等待队列中的所有线程。
Class Object is the root of the class hierarchy.Every class has Object as a
superclass. All objects, including arrays, implement the methods of this class.
总结:Object 是所有类的根,是所有类的父类,所有对象包括数组都实现了 Object 的方法。
18、Java 创建对象方式
- 使用 new 关键字,这也是我们平时使用的最多的创建对象的方式。
User user = new User();
- 使用反射方式创建对象,但得处理两个异常 InstantiationException、IlegalAccessException。
User user = User.class.newInstance();
Object object = (Object)Class.forName("java.lang.Object").newInstance()
- 使用 clone 方法,是 Object 的方法,所以所有对象都有这个方法。
User user2 = (User)user.clone();
- 使用反序列化创建对象,调用 ObjectInputStream 类的 readObject() 方法。我们反序列化一个对象,JVM 会给我们创建一个单独的对象。JVM 创建对象并不会调用任何构造函数。一个对象实现了 Serializable 接口,就可以把对象写入到文件中,并通过读取文件来创建对象。
public class MySerilizable {public static void main(String[] args) throws Exception { File file = new File("person.txt"); //序列化持久化对象ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); User user = new User("Peter", 27); out.writeObject(user); out.close(); //反序列化,并得到对象ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); Object obj = in.readObject(); in.close(); System.out.println(obj); }
}
19、获取一个类 Class 对象的方式
- 通过类对象的 getClass() 方法获取,是 Object 类里面的方法。
User user=new User();
//clazz就是一个User的类对象
Class<?> clazz=user.getClass();
- 通过类的静态成员表示,每个类都有隐含的静态成员 class。
//clazz就是一个User的类对象
Class<?> clazz=User.class;
- 通过 Class 类的静态方法 forName() 方法获取。
Class<?> clazz = Class.forName("com.tian.User");
总结:类对象和实例对象,但都是对象。
20、Java反射
定义
- 反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
实现方式
- Class:表示正在运行的Java应用程序中的类和接口。
Class clazz = Class.forName("类的路径");
- Constructor:提供关于类的单个构造方法的信息以及它的访问权限 。
Constructor ctor = clazz.getDeclarederConstructor();
- Field:提供有关类和接口的属性信息,以及对它的动态访问权限。
Field field = clazz.getField("fieldName");
- Method:提供类或接口中某个方法的信息。
Method method = clazz.getMethod("methodName", clazz);
优缺点
- 优点:
- 能够运行时动态获取类的实例,提高灵活性。
- 与动态编译结合。
- 缺点:
- 使用反射性能较低,需要解析字节码,将内存中的对象进行解析。
- 相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)。
- 解决方案:
- 通过setAccessible(true)关闭JDK的安全检查来提升反射速度。
- 多次创建一个类的实例时,有缓存会快很多。
- ReflectASM工具类,通过字节码生成的方式加快反射速度。
使用场景
- jdbc就是典型的反射,如hibernate,struts等框架使用反射实现的。
21、Java IO
- 按照流的流向分,可以分为输入流和输出流。
- 按照操作单元划分,可以划分为字节流和字符流。
- 按照流的角色划分为节点流和处理流。
Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
按操作方式分类结构图:
按操作对象分类结构图:
22、Java IO 与 NIO的区别
- NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。
23、重载与重写的区别
重写(Overried):从字面上看,重写就是重写写一遍。其实就是在子类把父类本身的方法重新写一遍。子类继承了父类原有的方法,但有时候子类并不想原封不动的继承父类的某个方法,所以在方法名、参数列表、返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下,对方法进行修改或重写。但要注意子类函数的访问修饰符权限不能小于父类的。
public class Father {public static void main(String[] args) {// TODO Auto-generated method stubSon s = new Son();s.sayHello();}public void sayHello() {System.out.println("Hello");}
}class Son extends Father{@Overridepublic void sayHello() {// TODO Auto-generated method stubSystem.out.println("hello by ");}
}
总结:
- 发生在父类与子类之间。
- 方法名、参数列表、返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)必须相同。
- 访问修饰符的限制一定不能大于被重写方法的访问修饰符。
- 重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。
重载(Overload):在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,不能通过返回类型来判断重载。
public class Father {public static void main(String[] args) {// TODO Auto-generated method stubFather s = new Father();s.sayHello();s.sayHello("wintershii");}public void sayHello() {System.out.println("Hello");}public void sayHello(String name) {System.out.println("Hello" + " " + name);}
}
总结:
- 重载是一个类中多态性的一种表现。
- 重载要求同名方法的参数列表不同(参数类型不同、参数个数不同甚至顺序不同)。
- 重载的时候,返回类型可以相同也可以不同。无法通过返回类型作为重载的区分标准。
24、equals与==的区别
==:比较的是变量(栈)内存中存放的对象的内存地址(堆地址),用来判断两个对象的地址是否相同,即是否指向同一个对象。比较的是真正意义上的指针操作。
- 比较的是操作符两端的操作数是否是同一个对象。
- 两边的操作数必须是同一类型的(可以是父子类之间)才能编译通过。
- 比较的是地址,如果具体的数字比较,值相等则为true,如:int a = 10与long b = 10L与double c = 10.0都是相同的(true),因为他们都指向地址为10的堆。
equals:比较两个对象的内容是否相等,由于所有的类都是继承自Object类,所以适用于所有对象。如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object的中equals方法返回的却是==的判断。
- equals与常量比较时,把常量写前面,因为使用Object的equals,Object可能为null,则空指针。
25、Hashcode 的作用
- Java的集合有两类,List(有序可重复)、Set(无序不可重复)。当我们在set中插入的时候怎么判断是否已经存在该元素呢?可以通过equals方法,但是如果元素太多,用这样的方法就会比较慢。
- 于是有人发明了哈希算法来提高集合中查找元素的效率。这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的那个区域。
- hashcode方法可以这样理解:他返回的就是根据对象的内存地址换算出来的一个值。这样一来,当集合要添加新的元素时,先调用这个元素的hashcode方法,就一下子能定位到它应该放置的物理位置。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了。如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不想同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
26、两个不相等的对象有相同的hashcode
- 在产生hash冲突时,两个不相等的对象就会有相同的 hashcode 值
解决方案:
- 拉链法:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表进行存储。
- 开放定址法:一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入List<Integer> iniData = new ArrayList<>()。
- 再哈希:又叫双哈希法,有多个不同的Hash函数.当发生冲突时,使用第二个,第三个….等哈希函数计算地址,直到无冲突。
27、String 与 StringBuffer 与 StringBuilder 区别
private final char value[];
- String是只读字符串,它并不是基本数据类型,而是一个对象,从底层源码来看是一个final类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String的操作都会生成新的String对象。
- 每次+操作,隐式在堆上new了一个跟原字符串相同的StringBuilder对象,再调用append方法 拼接+后面的字符。
/**
* The value is used for character storage.
*/
char[] value;
- StringBuffer和StringBuilder他们两都继承了AbstractStringBuilder抽象类,从AbstractStringBuilder抽象类中我们可以看到他们的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用StringBuffer和StringBuilder来进行操作。 另外StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
28、Collection 与 Collections 的区别
- Collection:是集合类的上级接口,子接口有 Set、List、LinkedList、ArrayList、Vector、Stack、Set。
- Collections:是集合类的一个帮助类,它包含有各种有关集合操作的静态多态方法,用于实现对各种集合的搜索、排序、线程安全化等操作。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
29、HashMap 和 HashTable 的区别
- 两者父类不同:HashMap是继承自AbstractMap类,而Hashtable是继承自Dictionary类。不过它们都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口。
- 出生的版本不一样: Hashtable 出生于 Java 发布的第一版本 JDK 1.0,HashMap 出生于 JDK1.2。
- 对外提供的接口不同:Hashtable比HashMap多提供了elments() 和contains() 两个方法。elments() 方法继承自Hashtable的父类Dictionnary。elements() 方法用于返回此Hashtable中的value的枚举。contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,contansValue() 就只是调用了一下contains() 方法。
- 对null的支持不同:Hashtable:key和value都不能为null。HashMap:key可以为null,但是这样的key只能有一个,因为必须保证key的唯一性;可以有多个key值对应的value为null。
- 安全性不同:HashMap是线程不安全的,在多线程并发的环境下,可能会产生死锁等问题,因此需要开发人员自己处理多线程的安全问题。Hashtable是线程安全的,它的每个方法上都有synchronized 关键字,因此可直接用于多线程中。虽然HashMap是线程不安全的,但是它的效率远远高于Hashtable,这样设计是合理的,因为大部分的使用场景都是单线程。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。
- 初始容量大小和每次扩充容量大小不同:Hashtable 的初始长度是 11,之后每次扩充容量变为之前的2n+1(n 为上一次的长度)而 HashMap 的初始长度为 16,之后每次扩充变为原来的两倍。
- 计算hash值的方法不同:Hashtable 直接使用对象的 hash 值。hash 值是 JDK 根据对象的地址或者字符串或者数字算出来的 int 类型的数值。然后再使用除留余数法来获得最终的位置。然而除法运算是非常耗费时间的,效率很低。HashMap 为了提高计算效率,将哈希表的大小固定为了 2 的幂,这样在取模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。
另外在 Hashtable 源码注释中有这么一句话:
Hashtable is synchronized. If a thread-safe implementation is not needed, it is
recommended to use HashMap in place of Hashtable . If a thread-safe highly-concurrent implementation is desired, then it is recommended to use
ConcurrentHashMap in place of Hashtable.大致意思:Hashtable 是线程安全,推荐使用 HashMap 代替 Hashtable;如果需要线程安全高并
发的话,推荐使用 ConcurrentHashMap 代替 Hashtable。
31、HashMap 中的 key 我们可以使用任何类作为 key 吗?
平时可能大家使用的最多的就是使用 String 作为 HashMap 的 key,但是现在我们想使用某个自定
义类作为 HashMap 的 key,那就需要注意以下几点:
- 如果类重写了 equals 方法,它也应该重写 hashCode 方法。
- 类的所有实例需要遵循与 equals 和 hashCode 相关的规则。
- 如果一个类没有使用 equals,你不应该在 hashCode 中使用它。
- 咱们自定义 key 类的最佳实践是使之为不可变的,这样,hashCode 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode 和 equals 在未来不会改变,这样就会解决与可变相关的问题了。
32、HashMap 的长度为什么是 2 的 N 次方呢?
为了能让 HashMap 存数据和取数据的效率高,尽可能地减少 hash 值的碰撞,也就是说尽量把数
据能均匀的分配,每个链表或者红黑树长度尽量相等。HashMap利用 % 取模的操作来实现。
取余(%)操作中如果除数是 2 的幂次,则等价于与其除数减一的与(&)操作。
hash % length == hash &(length - 1) 的前提是 length 是 2 的 n 次方。
并且,采用二进制位操作 & ,相对于 % 能够提高运算效率。
这就是为什么 HashMap 的长度需要 2 的 N 次方了。
33、HashMap 与 ConcurrentHashMap 的区别
- 都是 key-value 形式的存储数据。
- HashMap 是线程不安全的,ConcurrentHashMap 是 JUC 下的线程安全的。HashMap 底层数据结构是数组 + 链表(JDK 1.8 之前)。JDK 1.8 之后是数组 + 链表 + 红黑树。
- 当链表中元素个数达到 8 的时候,链表的查询速度不如红黑树快,链表会转为红黑树,红黑树查询速度快。
- HashMap 初始数组大小为 16(默认),当出现扩容的时候,以 0.75 * 数组大小的方式进行扩容。
- ConcurrentHashMap 在 JDK 1.8 之前是采用分段锁来现实的 Segment + HashEntry,Segment 数组大小默认是 16,2 的 n 次方;JDK 1.8 之后,采用 Node + CAS + Synchronized来保证并发安全进行实现。
34、红黑树有哪几个特征
35、ArrayList 和 LinkedList 的区别
ArrayList
- 优点:ArrayList 是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
- 缺点:因为地址连续,ArrayList 要移动数据,所以插入和删除操作效率比较低。
LinkedList
- 优点:LinkedList 基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址。对于新增和删除操作,LinkedList 比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景。
- 缺点:因为 LinkedList 要移动指针,所以查询操作性能比较低。
适用场景分析
- 当需要对数据进行对随机访问的时候,选用 ArrayList。
- 当需要对数据进行多次增加删除修改时,采用 LinkedList。
如果容量固定,并且只会添加到尾部,不会引起扩容,优先采用 ArrayList。
当然,绝大数业务的场景下,使用 ArrayList 就够了,但需要注意避免 ArrayList 的扩容,以及非顺序的插入。
36、Array 与 ArrayList
Array:大小固定。
ArrayList:可以使用默认的大小,当元素个数到达一定程度后,会自动扩容,动态数组。
37、Excption 与 Error 的区别
Java可抛出(Throwable)的结构分为三种类型:被检查的异常(CheckedException),运行时异常(RuntimeException),错误(Error)。
运行时异常(RuntimeException)
定义:
- RuntimeException及其子类都被称为运行时异常。
特点:
- Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。
常见的运行时异常:
- ArithmeticException(除数为零)
- ClassCastException(类转换异常)
- IndexOutOfBoundsException(数组越界)
- NullPointerException(空指针异常)
- ArrayStoreException(数据存储异常,操作数组是类型不一致)
- BufferOverflowException
- ConcurrentModificationException(fail-fast机制产生)
被检查异常(CheckedException)
定义:
- Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。
特点:
- Java编译器会检查它。 此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。
常见被检查异常:
- CloneNotSupportedException
- IOException
- FileNotFoundException
- SQLException
- NullPointerException
错误(Error)
定义:
- Error类及其子类。
特点:
- 和运行时异常一样,编译器也不会对错误进行检查。
常见错误:
- VirtualMachineError
- OutOfMemoryError
38、OOM 与 SOF
OOM(OutOfMemoryError):
- 堆溢出:java.lang.OutOfMemoryError:Java heap spacess。
java堆用于存储对象实例,我们只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。出现这种异常,一般手段是先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
如果是内存泄漏,可进一步通过工具查看泄漏对象到GCRoots的引用链。于是就能找到泄漏对象是
通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。
如果不存在泄漏,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。
- 虚拟机栈和本地方法栈溢出
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
这里需要注意当栈的大小越大可分配的线程数就越少。
- 运行时常量池溢出:java.lang.OutOfMemoryError:PermGenspace
如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。该方法
的作用是:如果池中已经包含一个等于此String的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。
- 方法区溢出:java.lang.OutOfMemoryError:PermGenspace
方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。
方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。
SOF(StackOverflow):
StackOverflowError 的定义:当应用程序递归太深而发生堆栈溢出时,抛出该错误。
因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。
栈溢出的原因:递归调用,大量循环或死循环,全局变量是否过多,数组、List、map数据过大。
39、什么是 fail-fast ?
定义:fail-fast 机制是 Java 集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生 fail-fast 事件。
例如:当某一个线程 A 通过 iterator 去遍历某集合的过程中,若该集合的内容被其他线程所改变
了,那么线程 A 访问集合时,就会抛出 ConcurrentModificationException 异常,产生 fail-fast 事
件。这里的操作主要是指 add、remove 和 clear,对集合元素个数进行修改。
解决办法:建议使用“java.util.concurrent 包下的类”去取代“java.util 包下的类”。
可以这么理解:在遍历之前,把 modCount 记下来 expectModCount,后面 expectModCount 去
和 modCount 进行比较,如果不相等了,证明已并发了,被修改了,于是抛出ConcurrentModificationException 异常。
40、说说你平时是怎么处理 Java 异常的
try-catch-finally
- try 块负责监控可能出现异常的代码
- catch 块负责捕获可能出现的异常,并进行处理
- finally 块负责清理各种资源,不管是否出现异常都会执行
- 其中 try 块是必须的,catch 和 finally 至少存在一个标准异常处理流程
抛出异常→捕获异常→捕获成功(当 catch 的异常类型与抛出的异常类型匹配时,捕获成功)
→异常被处理,程序继续运行 抛出异常→捕获异常→捕获失败(当 catch 的异常类型与抛出异
常类型不匹配时,捕获失败)→异常未被处理,程序中断运行
在开发过程中会使用到自定义异常,在通常情况下,程序很少会自己抛出异常,因为异常的类名通
常也包含了该异常的有用信息,所以在选择抛出异常的时候,应该选择合适的异常类,从而可以明
确地描述该异常情况,所以这时候往往都是自定义异常。
自定义异常通常是通过继承 java.lang.Exception 类,如果想自定义 Runtime 异常的话,可以继承
java.lang.RuntimeException 类,实现一个无参构造和一个带字符串参数的有参构造方法。
在业务代码里,可以针对性的使用自定义异常。比如说:该用户不具备某某权限、余额不足等。
41、说说深拷贝和浅拷贝
浅拷贝(shallowCopy):只是增加了一个指针指向已存在的内存地址。
深拷贝(deepCopy):是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存。
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
42、线程、程序、进程
线程:与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
程序:是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
进程:是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。