Java基础知识
面向对象与面向过程
- 面向过程性能比面向对象高,因为类的调用需要实例化,开销大
- Java性能差的根本原因是因为半编译语言,最终的执行代码不是可以直接被CPU执行的二进制码
Java特点
- Java虚拟机实现平台无关性
- 支持多线程
- C++11开始(2011年的时候),C++就引入了多线程库,在windows、linux、macos都可以使用std::thread和std::async来创建线程
- 编译与解释共存
##关于JVM、JDK和JRE
Java虚拟机(JVM)
- JVM运行Java字节码
- 字节码(扩展名为.class的文件)
- .class–>机器码,在这一步JVM类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢
JDK和JRE
- JDK是功能齐全的Java SDK,拥有JRE的一切,JRE是Java运行时环境
- JDK能够创建和编译程序,JRE不能创建新程序
Java与C++
- 都是OOP,支持封装、继承和多态
- Java不提供指针直接访问内存
- C++支持多重继承,Java单继承,但Java接口可多继承
- Java有自动内存管理机制,不需要手动释放无用内存
- C语言中‘、0’表示结束符,Java无
字符型常量和字符串常量的区别?
- 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算
- 字符串常量代表一个地址值(该字符串在内存中存放位置)
- 字符常量只占2个字节
- 字符串常量占若干个字节 (注意: char在Java中占两个字节)
构造器 Constructor 是否可被 override?
Constructor可以被overload,不可override
重载和重写的区别
- overload重载
- 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同
- override重写
- 重写是子类对父类的允许访问的方法的实现过程进行重新编写,发生在子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类
- 如果父类方法访问修饰符为 private 则子类就不能重写该方法
- 也就是说方法提供的行为改变,而方法的外貌并没有改变
Java 面向对象编程三大特性: 封装 继承 多态
- 封装
- 把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法
- 继承
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类无法访问,只是拥有
- 子类可以对父类进行扩展
- 子类可以用自己的方法实现父类的方法
- 多态
- 程序中定义的引用变量所指向的具体类型和通过该应用变量发出的方法调用在编程时并不确定,直到程序运行期间才确定。即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,运行时决定。
- 实现多态的两种形式:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)
String StringBuffer 和 StringBuilder
- String 类中使用final关键字修饰字符数组来保存字符串,
private final char value[]
,所以 String 对象是不可变的 - StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类
-
在 AbstractStringBuilder 中也是使用字符数组保存字符串
char[]value
但是没有用 final 关键字修饰,所以StringBuilder 与 StringBuffer都是可变的。 - String中对象不可变,线程安全
- AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法
- StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的
- StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的
- StringBuilder 比 StringBuffer 性能略高
自动装箱与拆箱
- 装箱:将基本类型用它们对应的引用类型包装起来
- 拆箱:将包装类型转换成基本数据类型
import java和javax有什么区别?
接口和抽象类的区别是什么?
成员变量与局部变量的区别有哪些?
- 从语法形式上看:成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰
- 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
- 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值
创建一个对象用什么运算符?对象实体与对象引用有何不同?
- new 运算符
- new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)
- 一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。
构造方法
构造方法有哪些特性?
- 名字与类名相同
- 没有返回值,但不能用void声明构造函数
- 生成类的对象时自动执行,无需调用。
在 Java 中定义一个不做事且没有参数的构造方法的作用
- Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”
- 如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误
一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?
静态方法和实例方法
- 实例方法只能通过对象.方法名的方式调用
- 静态方法还可以通过类名.方法名调用,无需创建对象
- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法
在一个静态方法内调用一个非静态成员为什么是非法的?
- 静态方法可以不通过对象进行调用
- 在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员
== 与 equals
- ==的作用是判断两个对象的地址是不是相等
- 判断两个对象是不是同一个对象
- 基本数据类型==比较的是值
- 引用数据类型==比较的是内存地址
- equals()的作用也是判断两个对象是否相等
hashCode 与 equals (重要)
- hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。
- HashSet 如何检查重复
- 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与该位置其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。
- 但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。
- 这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
- hashCode()与 equals()的相关规定
- 如果两个对象相等,则 hashcode 一定也是相同的
- 两个对象相等,对两个对象分别调用 equals 方法都返回 true
- 两个对象有相同的 hashcode 值,它们也不一定是相等的
- 因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
- hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
简述线程、程序、进程的基本概念。以及他们之间关系是什么?
- 线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
- 程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
- 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。
- 线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
线程有哪些基本状态?
- 线程创建之后它将处于 NEW(新建)状态,调用 start()方法后开始运行,线程这时候处于 READY(可运行)状态。
- 可运行状态的线程获得了 cpu 时间片(timeslice)后就处于 RUNNING(运行)状态。
- 当线程执行 wait()方法之后,线程进入 WAITING(等待)状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将 Java 线程置于 TIMED WAITING 状态。
- 当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞)状态。线程在执行 Runnable 的run()方法之后将会进入到 TERMINATED(终止)状态。
关于final关键字
- 对于一个 final 变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
- 当用 final 修饰一个类时,表明这个类不能被继承。final 类中的所有成员方法都会被隐式地指定为 final 方法。
- 使用 final 方法的原因有两个。
- 第一个原因是把方法锁定,以防任何继承类修改它的含义;
- 第二个原因是效率。在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的 Java 版本已经不需要使用 final 方法进行这些优化了)。类中所有的 private 方法都隐式地指定为 final。
Java中的异常处理
Java异常类
- java.lang 包中的 Throwable 类
- Throwable中有两个重要的子类:Exception(异常) 和 Error(错误) ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
- Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。
- 例如,Java 虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
- 这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如 Java 虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。
- 这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。
- Exception(异常):是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 异常由 Java 虚拟机抛出。
- NullPointerException(要访问的变量没有引用任何对象时,抛出该异常)
- ArithmeticException(算术运算异常,一个整数除以 0 时,抛出该异常)
- ArrayIndexOutOfBoundsException (下标越界异常)
- 注意:异常和错误的区别:异常能被程序本身处理,错误是无法处理。
Throwable类常用方法
public string getMessage()
:返回异常发生时的简要描述public string toString()
:返回异常发生时的详细信息public string getLocalizedMessage()
:返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同public void printStackTrace()
:在控制台上打印 Throwable 对象封装的异常信息
异常处理总结
- try 块: 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
- catch 块: 用于处理 try 捕获到的异常。
- finally 块: 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。
- 在以下 4 种特殊情况下,finally 块不会被执行:
- 在 finally 语句块第一行发生了异常。 因为在其他行,finally 块还是会得到执行
- 在前面的代码中用了
System.exit(int)
已退出程序。 exit 是带参函数 ;若该语句在异常语句之后,finally 会执行 - 程序所在的线程死亡。
- 关闭 CPU。
获取用键盘输入常用的两种方法
- Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
- BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
Java中的IO流
Java中的IO流分为几种
- 按照流的流向分,可以分为输入流和输出流
- 按照操作单元划分,可以划分为字节流和字符流
- 按照流的角色划分为节点流和处理流
- 四个抽象类基类
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
I/O 流操作要分为字节流操作和字符流操作
- 不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
- 字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。
- 所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
BIO,NIO,AIO 有什么区别?
- BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。
- 在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。
- 线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。
- 但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
- NIO (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 操作本身是同步的。
以上转载自JavaGuide