Java基础知识

1 String和StringBuffer。

这是Java中基础的基础了。String显然也不是Java中的基础数据类型,他是一个final类型的类,不可再被继承。String中的内容不允许更改,故如果改变了字符串的值,实际上是又在内存中开辟了一块空间来存储新的字符串。而StringBuffer则不同,他支持对内容的修改。所以如果对字符串有修改的需求时,请使用StringBuffer。StringBuffer是线程安全的,所以在性能上不及String。利弊都已陈述,根据需要自行选择。

 

2 ArrayList,vector,LinkedList的性能和特性。

1> 数据查找方面:ArrayList和vector都是使用数组的方式存储数据,可以直接根据索引进行定位,查找速度很快;LinkedList是基于双向链表,不能直接使用索引号定位(如使用get(4)不是直接通过索引获取值,而是通过链表从头查找,查找到第四个),需要使用指针前后移动进行元素查找,速度较慢。两者提供的方法差不多,linkedList多了addFirst/last removeFirst/last。

2> 数据插入和删除方面:ArrayList和vector在插入时设计元素的移动问题,比较慢;而LinkedList只需要处理一下指针指向即可,速度快。另外ArrayList和vector还存在一个扩容的问题。当数据元素数已经等于实际存储的元素数时再插入数据就需要进行扩容。ArrayList会按照公式int newCapacity(新容量) = (oldCapacity * 3) / 2 + 1;进行扩容,差不多就是每次增加50%,vector可以根据设定的增长因子进行扩容,通过设置capacityIncreaement字段,默认是增长1倍。

3> 线程安全方面:这三者中只有vector是线程安全的(使用sychronized修饰),所以性能比ArrayList差。

Collections下有一个synchronizedCollection(collection c)可以将任意一个集合转为线程安全的集合!

 

3 list和map区别。

list用来存储单列数据,map用来存储键值对类型的数据。list中存储的数据有序且允许重复,而map中的数据无序,键不能重复,值可以。list中可以放null,set不可以。

 

4 HashMap和HashTable的区别。

(key重复会覆盖)

value都不允许为空!

HashMap是Map接口的实现,非线程安全,多线程访问时需要加锁,允许空键值。

HashTable继承自Dictionary类,线程安全,多线程访问不需要加锁,不允许空键值(会报错NullPointerException)。

如果你不需要线程安全,那么使用HashMap,如果需要线程安全,那么使用ConcurrentHashMap。HashTable已经被淘汰了,不要在新的代码中再使用它。

HashTable默认的初始大小为11,之后每次扩充为原来的2n+1。HashMap默认的初始化大小为16,之后每次扩充为原来的2倍。

hashtable和hashmap都是用链地址法(哈希桶)来解决地址冲突。

初始值threshold默认为16,当构建完数组后,threshold为阈值,等于加载因子*数组长度,如数组长度为16,当threshold达到16*0.75时,就扩容,数组长度为之前的两倍。

关于hashmap的原理及源码解析:

https://blog.csdn.net/qq_32563713/article/details/81463078 (重要)

http://www.cnblogs.com/skywang12345/p/3310835.html

jdk1.7/1.8 的hashmap及concureentHashmap:

http://www.importnew.com/28263.html

hash桶(拉链法解决hash冲突):

https://blog.csdn.net/abm1993/article/details/80886058

HashMap将对象作为key为什么需要重写equals和hashcode方法:

https://blog.csdn.net/zyq1013906061/article/details/72868358

LinkedHashMap是hashMap的子类,HashMap有一个问题,就是迭代HashMap的顺序并不是HashMap放置的顺序,也就是无序,LinkedHashMap它虽然增加了时间和空间上的开销,但是通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序。该迭代顺序可以是插入顺序或者是访问顺序

treemap 使用二叉树来存储键值,且对key是排序的,可以自己定义排序规则,同treeset。

 

treeset和hashset (hashcode->equals)

 

 

5 final,finally, finalize的区别。

final用于声明属性、方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。

finally是异常处理语句结构的一部分,表示总是执行。

finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,如关闭文件等。

 

6 sleep()和wait()的区别。

sleep是Thread类的方法啊,导致此线程暂停执行指定的时间,把执行机会让给其他的线程,但是监控状态依然保持,到时后自动回复。调用本函数不会释放对象锁。

wait是Object类的方法,调用本函数会导致线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法或notifyAll后,本线程才进入对象锁定池准备获得对象锁进入运行状态。

 

7 重载和重写。

方法的重写和重载是Java多态性的不同表现。重写是父类和子类之间多态性的一种表现:在子类中定义某方法与其父类有相同的名称和参数,子类的对象在使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被屏蔽。重载是一个类中多态性的一种表现:在一个类中定义了多个同名的方法,他们或有不同的参数个数,或有不同的参数类型,通过重载可以改变方法的返回值类型。

8 GC是什么,为什么要有GC。

GC是Gabage Collection的简称。在Java中,没有提供释放已分配资源的方法,由JVM自动检测对象是否超过作用域,从而达到回收内存的机制。

 

9 多线程有几种实现方法?同步有几种实现方法?

