Java基础知识1-10
测试要点
- 一、Java基础
- 1.常用设计模式有哪些?在项目中有哪里用的到?单例中懒汉饿汉优缺点?
- 2.反射的原理?获取class对象的方式有哪些? 如何用反射取私有属性filed?
- 3.jvm垃圾回收机制和jvm调优?
- 4.线程的创建、开启、状态、状态切换?sleep和wait的区别?如何保证线程安全?为什么使用多线程?线程池的了解?
- 5.集合有哪些?哪些是线程安全的集合?hashmap的底层原理?hashMap、hashTable、currenthashmap区别?ArrayList、vector、linkedlist的区别?
- 6.jvm内存模型的理解?
- 7 抽象类与接口的区别?
- 8 volatile与synchronized的区别?
- 9 重写与重载的区别?
- 10 java中字符串的方法有哪些?string、stringbuffer和stringbuilder的区别?
一、Java基础
1.常用设计模式有哪些?在项目中有哪里用的到?单例中懒汉饿汉优缺点?
软件设计模式分为三类分别为创建型、结构型、行为型。
1.1创建型
创建对象时,不再由我们直接实例化对象;而是根据特定场景,由程序来确定创建对象的方式,从而保证更大的性能、更好的架构优势。创建型模式主要有简单工厂模式(并不是23种设计模式之一)、工厂方法、抽象工厂模式、单例模式、生成器模式和原型模式。
1.1.1单例模式(singleton)
有些时候,允许自由创建某个类的实例没有意义,还可能造成系统性能下降。如果一个类始终只能创建一个实例,则这个类被称为单例类,这种模式就被称为单例模式一般建议单例模式的方法命名为:getInstance(),这个方法的返回类型肯定是单例类的类型了。getInstance方法可以有参数,这些参数可能是创建类实例所需要的参数,当然,大多数情况下是不需要的
public class Singleton {
public static void main(String[] args)
{
//创建Singleton对象不能通过构造器,只能通过getInstance方法
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
//将输出true
System.out.println(s1 == s2);
} private static Singleton instance;
//将构造器使用private修饰,隐藏该构造器
private Singleton(){
System.out.println("Singleton被构造!");
}
//提供一个静态方法,用于返回Singleton实例
//该方法可以加入自定义的控制,保证只产生一个Singleton对象
public static Singleton getInstance()
{
//如果instance为null,表明还不曾创建Singleton对象
//如果instance不为null,则表明已经创建了Singleton对象,将不会执行该方法
if (instance == null)
{
//创建一个Singleton对象,并将其缓存起来
instance = new Singleton();
}
returninstance;
}
}
(1)、懒汉式(上面的就是懒汉式写法)
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
(2)、饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
懒汉式和饿汉式的优缺点
1、饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不在改变。懒汉式如果在创建实例对象时不加上synchronized则会导致对对象的访问不是线程安全的。
2、懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。
饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。
单例模式主要有如下两个优势:
-
减少创建Java实例所带来的系统开销
-
便于系统跟踪单个Java实例的生命周期、实例状态等。
1.1.2 简单工厂(StaticFactory Method)
简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。
A实例调用B实例的方法,称为A依赖于B。如果使用new关键字来创建一个B实例(硬编码耦合),然后调用B实例的方法。一旦系统需要重构:需要使用C类来代替B类时,程序不得不改写A类代码。而用工厂模式则不需要关心B对象的实现、创建过程
使用简单工厂模式的优势:让对象的调用者和对象创建过程分离,当对象调用者需要对象时,直接向工厂请求即可。从而避免了对象的调用者与对象的实现类以硬编码方式耦合,以提高系统的可维护性、可扩展性。工厂模式也有一个小小的缺陷:当产品修改时,工厂类也要做相应的修改。
1.1.3 工厂方法(Factory Method)和抽象工厂(Abstract Factory)
如果我们不想在工厂类中进行逻辑判断,程序可以为不同产品类提供不同的工厂,不同的工厂类和产不同的产品。
当使用工厂方法设计模式时,对象调用者需要与具体的工厂类耦合使用简单工厂类,需要在工厂类里做逻辑判断。而工厂类虽然不用在工厂类做判断。但是带来了另一种耦合:客户端代码与不同的工厂类耦合。
为了解决客户端代码与不同工厂类耦合的问题。在工厂类的基础上再增加一个工厂类,该工厂类不制造具体的被调用对象,而是制造不同工厂对象
1.2、结构模式
用于帮助将多个对象组织成更大的结构。结构型模式主要有适配器模式adapter、桥接模式bridge、组合器模式component、装饰器模式decorator、门面模式、亨元模式flyweight和代理模式proxy。
1.2.1代理模式(Proxy)
代理模式是一种应用非常广泛的设计模式,当客户端代码需要调用某个对象时,客户端实际上不关心是否准确得到该对象,它只要一个能提供该功能的对象即可,此时我们就可返回该对象的代理(Proxy)
1.2.2 享元模式
应用:在一个系统中,如果有多个相同的对象,那么只共享一份就ok了。不必每个都去实例化一个对象,可以节省大量的内存空间。
在Java中,String类型的使用了享元模式,String对象是final类型的,对象一旦创建就不可以被改变。在Java中字符串常量都是存在常量池中的,Java会确保一个字符串常量在常量池中只有一个拷贝。
1.2.3 装饰器模式
对已有的业务逻辑进一步的封装,使其增加额外的功能,如Java中的IO流就使用了装饰者模式,用户在使用的时候,可以任意组装,达到自己想要的效果。
1.2.4 适配器模式
将两种完全不同的事物联系到一起,就像现实生活中的变压器。假设一个手机充电器需要的电压是20V,但是正常的电压是220V,这时候就需要一个变压器,将220V的电压转换成20V的电压,这样,变压器就将20V的电压和手机联系起来了。
1.3、行为型
用于帮助系统间各对象的通信,以及如何控制复杂系统中流程。行为型模式主要有命令模式command、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式state、策略模式、模板模式和访问者模式。
1.3.1 命令模式(Command)
某个方法需要完成某一个功能,完成这个功能的大部分步骤已经确定了,但可能有少量具体步骤无法确定,必须等到执行该方法时才可以确定。(在某些编程语言如Ruby、Perl里,允许传入一个代码块作为参数。但Jara暂时还不支持代码块作为参数)。在Java中,传入该方法的是一个对象,该对象通常是某个接口的匿名实现类的实例,该接口通常被称为命令接口,这种设计方式也被称为命令模式。
1.3.2 策略模式(Strategy)
策略模式用于封装系列的算法,这些算法通常被封装在一个被称为Context的类中,客户端程序可以自由选择其中一种算法,或让Context为客户端选择一种最佳算法——使用策略模式的优势是为了支持算法的自由切换。
DiscountStrategy,折扣方法接口
public interface DiscountStrategy
{
//定义一个用于计算打折价的方法
double getDiscount(double originPrice);
}
OldDiscount,旧书打折算法
public class OldDiscount implements DiscountStrategy {
// 重写getDiscount()方法,提供旧书打折算法
publicdouble getDiscount(double originPrice) {
System.out.println("使用旧书折扣...");
return originPrice * 0.7;
}
}
VipDiscount,VIP打折算法
//实现DiscountStrategy接口,实现对VIP打折的算法
public class VipDiscount implements DiscountStrategy {
// 重写getDiscount()方法,提供VIP打折算法
publicdouble getDiscount(double originPrice) {
System.out.println("使用VIP折扣...");
return originPrice * 0.5;
}
}
策略定义
public static void main(String[] args)
{
//客户端没有选择打折策略类
DiscountContext dc = new DiscountContext(null);
double price1 = 79;
//使用默认的打折策略
System.out.println("79元的书默认打折后的价格是:"
+ dc.getDiscountPrice(price1));
//客户端选择合适的VIP打折策略
dc.setDiscount(new VipDiscount());
double price2 = 89;
//使用VIP打折得到打折价格
System.out.println("89元的书对VIP用户的价格是:"
+ dc.getDiscountPrice(price2));
}
测试
publicstaticvoid main(String[] args)
{
//客户端没有选择打折策略类
DiscountContext dc = new DiscountContext(null);
double price1 = 79;
//使用默认的打折策略
System.out.println("79元的书默认打折后的价格是:"
+ dc.getDiscountPrice(price1));
//客户端选择合适的VIP打折策略
dc.setDiscount(new VipDiscount());
double price2 = 89;
//使用VIP打折得到打折价格
System.out.println("89元的书对VIP用户的价格是:"
+ dc.getDiscountPrice(price2));
}
使用策略模式可以让客户端代码在不同的打折策略之间切换,但也有一个小小的遗憾:客户端代码需要和不同的策略耦合。为了弥补这个不足,我们可以考虑使用配置文件来指定DiscountContext使用哪种打折策略——这就彻底分离客户端代码和具体打折策略类。
1.3.3 观察者模式(Observer)
观察者模式定义了对象间的一对多依赖关系,让一个或多个观察者对象观察一个主题对象。当主题对象的状态发生变化时,系统能通知所有的依赖于此对象的观察者对象,从而使得观察者对象能够自动更新。
在观察者模式中,被观察的对象常常也被称为目标或主题(Subject),依赖的对象被称为观察者(Observer)。
主题中有观察者对象组成的集合。
2.反射的原理?获取class对象的方式有哪些? 如何用反射取私有属性filed?
2.1 反射的原理
『反射』就是指程序在运行时能够动态的获取到一个类的类型信息的一种操作。它是现代框架的灵魂,几尽所有的框架能够提供的一些自动化机制都是靠反射实现的,这也是为什么各类框架都不允许你覆盖掉默认的无参构造器的原因,因为框架需要以反射机制利用无参构造器创建实例。
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。总结说:反射就是把java类中的各种成分映射成一个个的Java对象,并且可以进行操作。
2.2 获取class对象的方式有哪些
1.对象调用 getClass() 方法来获取,通常应用在:比如你传过来一个 Object
类型的对象,而我不知道你具体是什么类,用这种方法
Person p1 = new Person();
Class c1 = p1.getClass();
2.类名.class 的方式得到,该方法最为安全可靠,程序性能更高
这说明任何一个类都有一个隐含的静态成员变量 class
Class c2 = Person.class;
3.通过 Class 对象的 forName() 静态方法来获取,用的最多,
但可能抛出 ClassNotFoundException 异常
Class c3 = Class.forName(“reflex.Person”);
2.3 如何用反射取私有属性filed
public static void main(String[] args) throws NoSuchFieldException {
// TODO Auto-generated method stub
/*
* 使用反射来创建构造方法私有化的对象
* */
//1:获取类的无参构造方法
Test test = new Test("张三");
Method[] methods = Test.class.getMethods();
Field[] fields = Test.class.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
fields[i].setAccessible(true);
try {
System.out.println(fields[i].get(test));
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(fields[i].getName());
}
}
然后,我们再来看一下输出的:
张三
name
0
age
3.jvm垃圾回收机制和jvm调优?
3.1 jvm垃圾回收机制
垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间的一种机制。
垃圾回收回收的是无任何引用的对象占据的内存空间而不是对象本身。换言之,垃圾回收只会负责释放那些对象占有的内存。对象是个抽象的词,包括引用和其占据的内存空间。当对象没有任何引用时其占据的内存空间随即被收回备用,此时对象也就被销毁。但不能说是回收对象,可以理解为一种文字游戏
3.2 jvm调优
参考
1.GC优化流程
GC频率不高,GC耗时不高,那么没有必要进行GC优化;如果GC时间超过1-3秒,或者频繁GC,则必须优化。指标参考:
a.Minor GC执行时间不到50ms;
b.Minor GC执行不频繁,约10秒一次;
c.Full GC执行时间不到1s;
d.Full GC执行频率不算频繁,不低于10分钟1次
1、性能定义:
①内存占用:垃圾收集器流畅运行所需要的内存数量。
②延迟:其度量标准是缩短由于垃圾啊收集引起的停顿时间或者完全消除因垃圾收集所引起的停顿,避免应用运行时发生抖动。
③吞吐量:指单位时间内系统处理用户的请求数。此处需要满足不考虑垃圾收集引起的停顿时间或内存消耗,能支撑应用达到的最高性能指标。
这三个属性中,其中一个任何一个属性性能的提高,几乎都是以另外一个或者两个属性性能的损失作代价,不可兼得,具体某一个属性或者两个属性的性能对应用来说比较重要,要基于应用的业务需求来确定。另外调优一般是从满足程序的内存使用需求开始的,之后是时间延迟的要求,最后才是吞吐量的要求,要基于这个步骤来不断优化,每一个步骤都是进行下一步的基础,不可逆行之。
2、调优过程中的原则
a.每次minor GC都要尽可能多的收集垃圾对象,以减少应用程序发生Full GC的频率。将转移到老年代的对象数量降低到最小,减少Major GC/Full GC的执行时间。解决方法如下:
①调整新生代的大小到最合适;
②选择合适的GC收集器;
③设置老年代的大小为最合适;
④减少使用全局变量和大对象;
b.GC内存最大化原则:处理吞吐量和延迟问题时候,垃圾处理器能使用的内存越大,垃圾收集的效果越好,应用程序也会越来越流畅。
c.在性能属性里面,吞吐量、延迟、内存占用,我们只能选择其中两个进行调优,不可三者兼得。
2.内存调优
1.测试时,启动参数采用JVM默认参数,不人为设置。
2.确保Full GC发生时,应用程序正处于稳定阶段。
3.延迟调优
在确定了应用程序的内存调优数据大小之后,我们需要再进行延迟性调优,因为对于此时堆内存大小,延迟性需求无法达到应用的需要,需要基于应用的情况来进行调试。这一步进行期间,我们可能会再次优化堆大小的配置,评估GC的持续时间和频率、以及是否需要切换到不同的垃圾收集器上。
4.吞吐量调优
吞吐量调优主要是基于应用程序的吞吐量(例如每小时批处理系统能完成的任务数量,在吞吐量方面优化的系统,较长的 GC 停顿时间也是可以接受的)要求而来的,应用程序有一个综合的吞吐指标,这个指标基于整个应用的需求和测试而衍生出来的。当有应用程序的吞吐量达到或者超过预期的吞吐目标,整个调优过程就可以圆满结束了。如果出现调优后依然无法达到应用程序的吞吐目标,需要重新回顾吞吐要求,评估当前吞吐量和目标差距是否巨大,如果在20%左右,可以修改参数,加大内存,再次从头调试,如果巨大就需要从整个应用层面来考虑,设计以及目标是否一致了,重新评估吞吐目标。
对于JVM调优来说,提升吞吐量的性能调优的目标就是就是尽可能避免或者很少发生FullGC 或者Stop-The-World压缩式垃圾收集(CMS),因为这两种方式都会造成应用程序吞吐量降低。尽量在Minor GC阶段回收更多的对象,避免对象提升过快到老年代
4.线程的创建、开启、状态、状态切换?sleep和wait的区别?如何保证线程安全?为什么使用多线程?线程池的了解?
4.1 创建
方式一:定义一个类继承Thread重写run方法,把线程要运行的代码写入run方法中创建Thread子类对象,调用start方法
方式二:定义一个类实现Runnable接口重写run方法 创建Thread类对象,创建Runnable实现类对象作为次数传入到Thread类的构造方法里面调用Thread类对象的start方法开启一个线程
4.2 开启 thread.start();
4.3 状态和状态切换
新建状态(new)
用new语句创建的线程处于新建状态,此时它和其他Java对象一样,仅仅在堆区中被分配了内存.它会一直保持这个状态直到启动了start()方法.
就绪状态(Runnable)
当一个线程对象启动了start()方法后,该线程就处于就绪状态,
Java虚拟机会为它创建方法调用栈和程序计数器.处于这个状态的线程位于可运行池中,等待获得CPU的使用权.
运行状态(Running)
如果一个线程处于这个状态,那么它正在占用CPU,执行程序代码.只有处于就绪状态的线程才有机会转到运行状态.
阻塞状态(Blocked)
阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行.当线程处于阻塞状态时,java虚拟机不会给线程分配CPU。直到线程重新进入就绪状态,它才有机会转到运行状态.
阻塞状态可分为三种:
1.位于对象等待池中(in Object’s wait pool)当线程处于运行状态的时候,如果执行了某个对象的wait()方法,java虚拟机就会把这个线程放到这个对象的等待池中.这设计到线程通信的内容.
2.位于对象锁池中(in Object’s lock pool)当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,java虚拟机就会把这个线程放到这个对象从锁池中,这涉及到"线程同步"的内容
3.其他阻塞状态(otherwise Blocked)当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求时,就会进入这个状态.
死亡状态(Dead)
当线程退出run()方法时,就进入死亡状态,该线程结束生命周期
sleep和wait的区别
sleep的作用是让线程休眠指定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行。
调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者。
区别:
1、来自不同的类:sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
2、有没有释放锁(释放资源):sleep不让出系统资源;wait是进入线程等待池等待,让出系统资源,其他线程可以占用CPU。
3、一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。
4.4 如何保证线程安全
1、互斥同步
互斥同步是最常见的一种并发正确性保障手段。同步是指在多线程并发访问共享数据时,保证共享数据在同一时刻只被一个线程使用(同一时刻,只有一个线程在操作共享数据)。而互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。因此,在这4个字里面,互斥是因,同步是果; …
2、非阻塞同步
随着硬件指令集的发展,出现了基于冲突检测的乐观并发策略,通俗地说,就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再采用其他的补偿措施。(最常见的补偿错误就是不断地重试,直到成功为止),这种乐观的并发策略的许多实现都不需要 …
3、无需同步方案
要保证线程安全,并不是一定就要进行同步,两者没有因果关系。同步只是保证共享数据争用时的正确性的手段,如果一个方法本来就不涉及共享数据,那它自然就无需任何同步操作去保证正确性,因此会有一些代码天生就是线程安全的。
4.5 为什么使用多线程
- 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
- 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
- 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
4.6 线程池
概念
所谓线程池,就是一个用来放线程的池子,里面存放着已经创建好的线程,当有任务提交的时候,池子里面的某个线程会执行这个任务,当任务结束后,线程又回到了池子里面等待下一个任务。当任务太多的时候,池子就要自动加水,创建更多的线程,但池子也是有容量的;当任务太少的时候,池子就要放掉一些线程,以免资源浪费。
功能
任务队列:用于缓存已经提交的任务。
线程数量管理:线程池应该自动控制线程池里面线程的数量,可以通过以下三个参数来实现:(1)初始线程数init,线程池能容纳最大线程数max,线程的活跃数量:core,三者的关系是int<=core<=max
任务拒绝策略:当线程数量达到上限且任务队列满的时候,需要有对应的拒绝策略来通知提交者线程被拒绝。
线程工厂:用于自定义线程,比如设置线程名称等。
提交队列:用于存放提交的线程,需要有数量限制。
活跃时间:线程自动维护的时间。
5.集合有哪些?哪些是线程安全的集合?hashmap的底层原理?hashMap、hashTable、currenthashmap区别?ArrayList、vector、linkedlist的区别?
5.1 List、Set、Map三类集合
1、list集合
ArrayList集合
底层数据结构是数组,查询快,增删慢,查询是根据数组下标直接查询速度快,增删需要移动后边的元素和扩容,速度慢。线程不安全,效率高
LinkedList集合
底层数据结构是链表,查询慢,增删快,查询需要遍历数组,速度慢,增删只需要增加或删除一个链接即可,速度快,线程不安全,效率高
Vector集合
底层数据结构是数组,查询快,增删慢,线程安全,效率低
2、Set集合
Hashset:
底层数据结构是哈希表,是根据哈希算法来存取对象的,存取速度快,当Hashset中元素个数超过数组大小(默认值位0.75)时,会进行近似两倍的扩容,哈希表依赖两个方法hashcode()和equals()方法,方法的执行顺序,判断hashcode值是否相同,是:执行equals方法看其返回值,true:说明元素重复不添加,false:直接添加到集合,hashcode值不相同直接添加到集合。
LinkedHashset:
底层数据结构是链表和哈希表,由链表保证元素有序,由哈希表保证元素的唯一
Treeset:
底层数据结构是红黑树(唯一,有序)由自然排序和比较器排序保证有序,根据返回值是否是0判断元素是否唯一
3、Map集合
HashMap:
HashMap是基于散列表实现的,其插入和查询的<k,v>的开销是固定的,可以通过构造器设置容量和负载因子来调整容器的性能,线程不安全,效率低
TreeSet:
基于红黑树实现,查看<k,v>时,它们会被排序,TreeMap是唯一带有subMap()方法的Map,subMap()方法可以返回一个子树。
LInkedHashMap:
类似于HashMap,但是迭代遍历它时,取得<K,V>的顺序是其插入次序,或者是最近最少使用(LRU)的次序。
5.2 线程安全的集合
Vector、HashTable和java.util.concurrent包中的集合ConcurrentHashMap、CopyOnWriteArrayList和CopyOnWriteArraySet
5.3 hashMap的底层原理
HashMap是基于哈希表的Map接口的非同步实现。实现HashMap对数据的操作,允许有一个null键,多个null值。
HashMap底层就是一个数组结构,数组中的每一项又是一个链表。数组+链表结构,新建一个HashMap的时候,就会初始化一个数组。Entry就是数组中的元素,每个Entry其实就是一个key-value的键值对,它持有一个指向下一个元素的引用,这就构成了链表,HashMap底层将key-value当成一个整体来处理,这个整体就是一个Entry对象。HashMap底层采用一个Entry【】数组来保存所有的key-value键值对,当需要存储一个Entry对象时,会根据hash算法来决定在其数组中的位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry对象时,也会根据hash算法找到其在数组中的存储位置, 在根据equals方法从该位置上的链表中取出Entry;
5.4 hashMap和hashTable、currenthashMap区别
HashTable与HashMap、ConcurrentHashMap主要的区别在于HashMap不是同步的、线程不安全的和不适合应用于多线程并发环境下,而hashTable和ConcurrentHashMap是同步的也是线程安全的。hashtable是全局同步,每次修改都会锁住整个对象而ConcurrentHashMap则会根据并发的等级来锁住操作的那一段数据,ConcurrentHashMap有很好的扩展性,在多线程环境下性能方面比做了同步的HashMap要好,但是在单线程环境下,HashMap会比ConcurrentHashMap好一点。
5.5 ArrayList、vector、LinkedList的区别
5.5.1 从存储数据结构分析
ArrayList和vector是数组,LinkedList是链表
5.5.2 从继承上分析
5.5.3 从并发安全上分析
vector线程安全,ArrayList和linkedlist线程不安全。
5.5.4 数据增长分析
Vector:缺省的情况下,增长为原数组长度的一倍。说到缺省,说明他其实是可以自主设置初始化大小的。
ArrayList:自动增长原数组的50%
6.jvm内存模型的理解?
6.1 方法区(Method Area)
方法区主要是放一下类似类定义、常量、编译后的代码、静态变量等,在JDK1.7中,HotSpot VM的实现就是将其放在永久代中,这样的好处就是可以直接使用堆中的GC算法来进行管理,但坏处就是经常会出现内存溢出,即PermGen Space异常,所以在JDK1.8中,HotSpot VM取消了永久代,用元空间取而代之,元空间直接使用本地内存,理论上电脑有多少内存它就可以使用多少内存,所以不会再出现PermGen Space异常。
6.2 堆(Heap)
几乎所有对象、数组等都是在此分配内存的,在JVM内存中占的比例也是极大的,也是GC垃圾回收的主要阵地,平时我们说的什么新生代、老年代、永久代也是指的这片区域,至于为什么要进行分代后面会解释。
6.3 虚拟机栈(Java Stack)
当JVM在执行方法时,会在此区域中创建一个栈帧来存放方法的各种信息,比如返回值,局部变量表和各种对象引用等,方法开始执行前就先创建栈帧入栈,执行完后就出栈。
6.4 本地方法栈(Native Method Stack)
和虚拟机栈类似,不过区别是专门提供给本地Native方法用的。
6.5 程序计数器(Program Counter Register)
占用很小的一片区域,我们知道JVM执行代码是一行一行执行字节码,所以需要一个计数器来记录当前执行的行数。
7 抽象类与接口的区别?
7.1 接口
接口是抽象方法的集合。如果一个类实现了某个接口,那么它就继承了这个接口的抽象方法。这就像契约模式,如果实现了这个接口,那么就必须确保使用这些方法。接口只是一种形式,接口自身不能做任何事情
7.2 抽象类
抽象类是用来捕捉子类的通用特性的 。它不能被实例化,只能被用作子类的超类。抽象类是被用来创建继承层级里子类的模板
区别
类是对事物的抽象,抽象类是对类的抽象,接口是对抽象类的抽象。接口用于抽象事物的特性,抽象类用于代码复用。
8 volatile与synchronized的区别?
synchronized关键字解决的是执行控制的问题,它会阻止其它线程获取当前对象的监控锁,这样就使得当前对象中被synchronized关键字保护的代码块无法被其它线程访问,也就无法并发执行。更重要的是,synchronized还会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性,同时也使得先获得这个锁的线程的所有操作
volatile关键字解决的是内存可见性的问题,会使得所有对volatile变量的读写都会直接刷到主存,即保证了变量的可见性。这样就能满足一些对变量可见性有要求而对读取顺序没有要求的需求。
8.1
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
8.2
volatile仅能使用在变量级别;synchronized则可以使用在方法和代码块
8.3
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
8.4
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
8.5
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
9 重写与重载的区别?
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。
10 java中字符串的方法有哪些?string、stringbuffer和stringbuilder的区别?
10.1 java中字符串的方法有哪些?
1、equals():比较两个字符串是否相等
2、equalsIgnoreCase( ):忽略大小写的两个字符串是否相等比较
3、toString():转换成String类型
4、String:转换成String类型
5、String.valueOf():转换成String类型(不用担心object是否为null值这一问题)
6、split():分隔符
7、subString():截取字符串中的一段字符串
8、charAt():返回指定索引处char值
9、toLowerCase():将所有在此字符串中的字符转化为小写(使用默认语言环境的规则)
10、indexOf():指出 String 对象内子字符串的开始位置
11、replace和replaceAll
12、getBytes():得到一个系统默认的编码格式的字节数组
13、StringBuffer的append方法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!