java基础知识点(1)

1:JRE与JDK

java基础知识点(1)_第1张图片
JDK:JRE+开发工具(编译工具+运行工具),比如编译器(javac)和工具(如 javadoc 和 jdb),它能够创建和编译程序
JRE:JVM+各种核心类库, Java 运行时环境,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序
JVM:保证各种语言的跨平台性,是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果

2: Java 语言有哪些特点

1.面向对象(封装,继承,多态);
2.平台无关性( Java 虚拟机实现平台无关性);
3.可靠性;安全性;
4.支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持);
5.支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便);
6.编译与解释并存;

3: 面向对象和面向过程的区别

面向过程 :面向过程性能比面向对象高。 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发。
面向过程没有面向对象易维护、易复用、易扩展。

面向对象 :面向对象易维护、易复用、易扩展。 因为面向对象有封装继承多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
面向对象性能比面向过程低。

4: 面向对象都有哪些特性

封装:把数据以及操作数据的方法绑定起来,对数据的访问只能通过已定义的接口
继承:从现有类继承信息创建新类的过程,现有类成为父类,新类被成为子类。子类可以在父类的基础上编写自己的方法,这让软件有了一定的延展性。
多态:允许不同子类型的对象对于同一消息做出不同的响应。简单来说就是同一个对象调用同一方法可能得到不同的结果。多态又分为编译时多态以及运行时多态。运行时多态可以是方法重载以及方法重写
抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

5:如何理解clone对象

在java中,new意味着内存的分配,首先看new后面的数据类型,然后决定分配多大的内存。之后再调用构造函数填充内存(对象的初始化

clone与new开始步骤是相同的,不过clone是将原对象的域中的所有内容填充进新分配的内存之中。

clone分为浅拷贝深拷贝
例如:一个对象有name以及age属性,age属于基本数据类型,但是name是引用数据类型
假如clone前后两个对象的name指向同一个地址称为浅拷贝,指向不同的地址则称为深拷贝
深拷贝必须要原有对象实现Clonable接口,并且重写clone方法,在方法内部将引用数据类型也进行clone一次,这就要求这个被引用的对象必须也要实现Cloneable 接口并且实现 clone 方法。

6:&与&&,| 与 || 的区别

A&B :A与B同时为true表达式为true,A为false时仍需要判断B,可能会引起空指针异常
例:username != null && !username.equals("")
A&&B:A与B同时为true表达式为true,A为false时不进行B的判断
A | B:a或b任意一个为true 就返回true ,A为true 继续判断B
A || B:a或b任意一个为true 就返回true , A为true时 不判断B

7:equals方法

(1)如果两个对象相同(equals 方法返回 true),那么它们的 hashCode 值一定要相同;
(2)如果两个对象的 hashCode 相同,它们并不一定相同
equals 方法必须满足
自反性(x.equals(x)必须返回 true)
对称性(x.equals(y)返回 true 时,y.equals(x)也必须返回 true)
传递性(x.equals(y)和 y.equals(z)都返回 true 时,x.equals(z)也必须返回 true)
一致性(x 、y 引用的对象信息没有被修改时,多次调用 x.equals(y)应该得到同样的返回值)

8:重载(overload)和重写(override)的区别?

重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。重载对返回类型没有特殊的要求。

重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理

重写:发生在子类与父类之间
1.返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
2.如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
3.构造方法无法被重写

重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法

9:String、StringBuffer 和、StringBuilder 的区别是什么? String 为什么是不可变的?

1.String 类是 final 类,不可以被继承(在 Java 9 之后,String 类的实现改用 byte 数组存储字符串 private final byte[] value)
2.单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder(非线程安全)
3.多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer(线程安全)
性能:相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

10:抽象类(abstract class)和接口(interface)有什么异同?

不同点

抽象类:
1.抽象类中可以定义构造器
2.可以有抽象方法和具体方法
3.接口中的成员全都是 public 的
4.抽象类中可以定义成员变量
5.有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法
6.抽象类中可以包含静态方法
7.一个类只能继承一个抽象类

接口:
1.接口中不能定义构造器
2.方法全部都是抽象方法
3.抽象类中的成员可以是 private、默认、protected、public
4.接口中定义的成员变量实际上都是常量
5.接口中不能有静态方法
6.一个类可以实现多个接口

相同点

1.不能够实例化
2.可以将抽象类和接口类型作为引用类型
3.一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类

11:阐述静态变量和实例变量的区别?

静态变量: 是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝; 存在于栈内存
实例变量: 必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。 存在在堆内存

12: ==和 equals 的区别?

equals 和 == 最大的区别是一个是方法一个是运算符。

==:如果比较的对象是基本数据类型,则比较的是数值是否相等;如果比较的是引用数据类型,则比较的是对象的地址值是否相等。

equals():用来比较方法两个对象的内容是否相等。

注意:equals 方法不能用于基本数据类型的变量,如果没有对 equals 方法进行重写,则比较的是引用类型的变量所指向的对象的地址

13:简述线程、程序、进程的基本概念。以及他们之间关系是什么?

线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

进程:程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。

程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。

14:线程有哪些基本状态?

线程创建之后它将处于 NEW(新建) 状态,调用 start() 方法后开始运行,线程这时候处于 READY(可运行) 状态。可运行状态的线程获得了 cpu 时间片(timeslice)后就处于 RUNNING(运行) 状态。(操作系统隐藏 Java 虚拟机(JVM)中的 READY 和 RUNNING 状态,它只能看到 RUNNABLE 状态,所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。)
当线程执行 wait()方法之后,线程进入WAITING状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞) 状态。线程在执行 Runnable 的run()方法之后将会进入到 TERMINATED(终止) 状态。