多线程有两种实现方法,分别是继承Thread类和实现Runnable接口。

1> 继承Thread类

public class MyThread extends Thread{

@overide

public void run(){

System.out.println("execute func");

}

public static void main(String[] args){

MyThread thread1 = new MyThread();

MyThread thread2 = new MyThread();

thread1.start();

thread2.start();

}

}

 

2> 当MyThread类已经继承了其他类时,由于Java语言的限定,我们不能多继承,要实现多线程就可以通过实现Runnable接口的方式。

public class MyThread extends OtherClass implements Runnable{

public void run(){

System.out.println("execute func");

}

public static void main(String[] args){

MyThread thread1 = new MyThread();

Thread thread = new Thread(thread1);

thread.start();

}

}

请注意调用MyThead的方式,与继承Thread的方式是不同的。

 

Java中的线程有4中状态,分别是运行、就绪、挂起和结束。

同步的实现方法有两种,分别是synchronized, java.util.concurrent.locks.lock。lock的锁定是通过代码实现的,而synchronized是通过jvm实现的。synchronized在锁定时,如果发生异常,jvm会自动将锁释放掉,不会因为异常没有释放锁导致线程死锁。而lock在出现异常时,必须在finally中将锁释放掉,否则会死锁。lock的性能比synchronized好。

 

10 垃圾回收的基本原理是什么?

对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆中的所有对象,通过这种方式确定有哪些对象是可达的,哪些对象是不可达的。当GC确定一些对象不可达时,GC就会收回这些内存空间。

程序员也可以手动执行System.gc()通知GC运行。但是java的语言规范并不保证GC一定会执行。

 

11 Java匿名内部类

匿名内部类就是没有名字的内部类。因为没有名字,所以只可以使用一次,目的是为了减少代码的编写。匿名内部内必须继承一个抽象类或者实现一个接口。

不是用匿名内部类的实现:

 

abstract class Person {

    public abstract void eat();

}

 

class Child extends Person {

    public void eat() {

        System.out.println("eat something");

    }

}

 

public class Demo {

    public static void main(String[] args) {

        Person p = new Child();

        p.eat();

    }

}

 

 

事实上Child只使用了一次,我们完全可以把它搞成一个匿名内部类:

 

abstract class Person {

    public abstract void eat();

}

 

public class Demo {

    public static void main(String[] args) {

        Person p = new Person() {

            public void eat() {

                System.out.println("eat something");

            }

        };

        p.eat();

    }

}

二者功能相同。

12 类文件结构

class类文件,亦称字节码文件,是由虚拟机规范规定了其结构形式的文件。class文件是一组以8位为基础单位的二进制流,哥哥数据项目严格按照顺序紧凑排列在class文件中,中间没有任何分隔符,以保证整个class文件中存储的内容全部是程序运行的必要数据,没有空隙。当遇到需要占用8位字节以上的空间数据时,会按照高位在前的方式分割成若干上8位字节进行存储。

Class文件格式中只有两种数据类型:无符号数和表。无符号数属于基本的数据类型,以u1、u2、u4、u8分别代表1、2、4、8个字节的无符号数。无符号数可以用来描述数字、索引引用、数量值、或者按照UTF-8编码构成字符串值。表是多个无符号数或者其他表作为数据项构成的符合数据类型,所有表都习惯性地以"_info"结尾。无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式,称这一系列连续的某一类型的数据为某一类型的集合。

虚拟机必须判定输入的文件是不是一个Class文件,其依据是文件的首4个字节的魔数(0xCAFEBABE)。虚拟机判断Class能够否被兼容,依据的是文件的第5~8个字节。

 

13 java的类加载机制?

虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被直接使用的Java类型,这就是虚拟机的类加载机制。

类的生命周期包括加载(Loading),验证(Verification),准备(Preparation),解析(Resolution),初始化(Initialization),使用(Using),卸载(Unloading)等7个阶段,其中验证、准备和解析三个部分统称为链接(Linking)。而类的加载指的是从加载到初始化这5个阶段。

这七个阶段的顺序除了解析阶段和使用阶段之外,其他几个阶段的开始顺序是确定的,必须按照这种顺序按部就班的开始,但不要求按这种顺序按部就班的完成。这些极端通常是相互交叉地混合式进行的,通常会在一个阶段执行的过程中调用或激活另外一个阶段。解析阶段在某些情况下可以在初始化阶段之后再开始,以支持Java的运行时绑定(RTTI),而使用阶段则是按类文件内容的定义的不同而在不同的阶段进行。

虚拟机规范对于何时进行加载这一阶段并没有强制约束,但对于初始化阶段,虚拟机规范是严格规定了有且只有四种情况必须立刻对类进行初始化:

1> 遇到new, getstatic, putstatic, 或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这四条指令的场景是:使用new关键字实例化对象;读取或设置一个类的静态字段;调用一个类的静态方法。当然,被final修饰并在编译期就把结果放入常量池的静态字段不属于这些场景,这类静态字段的值在编译期时就会被编译器优化而直接放入常量池,其引用直接指向其在常量池的入口。

2> 使用java.lang.reflect包的方法对类进行反射调用时,如果类没有进行过初始化,则需要先触发其初始化。

