JAVA基础
- JAVA中的几种基本数据类型是什么,各自占用多少字节。
1B=8bit
1Byte=8bit
1KB=1024Byte(字节)=8*1024bit
1MB=1024KB
1GB=1024MB
1TB=1024GB
boolean 1bit
byte 8bit
short 16bit
char 16bit
float 32bit
int 32bit
long 64bit
double 64bit
/**
* 输出各种基础类型的bit大小,也就是所占二进制的位数,1Byte=8bit
*/
private static void getBit() {
//The number of bits used to represent a {@code byte} value in two's complement binary form.
//用来表示Byte类型的值的位数,说到底,就是bit的个数,也就是二进制的位数。
System.out.println("Byte: " + Byte.SIZE);
System.out.println("Short: " + Short.SIZE);
System.out.println("Character: " + Character.SIZE);
System.out.println("Integer: " + Integer.SIZE);
System.out.println("Float: " + Float.SIZE);
System.out.println("Long: " + Long.SIZE);
System.out.println("Double: " + Double.SIZE);
System.out.println("Boolean: " + Boolean.toString(false));
}
- String类能被继承吗,为什么。
不可以,因为String类有final修饰符,而final修饰的类是不能被继承的,实现细节不允许改变。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
- String,Stringbuffer,StringBuilder的区别。
String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)
- ArrayList和LinkedList有什么区别。
ArrayList 本质上是一个可改变大小的数组.当元素加入时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问.元素顺序存储 ,随机访问很快,删除非头尾元素慢,新增元素慢而且费资源 ,较适用于无频繁增删的情况 ,比数组效率低,如果不是需要可变数组,可考虑使用数组 ,非线程安全.
LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList. 适用于 :没有大规模的随机读取,有大量的增加/删除操作.随机访问很慢,增删操作很快,不耗费多余资源 ,允许null元素,非线程安全.
Vector (类似于ArrayList)但其是同步的,开销就比ArrayList要大。如果你的程序本身是线程安全的,那么使用ArrayList是更好的选择。 Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%.
- 讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当new的时候,他们的执行顺序。
public class InitialOrderTest {
/* 静态变量 */
public static String staticField = "静态变量";
/* 变量 */
public String field = "变量";
/* 静态初始化块 */
static {
System.out.println( staticField );
System.out.println( "静态初始化块" );
}
/* 初始化块 */
{
System.out.println( field );
System.out.println( "初始化块" );
}
/* 构造器 */
public InitialOrderTest()
{
System.out.println( "构造器" );
}
public static void main( String[] args )
{
new InitialOrderTest();
}
}
运行以上代码,我们会得到如下的输出结果:
1静态变量
2静态初始化块
3变量
4初始化块
5构造器
- 用过哪些Map类,都有什么区别,HashMap是线程安全的吗,并发下使用的Map是什么,他们内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。
java为数据结构中的映射定义了一个接口java.util.Map;它有四个实现类,分别是HashMap Hashtable LinkedHashMap 和TreeMap.
Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允许值重复。
Hashmap 是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。 HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
Hashtable与 HashMap类似,它继承自Dictionary类,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。
LinkedHashMap 是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
一般情况下,我们用的最多的是HashMap,在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。如果需要输出的顺序和输入的相同,那么用LinkedHashMap 可以实现,它还可以按读取顺序来排列.
HashMap是一个最常用的Map,它根据键的hashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为NULL,允许多条记录的值为NULL。
HashMap不支持线程同步,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致性。如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力。
Hashtable与HashMap类似,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtable在写入时会比较慢。
LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。
在遍历的时候会比HashMap慢TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器。当用Iterator遍历TreeMap时,得到的记录是排过序的。
- JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。
jdk 1.8 取消了基于 Segment 的分段锁思想,改用 CAS + synchronized 控制并发操作,在某些方面提升了性能。并且追随 1.8 版本的 HashMap 底层实现,使用数组+链表+红黑树进行数据存储。
- 有没有有顺序的Map实现类,如果有,他们是怎么保证有序的。
TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,类可以实现多个接口么。
继承和聚合的区别在哪。
继承指的是一个类继承另外的一个类的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;在Java中此类关系通过关键字extends明确标识。
聚合指的是聚合体现的是整体与部分、拥有的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期;比如计算机与CPU、公司与员工的关系等;
- IO模型有哪些,讲讲你理解的nio ,他和bio,aio的区别是啥,谈谈reactor模型。
1.什么是BIO,NIO,AIO
JAVA BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程并处理,如果这个连接不做任何事情会造成不必要的开销,当然可以通过线程池机制改善
JAVA NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理
JAVA AIO(NIO2):异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理
2.使用场景
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
3.BIO 同步并阻塞
tomcat采用的传统的BIO(同步阻塞IO模型)+线程池模式,对于十万甚至百万连接的时候,传统BIO模型是无能为力的:
①线程的创建和销毁成本很高,在linux中,线程本质就是一个进程,创建销毁都是重量级的系统函数
②线程本身占用较大的内存,像java的线程栈一般至少分配512K-1M的空间,如果系统线程过高,内存占用是个问题
③线程的切换成本高,操作系统发生线程切换的时候,需要保留线程的上下文,然后执行系统调用,如果线程数过高可能执行线程切换的时间甚至大于线程执行的时间,这时候带来的表现是系统load偏高,CPUsy使用率很高
④容易造成锯齿状的系统负载。系统负载是用活动线程数或CPU核心数,一旦线程数量高但外部网络环境不是很稳定,就很容易造成大量请求的结果同时返回,激活大量阻塞线程从而使系统负载压力过大。
4NIO同步非阻塞
NIO基于Reactor,当socket有流可读或可写入socket,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。也就是,不是一个链接就要对应一个处理线程,而是一个有效请求对应一个线程,当连接没有数据时,是没有工作线程来处理的
Reactor模型
nio只有acceptor的服务线程是堵塞进行的,其他读写线程是通过注册事件的方式,有读写事件激活时才调用线程资源区执行,不会一直堵塞等着读写操作,Reactor的瓶颈主要在于acceptor的执行,读写事件也是在这一块分发
5AIO异步非堵塞IO
AIO需要一个链接注册读写事件和回调方法,当进行读写操作时,只须直接调用API的read或write方法即可,这两种方法均为异步,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序
即,read/write方法都是异步的,完成后会主动调用回调函数
- 反射的原理,反射创建类实例的三种方式是什么。
反射的好处(为什么需要反射机制):
通过反射机制可以获取到一个类的完整信息,例如:所有(包含private修饰)属性和方法,包信息等。
换句话说,Class本身表示一个类的本身,通过Class可以完整获取一个类中的完整结构,包含此类中的方法定义,属性定义等。
反射的核心概念:
一切的操作都是讲使用Object完成,类或者数组的引用是可以用Object进行接收。
这里,个人的理解是,对象的多态:Object object= 任何引用类型的实例对象
Class类的API特性和三种创建方式
在Java中,可以通过Class类获取到任何一个类中完整的信息。
一个很重要的概念:
在Java中,Object是所有类的超类,所有类的对象实际上也是java.lang,Class类的实例。 因此,所有的对象都可以转变成Class类型表示。
Class类常用的API
forName() : 传入完整的”包.类”名称实例化Class对象
getConstructors() : 得到一个类中的全部构造方法
getConstructor(Class<?>...parameterTypes):获取到指定参数类型的(public修饰的)构造方法
getDeclaredConstructor(Class<?>... parameterTypes):获取到指定参数类型的构造方法,包含private修饰和public修饰
getDeclaredFields(): 获得某个类的单独定义全部的字段(即包括public、private和proteced等修饰符的全部属性),但是不包括父类的申明字段或者实现接口中的字段。
getFields(): 获得某个类的所有的公共(public)的字段,包括父类中的公共字段或者实现接口的公共属性和类本身定义的公共属性。
getMethods() : 获取到本类中全部public修饰的方法,包含父类中公共方法和覆盖重写的方法和自己本身定义的公共方法。
getMethod(String name,Class...parameterType): 获取到指定名字,指定方法参数类型的公共方法。
getSuperclass(): 获取到父类的Class
getInterfaces() : 获取(实现的全部接口对应的)Class数组。
newInstance(): 实例化Class中定义类型的实例化对象。
getComponentType(): 获取数组类型的Class.
isArray(): 判断此Class是否是一个数组。
getName() : 获取到一个类完整”包.类”名称
创建Class类对象的方式有三种:
对象.getClass()方式
类名.Class方式
Class.forName( 类的包名 ) 方式
案例实战
创建一个测试类:
package com.xingen.classdemo;
public class ClassTest1 {
public static ClassTest1 newInstance() {
return new ClassTest1();
}
public static void createClassInstance1() {
//对象.getClass() 方式获取Class对象
Class<?> mClass = ClassTest1.newInstance().getClass();
//输出类所在的包路径
System.out.println(" 通过对象.getClass()方式, 反射出类所在的包路径: " + mClass.getName());
}
public static void createClassInstance2() {
//类名.class 方式获取Class对象
Class<?> mClass = ClassTest1.class;
//输出类所在的包路径
System.out.println(" 通过类名.Class 方式,反射出 类所在的包路径: " + mClass.getName());
}
public static void createClassInstance3() {
//Class.forName 方式获取Class对象
Class<?> mClass = null;
try {
mClass = Class.forName("com.xingen.classdemo.ClassTest1");
} catch (Exception e) {
e.printStackTrace();
}
//输出类所在的包路径
System.out.println(" 通过Class.forName 方式,反射出 类所在的包路径: " + mClass.getName());
}}
public class Client {
public static void main(String[] args) {
createClassInstance();
}
/**
* 创建Class类实例对象的三种方式
*/
private static void createClassInstance() {
ClassTest1.createClassInstance1();
ClassTest1.createClassInstance2();
ClassTest1.createClassInstance3();
}
}
- 反射中,Class.forName和ClassLoader区别 。
java中class.forName()和classLoader都可用来对类进行加载。
class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
而classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象
- 描述动态代理的几种实现方式,分别说出相应的优缺点。
看这里 - 动态代理与cglib实现的区别。
一 JDK和CGLIB动态代理原理
1、JDK动态代理
利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,
在调用具体方法前调用InvokeHandler来处理。
2、CGLIB动态代理
利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
3、何时使用JDK还是CGLIB?
1)如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
2)如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
3)如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。
4、如何强制使用CGLIB实现AOP?
1)添加CGLIB库(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)
2)在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
5、JDK动态代理和CGLIB字节码生成的区别?
1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,
并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,
对于final类或方法,是无法继承的。
6、CGlib比JDK快?
1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,
在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,
因为CGLib原理是动态生成被代理类的子类。
2)在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,
只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,
总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。
7、Spring如何选择用JDK还是CGLIB?
1)当Bean实现接口时,Spring就会用JDK的动态代理。
2)当Bean没有实现接口时,Spring使用CGlib是实现。
3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。
- 为什么CGlib方式可以对接口实现代理。
look - final的用途。
final是Java中的一个关键字,这个关键字有着很多种不同的用法,而且在不同的环境下,语义也不尽相同。所以,要想理解好final, 我们就需要将final在Java中的藏身之地一网打尽。
final是一个关键字,在Java中表示为一个修饰符(Modifier),有时候对我自己来说,我也很好奇,这些修饰符是怎么起到作用的,例如我们举一个例子来说,一个被final修饰的类是无法有子类的,那么它为什么不能有子类,修饰符只是在Java语言层面上限制了这个关系,那么程序运行的时候系统是怎么知道的呢?后面也会简单的介绍这个内容。
那么,final既然是一个修饰符,在Java中,final 能修饰哪些东西呢?基本上可以概括的说,在Java中基本可以修饰面向对象的绝大部分元素。我们可以通过如下的代码段来看final修饰的部分。
1.修饰类(Class)
public final class SystemUtils {...}
public class OuterClass { final class InnerClass{...} }
2.修饰方法(Method)
public final void foo() {...}
3.修饰域(Field)
public final int fee = 25;
private static final float POINT_X = 2.6f;
public void foo() { final int type = 3; }
4.修饰方法参数(Method Argument)
public void foo(final int x, final int y) {...}
由上面的代码片段我们可以看出来final关
- 写出三种单例模式实现 。
java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种。
单例模式有以下特点:
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。选择单例模式就是为了避免不一致状态,避免政出多头。
在程序开发中,Service和Dao都可以设置为单例模式(保证线程安全)。
1. 饿汉单例模式
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的。
public class StudentServiceImpl {
private static StudentServiceImpl studentService;
static{ //静态块
System.out.println("-----static 在类加载到JVM中会触发,且触发一次-----");
studentService = new StudentServiceImpl();
}
//为了控制创建对象的数量,将构造方法设为私有
private StudentServiceImpl(){}
//单例模式中,创建对象的方法一定是静态的
public static StudentServiceImpl getInstane(){
return studentService;
}
//主函数测试
public static void main(String[] args) {
System.out.println(StudentServiceImpl.getInstane());
System.out.println(StudentServiceImpl.getInstane());
}
}
测试结果:
-----懒汉模式测试-----
实例被创建
StudentServiceImpl@4c4975
StudentServiceImpl@4c4975
2. 懒汉单例模式
在第一次调用的时候实例化自己。
public class StudentServiceImpl {
private static StudentServiceImpl studentService;
//为了控制创建对象的数量,将构造方法设为私有
private StudentServiceImpl(){
System.out.println("实例被创建");
}
//懒汉模式,在使用时才创建,
public static StudentServiceImpl getInstane(){
if(studentService == null){
studentService = new StudentServiceImpl();
}
return studentService;
}
//主函数测试
public static void main(String[] args) {
System.out.println("-----懒汉模式测试-----");
System.out.println(StudentServiceImpl.getInstane());
System.out.println(StudentServiceImpl.getInstane());
}
}
注意:此时的程序是线程不安全的。
public class StudentServiceImpl {
private static StudentServiceImpl studentService;
//为了控制创建对象的数量,将构造方法设为私有
private StudentServiceImpl(){
System.out.println("实例被创建");
}
//懒汉模式,在使用时才创建,synchronized: 同一事件只能有一个线程进入临界区
public static StudentServiceImpl getInstane(){
//if:为了提升性能
if(studentService == null){
synchronized (StudentServiceImpl.class) { //定制synchronized所属
//if:保证创建对象数量唯一
if(studentService == null){
studentService = new StudentServiceImpl();
}
}
}
return studentService;
}
//主函数测试
public static void main(String[] args) {
System.out.println("-----懒汉模式测试-----");
System.out.println(StudentServiceImpl.getInstane());
System.out.println(StudentServiceImpl.getInstane());
}
}
为了保证线程安全,可以采用以下代码:
3. 登记式单例
类似Spring里面的方法,将类名注册,下次从里面直接获取。
public class Dengjishi {
private static Map<String,Dengjishi> map = new HashMap<String,Dengjishi>();
static{
Dengjishi single = new Dengjishi();
map.put(single.getClass().getName(), single);
}
//保护的默认构造子
protected Dengjishi(){
System.out.println("-----实例对象被创建-----");
}
//静态工厂方法,返还此类惟一的实例
public static Dengjishi getInstance(String name) {
if(name == null) {
name = Dengjishi.class.getName();
System.out.println("name == null"+"--->name="+name);
}
if(map.get(name) == null) {
try {
map.put(name, (Dengjishi) Class.forName(name).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
return map.get(name);
}
public static void main(String[] args) {
System.out.println(Dengjishi.getInstance("Dengjishi"));
System.out.println(Dengjishi.getInstance("Dengjishi"));
}
如何在父类中为子类自动完成所有的hashcode和equals实现?这么做有何优劣。
请结合OO设计理念,谈谈访问修饰符public、private、protected、default在应用设计中的作用。
public: Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包(package)访问。
private: Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的类、属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。
protect: 介于public 和 private 之间的一种访问修饰符,一般称之为“保护形”。被其修饰的类、属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
default:即不加任何访问修饰符,通常称为“默认访问模式“。该模式下,只允许在同一个包中进行访问。
类的只有两种public,default(不同包不可以访问)
public–都可访问(公有)
private–类内可访问(私有)
protected–包内和子类可访问(保护)
不写(default)–包内可访问 (默认)
public>protected>default>private
Java 方法默认访问级别 : 包访问
Java 类默认访问级别 : 包访问对于一个Class的成员变量或成员函数,如果不用public, protected, private中的任何一个修饰,那么该成员获得“默认访问控制”级别,即package access (包访问)。
属于package access的成员可以被同一个包中的其他类访问,但不能被其他包的类访问。
包访问的控制力弱于private,但强于protected。因为一方面,只要是子类,不管子类与父类是否位于同一个包中,那么子类都可以访问父 类中的protected方法。但是一旦位于原类的包外,不管是否是其子类,都无法访问其属于package access级别的成员。而另一方面,一个类可以访问同一个包中另一个类的package access成员,同时也能访问其protected成员。
(注:package是Java中的关键字,虽然包访问也是一种访问控制级别,但关键字”package”只能用来表示类属于哪个包,而不能像”private”,”public”那样放到成员变量或函数前面,作为访问控制修饰符。)
访问级别保护的强度:public<protected<默认<private
- 深拷贝和浅拷贝区别。
1、浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
2、深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
- 数组和链表数据结构描述,各自的时间复杂度。
两种数据结构都是线性表,在排序和查找等算法中都有广泛的应用
各自的特点:
数组:
数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。
链表:
链表恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。比如:上一个元素有个指针指到下一个元素,以此类推,直到最后一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。
数组和链表的区别:
1.从逻辑结构角度来看:
a, 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
b,链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)
2.数组元素在栈区,链表元素在堆区;
3.从内存存储角度来看:
a,(静态)数组从栈中分配空间, 对于程序员方便快速,但自由度小。
b, 链表从堆中分配空间, 自由度大但申请管理比较麻烦.
数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。
- error和exception的区别,CheckedException,RuntimeException的区别。
1.Throwable 类是 Java 语言中所有错误或异常的超类。它的两个子类是Error和Exception;
2.Error 是 Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题。大多数这样的错误都是异常条件。虽然 ThreadDeath 错误是一个“正规”的条件,但它也是 Error 的子类,因为大多数应用程序都不应该试图捕获它。在执行该方法期间,无需在其 throws 子句中声明可能抛出但是未能捕获的 Error 的任何子类,因为这些错误可能是再也不会发生的异常条件。
3.Exception 类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。
4.RuntimeException 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。可能在执行方法期间抛出但未被捕获的RuntimeException 的任何子类都无需在 throws 子句中进行声明。它是Exception的子类。
5.方法重写时:在子类中一个重写的方法可能只抛出父类中声明过的异常或者异常的子类
二者的不同之处:
Exception:
1.可以是可被控制(checked) 或不可控制的(unchecked)
2.表示一个由程序员导致的错误
3.应该在应用程序级被处理
Error:
1.总是不可控制的(unchecked)
2.经常用来用于表示系统错误或低层资源的错误
3.如何可能的话,应该在系统级被捕捉
- 请列出5个运行时异常。
ClassCastException(类转换异常)
IndexOutOfBoundsException(数组越界)
NullPointerException(空指针)
ArrayStoreException(数据存储异常,操作数组时类型不一致)
还有IO操作的BufferOverflowException异常
在自己的代码中,如果创建一个java.lang.String类,这个类是否可以被类加载器加载?为什么。
说一说你对java.lang.Object对象中hashCode和equals方法的理解。在什么场景下需要重新实现这两个方法。
这里有一个约定:hashCode相等,对象不一定相等,对象相等,hashCode一定相等。
为什么需要hashCode?
1、 在map等集合中,通过hashCode可以很快的找到元素的位置
2、比较两个对象是否相等时,先通过hashCode比较,这样速度比较快,如果不相等直接返回false
为什么要重载equal方法?
Object对象默认比较的是两个对象的内存地址是否一样,正常大家应该比较的是对象里面的值是否一样。
为什么重载hashCode方法?
如果我们只重写equals,而不重写hashCode方法,就会出现两个对象一样,但是hashCode不相等情况,在map等集合中应用时,就会出现问题,因为hashCode不一样,两个一样的对象会放到集合中。
- 在jdk1.5中,引入了泛型,泛型的存在是用来解决什么问题。
类型的参数化,就是可以把类型像方法的参数那样传递。这一点意义非凡。
泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常。
- 这样的a.hashcode() 有什么用,与a.equals(b)有什么关系。
hashcode()方法提供了对象的hashCode值,是一个native方法,返回的默认值与System.identityHashCode(obj)一致。
通常这个值是对象头部的一部分二进制位组成的数字,具有一定的标识对象的意义存在,但绝不定于地址。
作用是:用一个数字来标识对象。比如在HashMap、HashSet等类似的集合类中,如果用某个对象本身作为Key,即要基于这个对象实现Hash的写入和查找,那么对象本身如何实现这个呢?就是基于hashcode这样一个数字来完成的,只有数字才能完成计算和对比操作。
hashcode是否唯一
hashcode只能说是标识对象,在hash算法中可以将对象相对离散开,这样就可以在查找数据的时候根据这个key快速缩小数据的范围,但hashcode不一定是唯一的,所以hash算法中定位到具体的链表后,需要循环链表,然后通过equals方法来对比Key是否是一样的。
equals与hashcode的关系
equals相等两个对象,则hashcode一定要相等。但是hashcode相等的两个对象不一定equals相等
有没有可能2个不相等的对象有相同的hashcode。
Java中的HashSet内部是如何工作的。
什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决。
## 用来处理对象流
简单来说序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化,流的概念这里不用多说(就是I/O),我们可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间**(注:要想将对象传输于网络必须进行流化)**!在对对象流进行读写操作时会引发一些问题,而序列化机制正是用来解决这些问题的!
## 问题的引出:
如上所述,读写对象会有什么问题呢?比如:我要将对象写入一个磁盘文件而后再将其读出来会有什么问题吗?别急,其中一个最大的问题就是**对象引用**!
举个例子来说:假如我有两个类,分别是A和B,**B类中含有一个指向A类对象的引用**,现在我们对两个类进行实例化{ A a = new A(); B b = new B(); },这时在内存中实际上分配了两个空间,一个存储对象a,一个存储对象b,**接下来我们想将它们写入到磁盘的一个文件中去**
就在写入文件时出现了问题!**因为对象b包含对对象a的引用**,所以系统**会自动的将a的数据复制一份到b中**,这样的话当我们从文件中恢复对象时(也就是重新加载到内存中)时,**内存分配了三个空间**,而对象a同时在内存中存在两份,想一想后果吧,如果我想修改对象a的数据的话,**那不是还要搜索它的每一份拷贝来达到对象数据的一致性,这不是我们所希望的**!
### 以下序列化机制的解决方案:
1.保存到磁盘的所有对象都获得一个序列号(1, 2, 3等等)
2.当要保存一个对象时,先检查该对象是否被保存了。
3.如果以前保存过,只需写入"与已经保存的具有序列号x的对象相同"的标记,否则,保存该对象
通过以上的步骤序列化机制解决了对象引用的问题!
## 序列化的实现
将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。
- java8的新特性。
look