15:关于 final 关键字

final 关键字主要用在三个地方:变量、方法、类。
final 修饰变量:如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
final 修饰方法:方法不可被重写
final 修饰类:类不能被继承。 类中的所有成员方法都会被隐式地指定为 final 方法。

16:Java 中的异常处理

在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable: 有两个重要的子类:Exception(异常)Error(错误) ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
Error(错误):程序无法处理的错误,这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
Exception(异常):是程序本身可以处理的异常。按照异常需要处理的时机分为编译时异常(也叫强制性异常)也叫 CheckedException 和运行时异常(也叫非强制性异常)也叫 RuntimeException。

异常处理:try…catch…finally或者抛出异常
在以下 4 种特殊情况下,finally 块不会被执行:
1.在 finally 语句块第一行发生了异常。 因为在其他行,finally 块还是会得到执行
2.在前面的代码中用了 System.exit(int)已退出程序。 exit 是带参函数 ;若该语句在异常语句之后,finally 会执行。
3.程序所在的线程死亡。
4.关闭 CPU。

17:最常见的 RuntimeException

1)java.lang.NullPointerException 空指针异常;出现原因:调用了未经初始化的对象或者是不存在的对象。
2)java.lang.ClassNotFoundException 指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序试图通过字符串来加载某个类时可能引发异常。
3)java.lang.NumberFormatException 字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。
4)java.lang.IndexOutOfBoundsException 数组角标越界异常,常见于操作数组对象时发生。
5)java.lang.IllegalArgumentException 方法传递参数错误。
6)java.lang.ClassCastException 数据类型转换异常。
7)java.lang.NoClassDefFoundException 未找到类定义错误。
8)SQLException SQL 异常,常见于操作数据库时的 SQL 语句错误。
9)java.lang.InstantiationException 实例化异常。
10)java.lang.NoSuchMethodException 方法不存在异常。

18:Java 序列化中如果有些字段不想进行序列化,怎么办?

对于不想进行序列化的变量,使用 transient 关键字修饰。

transient :阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。

19:switch(expr)中参数类型?

Java5 以前 switch(expr)中,expr 只能是 byte、short、char、int。从 Java 5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型。
从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。

20:Java 中 IO 流

IO流的分类:
1.按照流的流向分,可以分为输入流和输出流;
2.按照操作单元划分,可以划分为字节流和字符流;
3.按照流的角色划分为节点流和处理流。

IO流的基类:
InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

字节流如何转为字符流
1.字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。
2.字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象。