3> 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

4> 当虚拟机启动时,用户需要制定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类。

以上四种场景中的行为称为对一个类进行主动引用,除此之外所有引用类的方式都不会触发初始化,称为被动引用。

接口的加载过程与类的加载过程最主要的区别在于第三点,即当初始化一个接口时,并不需要先初始化其父接口,而是只有真正使用到父接口中的字段的时候才会初始化。

类加载的各个阶段简单说明:

1> 加载阶段,虚拟机主要完成三件事:通过一个类的全限定名来获取定义此类的二进制字节流,将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,在Java堆中生成一个代表这个类的java.lang.class对象,作为方法区这些数据的访问入口。

2> 验证阶段,不同虚拟机会进行不同类验证的实现,但大致都会完成以下四个阶段的校验过程:文件格式验证(验证字节流是否符合class文件格式的规范,并能被当前版本的虚拟机处理);元数据验证(对字节码描述信息进行语义分析,保证其描述信息符合java语言规范);字节码验证(对类方法体进行数据流和控制流分析,保证类的方法在运行时不会做出危害虚拟机的行为);符号引用验证(发生在将符号引用转化为直接引用的时候,在解析阶段中发生)。

3> 准备阶段,正式为类成员变量(注意,不是实例成员变量,实例变量会在对象实例化时随着对象一起分配在java堆上),分配内存并设置类变量初始值(通常情况下是数据类型的零值,不进行赋值操作)的阶段,这些内存都将在方法区中进行分配。

4> 解析阶段,虚拟机将常量池内的符号引用替换为直接引用的过程。符号引用与内存布局无关,而直接引用的目标必定已经在内存中存在。解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行。

5> 初始化阶段,真正开始执行类中定义的java程序代码(字节码),是执行类构造器<clinit>()方法的过程。

<clinit>方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量。

<clinit>方法与类的构造函数(或者说实例构造器<init>方法)不同,它不需要显示的调用父类构造器,虚拟机会在子类的<clinit>方法执行之前完成父类<clinit>方法的执行,因此父类中定义的静态语句块要先于子类的变量赋值操作。

<clinit>方法对于类或接口来说不是必须的,如果一个类中没有静态语句块,也没有对变量的赋值操作,则编译器可以不为这个类生成<clinit>方法。

接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成<clinit>方法,不同于类的地方是执行接口的<clinit>方法时不用先执行父类的<clinit>方法。

虚拟机会保证一个类的<clinit>方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,则只有一个线程去执行这个类的<clinit>方法,其他线程阻塞等待,直到活动线程执行<clinit>方法完毕。

 

14 类加载器的作用及类别?

类加载器只用于实现类的加载动作,即实现通过一个类的全限定名来获取此类的二进制字节流。但对于类来说,要判断两个类是否相等(instanceof, equal),其前提是两个类是由同一个类加载器所加载的,否则无论两个类是否来源于同一个class文件,这两个类都必定不等,即,对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机的唯一性。

在Java开发人员看来,类加载器可划分为以下三类系统提供的类加载器:

1> 启动类加载器(Boostrap ClassLoader),负责将存放在<JAVA_HOME>\lib目录中的类库加载到虚拟机内存中,其无法被java程序直接引用。

 

2> 扩展类加载器(Extension ClassLoader),由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>\lib\ext目录中的类库,可被开发者直接使用。

3> 应用程序类加载器,由sun.misc.Launcher$AppClassLoader来实现,负责加载用户类路径(ClassPath)上指定的类库,可被开发者直接使用,且为默认的类加载器。

java中采用双亲委派模型(Parents Delegation Model)来实现类的加载模式。双亲委派模型除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围没有找到所需的类)时,子加载器才会自己去加载。如下图所示:

 

18 Java序列化

可以通过序列化来保存一个对象的状态(实例变量)到文件中,也可以从这个格式化的文件中很容易地读取对象的状态从而可以恢复保存的对象。

ObjectOutputStream:输出流并序列化对象。 
ObjectInputStream:读取流并反序列化对象。 
Serializable:一个对象要想被序列化,那么它的类就要实现此接口。

示例代码如下:

1> Book.java 
这里写图片描述

2> Student.java 
这里写图片描述

3> Simulator.java 
这里写图片描述

代码运行结果如下: 
这里写图片描述

总结如下:

1> 基本类型的数据可以直接序列化。 
2> 对象要被序列化,它的类必须要实现Serializable接口;如果一个类中有引用类型的实例变量,这个引用类型也要实现Serializable接口。在上述例子中,Student类有一个Book类型的实例,如果想让Student的对象成功序列化,那么Book也必须要实现Serializable接口。当一个父类实现序列化时,子类自动实现序列化,不需要显示实现Serializable接口。 
3> static, transient后的变量不能被序列化。

19 Java序列化与JSON序列化 
Java观点:Object2Object,使用时简单快速,由于是二进制,序列化的数据小。 
JSON观点:JSON格式与语言无关,扩展性强。

以上是传统观点,经过大牛的测试之后,JSON-smart比java序列化更吊。所以究竟谁更优秀还有待考究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值