Java基础面试题(一)
1、string、Stringbuffer、StringBuilder的区别
- 可变性:String是不可变的,即一旦一个String对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。
- 线程安全:String由于是final修饰,线程安全;StringBuffer是由Sychronoied修饰的,线程安;StringBuilder线程不安全。
- 性能:String由于是常量修饰不可变,每次做字符串拼接或修改的时候,都需要创建新的对象并且分配内存空间,性能最差;Stringbuffer其次,由于其可变性,字符串可以直接被修改,性能最高的是StringBuilder,StringBuffer加了同步锁,会对性能产生影响。
- 存储:String存在字符串常量池里,StringBuffer和StringBuilder存储在读内存中。
2、io的分类
- 按照方向分类
1.1 输入流
1.2 输出流 - 按照单位分类
2.1 字节流
2.2 字符流 - 按照功能分类
3.1节点流
3.2 过滤流
3、线程的实现方式
1.继承Thread类创建线程
2. 实现Runable接口
3. 实现 Callable接口
4. 利用线程池创建
4、Arraylist和linkedlist的区别
ArrayList:基于动态数组、连续内存存储,适合下标访问
扩容机制:数组长度固定,超出长度数据时新建数组,老数组copy到新数组,使用尾插法并指定初始容量可以提升性能,甚至查过LinkedList(需要大量创建node对象)LinkedList:基于 链表,可以存储在分散的内存中,适合做插入及删除操作,还是和查询,需要注意遍历,必须使用iterator(迭代器),不能使用for循环(for循环、get(i))取得某一元素都需要List重新遍历,性能消耗极大。
拓展:
不要试图使用indexOf等返回运输索引,并利用其进行 遍历,使用indexOf对 list进行遍历,当结果为null时,会遍历整个列表。
5、集合基类,下面子类 ,list和set的区别
collection(存放单列数据)
List 存放数据允许重复
2.1.1 Arraylist 底层基于数组数据结构实现 默认初始化容量为10 保证了数据的有序性(数组的特性就是可以保证有序性)
2.1.2 LinkedList 底层基于链表数据结构实现 保证了数据的有序性set 存放数据不允许重复 set集合对数据去重
3.1Hashset 不允许存放重复数据 底层基于map实现Map(存放队列数据 key value)
3.1HashMap 底层 基于数组+链表实现(JDK1,7) 基于数组+链表+红黑树实现(JDK1,8)
3.2HashTable 线程安全 底层同HashMap
3.3concurrentHashMap 线程安全,数据结构:ReetrantLock(对HashEntry数组 加锁)+segment+HashEntry(JDK1.7) Sychronized(对头结点加锁)+CAS+Node+红黑树(JDK1.8)
拓展 :
HashTable采用的是全表锁,当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态。
concurrentHashMap1.7的时候,采用的是分段锁segment(包含一个HashEntry数组) ,每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。1.8之后采用Node节点,每一个 HashEntry为一个Node节点(Node(两种分别是ForwardingNode和 ReservationNode)的val和next都用volatile修饰,保证可见性)),Node只存在于链表 结构,红黑树情况需要使用TreeNode(在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N))))。
concurrentHashMap中的get方法是无锁获取,提高了其性能,其修改或者写入的时候,通过volatile来确保其可见性(其声明的储存数据的table前是有volatile修饰的)。
6、链表的数据结构是什么样的
单向链表的节点(Node)分为两个部分,第一个部分(data)保存或者显示关于节点的信息,另一个部分(next)存储下一个节点的地址。最后一个节点存储地址的部分指向空值。单向链表 只可以 一个方向遍历。
双向链表每个链结点既有下一个链结点的引用,也有上一个链结点的引用。
循环链表尾节点保留着头节点的地址,其他同单向链表。
有序链表按照特性的顺序有序排列(递增或者递减)
7、hashmap和hashTable的区别
相同点:
1.HashMap和HashTable都是java.util包下的类
2.HashMap和HashTable都实现了Map接口,存储方式都是key-value形式
3.HashMap和HashTable都实现了Serializable和Cloneable接口
4.HashMap和HashTable负载因子都是0.75
5.HashMap和HashTable部分方法相同,比如put、remove等方法。不同的点:
1.HashMap是非线程安全的,HashTable是线程安全的
2.HashMap允许null作为键或者值的,HashTable不允许–运行时报空指针
3.HashMap添加元素使用的是自定义hash算法,HashTable使用的key的hashCode
4.HashMap在数组+链表的结构中引入了红黑树,HashTable没有
5.HashMap的初始容量为16,HashTable的初始容量为11
6.HashMap扩容时当前容量翻倍,HashTable是翻倍+1
7.HashMap只支持iterator遍历,HashTable支持iterator和Enumeration
8.HashMap与HashTable部分方法不同,比如HashTable有contains方法。拓展:
1.concurrentHashMap,线程安全,采用分段锁
2.jdk7中,数据结构为:ReetrantLock+segement+HashEntry,一个segment包含一个HashEntry数组,每一个hashEntry又是一个链表
3.jdk8中,数据结构为:Sychronized+CAS(乐观锁)+Node+红黑树,Node(两种分别是ForwardingNode和 ReservationNode)的val和next都用volatile修饰,保证可见性)。
8、锁有哪些,以及区别
synchronized和ReetrantLock
相同点:
1.1 都需要锁定对象不同点:
1.1 synchronized锁定之后,A线程 不释放,B会一直等待下去。
1.2 synchronized在代码执行出现异常时,当前线程会中断
1.3 synchronized在竞争不激烈的情况下,性能更佳,可读性好。
1.4 ReetrantLock锁定之后,A线程不释放,B在一定时间之后会中断等待,继续其他工作。
1.5 Lock需要通过代码才能保证一定被释放,必须将unLock()方法放在finally{}中 。
1.6 ReetrantLock在竞争激烈的情况下,任然可以 保持较好的性能(相对synchronized竞争激烈情况下更优)。拓展:
ReentrantLock获取锁定与三种方式:
(a)lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁.
(b)tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false。
©tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false。
(d)lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断。死锁
悲观锁和乐观锁(详情见第九题)
偏向锁、轻量级锁、重量级锁
自旋锁、自适应锁
9、什么死锁?悲观锁和乐观锁?
死锁,在两个或多个并发进程中,如果每个进程持有某种资源而又都等待着别的进程释放它或它们现在保持着的资源,否则就不能向前推进,此时每个进程都占用了一定的资源但又都不能向前推进,称这一组进程产生了死锁。
解决方案:
1.1 任一进程预先申请全部资源,避免运行期间 ,再次申请其他资源。
1.2 进程申请新资源得不到立即满足时,释放保持的资源,待需要时再次申请。
1.3 采用银行家算法(安全状态以及非安全状态 )预防死锁。悲观锁,当一个线程使用数据时,悲观锁总是认为其它线程也会过来修改这个数据。为了保证数据安全,其采用的是一种先加锁再访问的策略,其它线程要想也访问该数据则被阻塞等待、所以悲观锁也是重量锁,直到其获取到锁才可以访问。典型的,Java中的synchronized锁就是悲观锁
乐观锁,在使用数据的过程中其它线程不会修改这个数据,故不加锁直接访问。而当该线程需要提交更新、修改时,才会判断该数据在此期间有没有被其它线程更新、修改。如果其它线程确实没有修改,则该线程直接写入完成更新;反之如果该数据已经被其它线程更新、修改,则该线程将放弃本次数据的更新提交操作以避免出现冲突,并通过报错、重试等方式进行下一步处理。
拓展:CAS算法(乐观锁的一种典型实现)
(a)在CAS(比较与交换)算法中涉及3个操作数:变量当前内存值V、变量的预期值E、新值U。只有该变量当前的内存值V与预期值E相同时,才会将新值U写入内存完成变量修改,否则什么都不做。CAS通过该变量的地址即可获取该变量当前的内存值V。当本轮CAS操作失败后,会重新读取该变量内存中最新的值并重新计算新值,直到其CAS操作修改变量成功为止。(b) ABA问题,在CAS操作中,判断变量是否被其他线程修改,是通过比较当前内存值V和预期值E来完成的。现在考虑这样一个场景,线程1读到某变量的值为A,在其计算新值的过程中,另外一个线程2已经将该变量的值从A先修改为B、然后又将其从B修改回A。此时,当线程1通过CAS操作进行新值写入虽然可以成功,而实际上线程1执行CAS操作时 预期值的A 和 读取该变量当前值的A 已经不是"同一个"了,因为后者是线程2修改的。
解决办法:可以通过给变量附加时间戳、版本号等信息来解决。
10、java 反射机制原理、以及场景
Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
优点:
在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
缺点:
1. 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
2.反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
- 用途:
- 反编译:.class——>.java
- 通过反射机制访问java对象的方法、属性、构造方法等(例如:我们写代码时,当我们输入一个对象或者类,在后面输入点,编译器就会自动列出其属性和方法)。
- 反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。(了解,不强制)
- 加载数据库驱动。(了解,不强制)
11、线程池的理解
线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务
优势:
1.1线程和任务分离,提高线程的重用性
1.2 控制并发线程数,降低服务器压力,统一管理线程。
1.3提升系统响应速度。
应用场景:
2.1.网购秒杀、云盘上下载、12306购票等
2. 2ThreadPoolExecutor相关参数
2.3 构造方法:
public ThreadPoolExecutor(int corePoolSize, //核心线程数量
int maximumPoolSize,// 最大线程数
long keepAliveTime, // 最大空闲时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 饱和处理机制
)
参数详情
1. 核心线程数(corePoolSize)
2. 核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定,例如:执行一个任务需要0.1秒,系统百分之80的时间每秒都会产生100个任务,那么要想在1秒内处理完这100个任务,就需要10个线程,此时我们就可以设计核心线程数为10;当然实际情况不可能这么平均,所以我们一般按照8020原则设计即可,既按照百分之80的情况设计核心线程数,剩下的百分之20可以利用最大线程数处理;
3. 任务队列长度(workQueue)
4. 任务队列长度一般设计为:核心线程数/单个任务执行时间*2即可;例如上面的场景中,核心线程数设计为10,单个任务执行时间为0.1秒,则队列长度可以设计为200;
5. 最大线程数(maximumPoolSize)
6. 最大线程数的设计除了需要参照核心线程数的条件外,还需要参照系统每秒产生的最大任务数决定:例如:上述环境中,如果系统每秒最大产生的任务是1000个,那么,最大线程数=(最大任务数-任务队列长度)*单个任务执行时间;既: 最大线程数=(1000-200)*0.1=80个;
7. 最大空闲时间(keepAliveTime)
8. 这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值,用户可以根据经验和系统产生任务的时间间隔合理设置一个值即可;
12、重写和重载的区别
- 重写:参数列表与被重写的方法相同,返回类型也须一致,访问修饰符限制大于被重写的方法,抛出异常不能抛出新的异常或者更加宽泛的检查异常(重写:IOException;被重写Exception)。
- 重载:必须不同的参数列表,可以不同 的返回类型,可以有不同的访问修饰符,可以抛出部不同的检查异常。
- 重写是父类与子类之间的多态性的表现,在运行时作用(动态多态性;如:动态绑定)
- 重载是一个类中多态性的表现,在编译时作用(静态多态性;如:静态绑定)
13、javaEE(用于分布式的网络程序的开发)的理解
JavaEE不是语言而是一种结构,一种标准。JavaEE是为了简化和规范化分布式多层企业应用的开发和部署,简言之就是为简化而做的约定。JavaEE的主要作用就是让企业级应用的开发更容易且规范。
技术结构:
2.1 表示层技术是在JSP页面中用HTML标签、JavaScript脚本等。主要功能是向服务器端发送请求,处理数据或者根据返回的数据重新显示页面。
2.2中间层技术主要是JSP、Servlet、JSTL、JavaBean、Struts框架。其中JSP是显示动态内容的服务器网页,Servlet是接收客户端的请求并作出响应,JSTL是帮助JSP显示动态内容的标准标签库,JavaBean是JavaEE的模型组件。需要注意的是,Struts框架技术主要是扩充了Servlet。在中间层中,Servlet是重要的组成部分,它控制着其它的组件。
2.3 数据层技术用的是JDBC、JNDI、Hibernate框架。其中JDBC是如今流行的数据库连接池,现在流行的是关系型的数据库,我们所用的大多数都是,如:SQL Server、Oracle、MySQL等。
14、==和equals的区别
==可以比较基本数据类型,也可以比较引用类型
比较基本数据类型,比较的是变量里面的存储的值
比较引用类型,比较的是变量里面存储的地址引用equals()是object类默认存在的方法
所有引用类型的对象都可调用,
其默认比较的规则就是==,所以对象调用默认比较的是地址引用
如果想要修改默认的equals()方法比较规则,可以进行方法重写
比如String类、Integer等,就重写了equals()改为比较内容
当然,重写equals()方法时,通常会伴随着重写hashCode()方法。
15、对于异常的理解
异常Throwable体系架构
1.1Error,严重错误Error,无法通过处理的错误,只能事先避免。1.2Exception,表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。
(a)运行时异常,runtime异常。在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)。例如:ArrayIndexOutOfBoundsException;NullPointerException;ClassCastException(强转异常)。
(b)非运行时异常:checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。例如:IOException常用方法
3.1 printStackTrace():打印异常的详细信息
3.2 getMessage():获取发生异常的原因。
3.3toString():获取异常的类型和异常描述信息(不用)。异常的处理
3.1throw与throws
(a)throw——>手动引发一个自定义异常
(b)throws——>抛出异常给上一级(谁调用谁是上一级)
3.2 try-catch
3.3finally关键字,无论是否有异常都会执行。
16、设计模式
- 单例模式(懒汉、恶汉):保证一个类只有一个实例,并提供一个访问它的全局访问点。
- 工厂模式:又叫做静态工厂方法,定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂模式使得一个类的实例化延迟到子类。
- 适配器模式:将一个类的接口转换成客户希望的另外一个接口。该模式使得原本接口不兼容而不能一起工作的类可以一起工作。
- 装饰器模式:动态的给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更加灵活。
- 代理模式:为其他对象提供一种代理以控制对这个对象的访问。
- 享元模式:为运用共享技术有效地支持大量细粒度的对象。
- 策略模式:定义一些列算法,把他们一个个封装起来,并且使他们可以相互替换。本模式使得算法可以独立于使用它的客户而变化。
- 观察者模式:定义对象间一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都得到通知并被自动更新。
- 迭代器模式:提供一种方法顺序访问一个聚合对象种的各个元素,而又不许暴露该对象的内部表示。