BIO,NIO,AIO 有什么区别?
BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。
NIO (Non-blocking/New I/O): NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。

21:什么是 java 序列化,如何实现 java 序列化?

序列化:就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。

序列化的实现:将需要被序列化的类现 Serializable接口 , 该接口没有需要实现的方法,implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则用输入流。

用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
会出现异常。序列化运行时将每个可序列化的类与称为serialVersionUID的版本号相关联,该序列号在反序列化期间用于验证序列化对象的发送者和接收者是否已加载与该序列化兼容的对象的类。 如果接收方加载了一个具有不同于相应发件人类的serialVersionUID的对象的类,则反序列化将导致InvalidClassException 。 一个可序列化的类可以通过声明一个名为"serialVersionUID"的字段来显式地声明它自己的serialVersionUID,该字段必须是static,final,类型是long

如果可序列化类没有显式声明serialVersionUID,则序列化运行时将根据Java(TM)对象序列化规范中所述的类的各个方面计算该类的默认serialVersionUID值。

直白的理解就是,当你修改了对象所属类的属性时,会发生版本不一致的冲突,此冲突会导致InvalidClassException异常

如果一个对象中的某个成员变量的值不想被序列化,又该如何实现?
使用transient关键字修饰

22:Collections 工具类和 Arrays 工具类常见方法总结

https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/basic/Arrays,CollectionsCommonMethods.md

23:线程相关

Executors 线程池工厂类:线程池作用就是限制系统中执行线程的数量
具体实现原理:根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程 排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程 池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

为什么要使用线程池
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累趴下(每个线程需要大约 1MB 内存,线程开的越多,消耗的内存也就越大,最后死机)

线程池创建线程大致可以分为下面三种:

//创建固定大小的线程池
ExecutorService fPool = Executors.newFixedThreadPool(3);
//创建缓存大小的线程池
ExecutorService cPool = Executors.newCachedThreadPool();
//创建单一的线程池
ExecutorService sPool = Executors.newSingleThreadExecutor();

合理利用线程池的优点:
1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3.提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

getID():取得线程的唯一标识
停止线程:使用interrupt()此方法不会立即终止线程,会在执行完此线程的代码之后再进行终止线程,stop()会立即终止线程,但是已经废弃,此方法不安全:使用此方法线程会强性终止,
正在处理的数据就有可能遭到破坏,可能会出现数据不一致的情况,所以此方法被废弃使用return与interrupt()方法相结合的方式也可以终止线程,不过还是建议使用在线程中抛异常的方式来进行终止线程,否则会造成return污染。
设置线程的优先级:setPriority()分为1-10这10个等级

当线程呈现wait()时,调用线程对象的 interrupt() 方法会出现InterruptedException异常

24:在 java 中 wait 和 sleep 方法的不同?

最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。wait 通常被用于线程间交互,sleep 通常被用于暂停执行。

25:synchronized 和 volatile 关键字的作用

一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是
立即可见的。
2)禁止进行指令重排序。

volatile 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

1.volatile 仅能使用在变量级别;
synchronized 则可以使用在变量、方法、和类级别的
2.volatile 仅能实现变量的修改可见性,并不能保证原子性;
synchronized 则可以保证变量的修改可见性和原子性
3.volatile 不会造成线程的阻塞;
synchronized 可能会造成线程的阻塞。
4.volatile 标记的变量不会被编译器优化;
synchronized 标记的变量可以被编译器优化

26:线程池的启动策略?

1.线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
2.当调用 execute() 方法添加一个任务时,线程池会做如下判断:
a: 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
b:如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
c:如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
d:如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。
3.当一个线程完成任务时,它会从队列中取下一个任务来执行。
4.当一个线程无事可。做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

27:如何控制某个方法允许并发访问线程的个数?

可以使用 Semaphore 控制
Semaphore 初始化时可以限定并发访问的个数

// 创建一个 Semaphore 对象,并且初始化了 5 个信号
static Semaphore semaphore = new Semaphore(5,true);
...
//申请一个请求
semaphore.acquire();
...
//释放一个请求
semaphore.release();

你可能感兴趣的:(java记录)