文章目录
- Java程序编译过程
- 编译型和解析型语言
- 命名规范
- 编程风格
- 大括号
- 非C风格的数组声明
- 阿里巴巴Java开发手册
- On Java 8
- Google Java 编程风格指南
- 基本数据类型
- 整数
- 浮点数
- 字符型
- 布尔型
- 运算符优先级
- 访问控制符
- 一些默认值
- 注释
- 类注释
- 方法注释
- 字段注释
- 其他注释
- 面向对象编程三大特性
- 类初始化顺序
- 抽象类
- this用法
- 泛型的特性
- 类加载器
- 运行体系结构
- 进程和线程
- 线程状态
- 线程优先级
- 线程休眠、唤醒、让步
- 线程同步
- 同步块
- 同步化方法
- 异常
- 垃圾回收和内存管理
- 输入输出
- 输入流和输出流
- 字节流
- 字符流
- 对象序列化控制输入输出
- 数据结构
- 集合接口
- XML
- 基础内容
- 良好的XML文档
- 网络编程
- TCP/IP协议和UDP协议
- IP协议和IP地址
- 套接字
- TCP/UDP程序设计
- 零碎小知识点
- 常见疑难解答
- 1、一般的程序可否用分支语句来代替条件语句
- 2、普通循环是使用for语句还是while语句
- 3、equals和“==”的区别
- 4、String类为何被定义成final约束
- 5、如何设计继承
- 6、动态和静态编译是什么
- 7、多态与重载的区别
- 8、抽象与接口的区别
- 9、线程与线程之间怎么通信
- 10、进程的死锁和饥饿
- 11、什么时候会涉及线程程序
- 12、多线程的死锁问题
- 13、多线程的缺点
- 14、为什么抛出的异常一定是检查异常
- 15、字节流与字符流的主要区别
- 16、什么是管道流
- 17、Collection集合接口和Collections集合类的区别
- 18、ArrayList数组列表类和Vector存储类的区别
- 19、HashMap散列映射和Hashtable散列表的区别
- 20、数据结构的种类有哪些
- 21、List接口和Set接口的区别
- 22、为什么Map接口不继承Collection接口
- 23、哪些是线程安全的数据结构
- 24、Vector是什么样的数据结构
- 25、XML与HTML的区别
- 26、TCP和UDP的区别
- 27、什么是TCP/IP协议,分为几层,什么功能
- 面试题
- 1、Java的引用和C++的指针有什么区别
- 2、类和对象有什么区别
- 3、说明private、protected、public和default的区别
- 4、Java可以用非0来代表true吗
- 5、StringBuffer和StringBuilder存在的作用是什么
- 6、二维数组的长度是否固定
- 7、符合什么条件的数据集合可以使用foreach循环
- 7、如何序列化和反序列化一个Java对象
- 8、如何使用Java的线程池
- 9、如何利用反射实例化一个类
- 10、TCP协议的通信特点是什么
- 11、JDBC操作数据库的编程步骤
- 12、如何使用连接池技术
- 13、接口和抽象类的区别
- 14、根据代码判断创建的对象个数
- 15、ArrayList、Vector、LinkedList的存储性能和特性
Java程序编译过程
1、Java
源程序(.java
)先编译成与平台无关的字节码文件(.class
)
2、Java
虚拟机(Java Virtual Machine
)将字节码文件再解释成机器码运行
3、采用字节码的最大好处是:可以实现一次编译到处运行,也就是java
的与平台无关性
4、所谓字节码,就是当Java
虚拟机加载某个类的对象时,首先需要把硬盘上关于该类的二进制源码编译成class
文件的二进制代码(字节码),然后把关于class
文件的字节码加载到内存中,然后再创建关于该类的对象。
编译型和解析型语言
编译型语言
需要通过编译器,将源代码编译成机器码之后才能执行的语言。一般是通过编译和链接两个步骤,编译是将我们的程序编译成机器码,链接是程序和依赖库等串联起来。
**优点:**编译器一般会有预编译的过程对代码进行了优化,因为编译只做了一次,运行时不会在编译,所以编译型语言效率高。
**缺点:**编译之后如果想要修改某一个功能,就需要整个模块重新编译。编译的时候根据对应的运行环境生成不同的机器码。不同的操作系统之间,可能会有问题。需要根据环境的不同,生成不同的可执行文件。
代表语言:C、C++、Pascal、Object-C、swift、GO
解析型语言
解释型语言不需要编译,相比编译型语言省了道工序,解释型语言在运行程序的时候才逐行进行翻译。字节码也是解释型的一部分。
**优点:**有良好的平台兼容性,只要安装了虚拟机,就可以。容易维护,方便快速部署,不用停机维护。
**缺点:**每次运行的时候都要解释一遍,性能上不如编译型语言。
代表语言:JavaScript、Python、Erlang、PHP、Perl、Ruby
命名规范
1、项目名:全部小写
2、包名:全部小写,连续的单词只是简单地连接起来,不使用下划线。域名作为包的唯一前缀
- 公司项目:com.公司名.项目名.模块名…
- 团队项目:team.团队名.项目名.模块名…
- 个人项目:
-
indi
:个人发起,但非自己独自完成的项目,可公开或私有项目,copyright
主要属于发起者:indi.发起者名.项目名.模块名… -
pers
:指个人发起,独自完成,可分享的项目,copyright
主要属于个人:pers.个人名.项目名.模块名… -
priv
:私有项目,指个人发起,独自完成,非公开的私人使用的项目,copyright属于个人:priv.个人名.项目名.模块名… -
onem
:与indi
相同,推荐使用indi
3、类名:首字母大写,其余组成词首字母依次大写。测试类的命名以它要测试的类的名称开始,以Test
结束。
4、变量名、方法名、参数名:首字母小写,如果名称由多个单词组成,除首字母外的每个单词的首字母都要大写。下划线可能出现在JUnit
测试方法名称中用以分隔名称的逻辑组件。参数应该避免用单个字符命名。
5、常量名:全部大写,用下划线分隔单词
6、所有命名规则必须遵循以下规则 :
- 名称只能由字母、数字、下划线、$符号组成
- 不能以数字开头
- 名称不能使用
Java
中的关键字 - 坚决不允许出现中文及拼音命名
编程风格
大括号
- 大括号与
if, else, for, do, while
语句一起使用,即使只有一条语句(或是空),也应该把大括号写上。 - 对于非空块和块状结构,大括号遵循
Kernighan
和Ritchie
风格
- 左大括号前不换行
- 左大括号后换行
- 右大括号前换行
- 如果右大括号是一个语句、函数体或类的终止,则右大括号后换行; 否则不换行。例如,如果右大括号后面是
else
或逗号,则不换行。
public void method() { // 左大括号前不换行
do(); // 左大括号后换行
if (condition == 0) {
try {
something();
} catch(Exception e) { // 右大括号不换行
recover();
} // 右大括号前换行
} // 右大括号换行
}
- 一个空的块状结构里什么也不包含,大括号可以简洁地写成
{}
,不需要换行。例外:如果它是一个多块语句的一部分(if/else
或try/catch/finally
) ,即使大括号内没内容,右大括号也要换行。
非C风格的数组声明
中括号是类型的一部分:String[] args
, 而非String args[]
。
阿里巴巴Java开发手册
下载地址
On Java 8
在线阅读地址
Google Java 编程风格指南
在线阅读地址
基本数据类型
全局变量可以不用进行初始化赋值(有默认值),而局部变量必须要进行初始化赋值。
9种基本类型:boolean
、byte
、char
、short
、int
、long
、float
、double
和void
整数
名称 | 字节 | 默认值 | 包装类 |
字节型byte | 1字节 | 0 | Byte |
短整型short | 2字节 | 0 | Short |
整数型int | 4字节 | 0 | Integer |
长整型long | 8字节 | 0或0L | Long |
浮点数
名称 | 字节 | 默认值 | 包装类 |
单精度float | 4字节(符号1bit+指数8bit+位数23bit) | 0.0f | Float |
双精度double | 8字节(符号1bit+指数11bit+位数52bit) | 0.0d | Double |
字符型
名称 | 字节 | 默认值 | 包装类 |
字符char | 2字节 | (空) | Character |
布尔型
名称 | 字节 | 默认值 | 包装类 |
布尔boolean | boolean类型数据通过int类型表示,此时boolean数据4字节32bit;boolean数组表示为byte数组,此时每个boolean数据1字节占8bit | false | Boolean |
运算符优先级
优先级 | 运算符 | 结合性 |
1 | ()、[]、{} | 从左向右 |
2 | !、+、-、~、++、– | 从右向左 |
3 | *、/、% | 从左向右 |
4 | +、- | 从左向右 |
5 | «、»、>>> | 从左向右 |
6 | <、<=、>、>=、instanceof | 从左向右 |
7 | ==、!= | 从左向右 |
8 | & | 从左向右 |
9 | ^ | 从左向右 |
10 | | | 从左向右 |
11 | && | 从左向右 |
12 | || | 从左向右 |
13 | ?: | 从右向左 |
14 | =、+=、-=、*=、/=、&=、|=、^=、~=、«=、»=、>>>= | 从右向左 |
访问控制符
一些默认值
- 接口中的成员变量默认为
public static final
(并且只能是 public
,用 private
修饰会报编译错误) - 接口中的成员方法为
public abstract
(只能是 public abstract
,其他修饰符都会报错,不是 default
) - 接口的声明默认是
public
- 外部类的访问控制符:
public
或default
- 内部类的访问控制符:
public
、protected
、default
、private
注释
类注释
放在所有的“import”
语句之后,类定义之前,主要声明该类可以做什么,以及创建者、创建日期、版本和包名等一些信息。
/**
@projectName(项目名称): project_name
@package(包): package_name.file_name
@className(类名称): type_name
@description(类描述): 一句话描述该类的功能
@author(创建人): user
@createDate(创建时间): datetime
@updateUser(修改人): user
@updateDate(修改时间): datetime
@updateRemark(修改备注): 说明本次修改内容
@version(版本): v1.0
*/
public class student{
// ...
}
方法注释
紧靠在方法定义的前面,主要声明方法参数、返回值、异常等信息。
/**
@param num1: 加数1
@param num2: 加数2
@return: 两个加数的和
@throws: 可能抛出xx异常
*/
public int add(int num1,int num2) {
// ...
return value;
}
字段注释
定义字段的前面,用来描述字段的含义。
/**
* 用户名
*/
public String name;
// 或
/**用户名*/
public String name;
其他注释
单行:// ...
多行:/* ... */
面向对象编程三大特性
- **封装:**利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其他对象只能通过包裹在数据外面的已经授权的操作来与这个封装的对象进行交流和交互。
- 1、良好的封装能够减少耦合。
- 2、类内部的结构可以自由修改。
- 3、可以对成员进行更精确的控制。
- 4、隐藏信息,实现细节。
- **继承:**使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。
- 1、子类拥有父类非
private
的属性和方法。 - 2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 3、子类可以用自己的方式实现父类的方法。
- 多态:程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定。包括:重载和覆盖。
类初始化顺序
- 正常类的加载顺序
- 静态变量/静态代码块 ->
main
方法 -> 非静态变量/代码块 -> 构造方法 - 静态代码块与静态变量的执行顺序同代码定义的顺序;非静态变量与代码块的执行顺序同代码执行顺序
- 初始化顺序
- 1、父类–静态变量、父类–静态初始化块
- 2、子类–静态变量、子类–静态初始化块
- 3、父类–普通变量、父类–普通初始化块
- 4、父类–构造函数
- 5、子类–普通变量、子类–普通初始化块
- 6、子类–构造器函数
抽象类
含有抽象方法的类一定是抽象类,但抽象类不一定含有抽象方法,也可以全部
都是具体的方法。
抽象类不能被实例化,即不能使用关键字new
来生成实例对象,但可以声明一个抽象类的变量指向具体子类的对象。
this用法
this
首先是一个对象,它代表调用这个函数的对象,可以理解为:指向对象本身的一个指针。
-
this
代表当前对象的一个引用。区分成员变量和局部变量:this.name = name;
- 使用this关键字调用重载构造方法。避免相同的初始化代码,只能在构造方法中用,并且必须位于构造方法的第一句:
this(name, age);
- 普通的直接引用。相当于是指向当前对象本身。关于返回类自身的引用:
return this;
泛型的特性
1、参数化类型与原始类型的兼容
当参数化类型引用一个原始类型的对象时,编译器只是警告而不报错。同样当原始类型引用一个参数化类型对象时,编译器也只是警告而不报错。
Vector<String> v = new Vector();
Vector v = new Vector(String);
2、参数化类型无继承性
错误示例:
Vector<String> v = new Vector(String);
Vector<Object> v1 = v;
v1.add(new Object());
String s = v.get(0);
// 因为v对象中的成员不再是String类型。所以代码“Vector<Object>v1=v”是错误的。
3、泛型的“去类型”特性
泛型中的类型只是提供给编译器使用的,当程序编译成功后就会去掉“类型”信息。泛型的作用只是限制集合中的输入类型,让编译器挡住源程序中的非法输入。这样有一个好处,程序具体运行时将不会受到泛型的影响。
正确示例:
// 创建对象arry1
ArrayList<String> arry1= new ArrayList<String>();
arry1.add("cjg");
// 创建对象arry2
ArrayList<Integer> arry2 = new ArrayList<Integer>();
arry2.add(27);
// 输出true。说明编译器生成的关于对象arry1集合和arry2集合的字节码为同一个对象
System.out.println((arry1.getClass()==arry2.getClass()));
4、利用反射绕过泛型的类型限制
由于编译器生成的字节码会去掉泛型的类型信息,所以只要能跳过编译器,还是可以给通过泛型限制类型的集合中加入其他类型的数据。
正确示例:
// 创建集合arry1
ArrayList<Integer> arry1 = new ArrayList<Integer>();
// 添加整数型数字
arry1.add(27);
// 通过反射向集合中添加字符型abc
arry1.getClass().getMethod("add", Object.class).invoke(arry1, "abc");
类加载器
当程序运行时,Java
虚拟机将编译生成的.class
文件按照需求和一定的规则加载进内存,并组织成为一个完整的Java
应用程序,该过程由类加载器自动完成。
Java
虚拟机(JVM
)运行类的第一件事情就是将该类的字节码加载进来,即类加
载器根据类的名称,定位和生成类的字节码数据,然后返回给JVM
。
类加载器只要能提供给JVM
调用的类字节码就可以,因此类加载器也可以描述为字节码的制造器。类加载器装载某个类字节码的过程实际上就是创建Class
类的一个实例对象。
第一个类加载器:BootstrapLoader
(引导类加载器)。由于BootstrapLoader
加载器不需要加载,所以其不是Java
类而是利用C++
语言编写的。
两个内置类加载器:ExtClassLoader
(扩展类加载器)、AppClassLoader
(应用程序类加载器)。
运行体系结构
当一个类被加载后,JVM
将其编译为可以执行的代码(字节码)存储到内存中,同时会将索引信息存储到一个HashTable
中,注意索引的关键字就是被加载类的完整名字。如果JVM
想运行某个类时,首先会使用类名作为关键字在HashTable
中查找相应的信息,如果该可执行代码已经存在,JVM
就直接会从内存里调用该可执行代码,否则就调用类加载器进行加载和编译。
进程和线程
程序是计算机指令的集合,它以文件形式存储在磁盘上。
进程就是一个执行中的程序,每一个进程都有其独立的内存空间和系统资源。支持多进程,就是CPU
在交替轮流执行多个程序。
线程是CPU
调度和分配的基本单位,一个进程可以由多个线程组成,而这多个线程共享同一个存储空间,这使得线程间的通信比较容易。
在一个多进程的程序中,如果要切换到另一个进程,需要改变地址空间的位置。然而在多线程的程序中,就不会出现这种情况,因为它们位于同一个内存空间内,只需改变运行的顺序即可。
多线程指单个程序可通过同时运行多个不同的线程,以执行不同任务。所谓同时,也要依据CPU
。如果是多个CPU
,则并发运行,如是一个CPU
,则根据系统具体情况,执行多个线程。
创建线程的方法一般有两种:
- 通过实现
Runnable
接口的方式创建线程。 - 通过继承
Thread
类来创建线程。
线程状态
Java规范中只定义了线程的4种状态,即新建状态、可运行状态、阻塞状态和死亡状态。为了更清晰地说明线程的状态变化过程,我们认为划分为5个状态更好理解,这里把可运行状态(Runnable)分解为就绪状态和运行状态,可以更好地理解可运行状态的含义。
- 新建状态:线程对象(通过
new
关键字)已经建立,在内存中有一个活跃的对象,但是没有启动该线程,所以它仍然不能做任何事情 - 就绪状态:一个线程一旦调用了
start()
方法,该线程就处于就绪状态。此时线程等待CPU
时间片,一旦获得CPU
时间周期,线程就可以执行。这种状态下的任何时刻线程是否执行完全取决于系统的调度程序。 - 运行状态:一旦处于就绪状态的线程获得
CPU
执行周期,就处于运行状态。在选择哪个线程可以执行时,操作系统的调度程序考虑线程的优先级 - 阻塞状态:该状态下线程无法运行,必须满足一定条件后方可执行。一旦线程满足一定的条件就解除阻塞,线程处于就绪状态。当发生以下情况时会使得线程进入阻塞状态:
- 线程正等待一个输入、输出操作,该操作完成前不会返回其调用者。
- 线程调用了
wait()
方法或sleep()
方法。 - 调用了线程的
suspend()
方法,该方法已经不推荐使用。 - 线程需要满足某种条件才可以继续执行。
- 死亡状态:线程一旦退出
run()
方法就处于死亡状态。在Java2
中通过调用stop()
和destroy()
方法使得线程死亡,但这些方法都引起程序的不稳定,由于stop()
方法已经过时了,所以最好不要在自己的程序中调用该方法。
线程优先级
如一个线程创建后,可通过在线程中调用setPriority()
方法,来设置其优先级。
-
public final static int MIN_PRIORITY=1
:表示最低优先级 -
public final static int MAX_PRIORITY=10
:表示最高优先级 -
public final static int NORM_PRIORITY=5
:表示默认优先级
线程休眠、唤醒、让步
- 线程的休眠:线程暂时处于等待的一种状态,需要调用
Thread
类的sleep()
方法。 - 线程的唤醒:使线程从休眠等待状态进入可执行状态,可以调用
interrupt()
方法。 - 线程让步:使当前正在运行的线程对象退出运行状态,让其他线程运行,通过调用
yield()
方法实现。这个方法不能将运行权让给指定的线程,只是允许这个线程把运行权让出来,至于给谁,这就需要看由哪个线程抢占到了。
线程同步
一个程序运行到一半时,突然被另一个线程抢占了运行权,此时这个线程数据处理了一半,而另一个线程也在处理这个数据,那么就会出现重复操作数据的现象,最终导致整个系统的混乱。解决同步问题的方法有两种:一种是同步块,另一种是同步化方法。
同步块
使具有某个对象监视点的线程,获得运行权限的一种方法,每个对象只能在拥有这个监视点的情况下,才能获得运行权限:synchronized(obj) { 代码段 }
。
obj
是一个监视点对象,可以是实际存在的,也可以是假设的。在很多程序段中,这个监视点对象都是假设的。其实这个监视点就相当于一把锁,给一个线程上了锁,那么其他线程就会被拒之门外,就无法得到这把锁。直到这个线程执行完了,才会将这个锁交给其他线程。其他的线程得到锁后,将自己的程序锁住,再将其他线程拒之门
外。
同步化方法
对整个方法进行同步:synchronized void f() { 代码 }
。
异常
Java
中的异常分为两大类:错误Error
和异常Exception
Error
一般是指Java
虚拟机相关的问题,如系统崩溃、虚拟机出错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中断,通常应用程序无法处理这些错误,因此应用程序不应该捕获Error
对象,也无须在其throws
子句中声明该方法抛出任何Error
或其子类。
Java
提供了2种异常机制:
- 运行时异常(
RuntimeExepction
):我们可以不处理。当出现这样的异常时,总是由虚拟机接管。 - 检查式异常(
CheckedExecption
):对于这种异常,Java编译器要求我们必须对出现的这些异常进行catch
。 所以,面对这种异常不管我们是否愿意,要么用try-catch
语句捕获它,要么用throws
子句声明抛出它,否则编译不会通过。
常见的5种运行时异常:
-
ClassCastException
(类转换异常) -
IndexOutOfBoundsException
(数组越界) -
NullPointerException
(空指针) -
ArrayStoreException
(数据存储异常,操作数组时类型不一致) -
BufferOverflowException
(缓存区溢出异常)
垃圾回收和内存管理
垃圾回收的主要问题是程序无法估计时间延迟导致程序执行的延迟。虽然拥有了垃圾回收机制,但是Java
程序仍然可能存在内存泄漏。
有关内存管理的经验:
- 最基本的建议就是尽早释放无用对象的引用。大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域后,自动设置为
null
。在使用这种方式时,必须特别注意一些复杂的对象。例如,数组、队列、树、图等,这些对象之间的相互引用关系较为复杂。对于这类对象,GC
(垃圾回收)回收它的效率一般较低,如果程序允许,尽早将不用的引用对象赋为null
,这样可以加速GC
的工作。 - 尽量少用
finalize
函数。finalize
函数是Java
给程序员提供的一个释放对象或资源的机会。但是,它会加大GC
的工作量,因此尽量少采用finalize
方式回收资源。 - 注意集合数据类型,包括数组、树、图、链表等数据结构,这些数据结构对
GC
来说,回收更为复杂。另外,注意全局变量以及静态变量,这些变量往往容易引起悬挂对象,造成内存浪费。 - 尽量避免在类的默认构造器中创建、初始化大量的对象,防止在调用其自己类的构造器时,造成不必要的内存资源浪费。
- 尽量避免强制系统做垃圾内存的回收(通过显式调用方法
System.gc()
),增长系统做垃圾回收的最终时间,降低系统性能。 - 尽量避免显式申请数组空间,当不得不显式地申请数组空间时,尽量准确地估计出其合理值,以免造成不必要的系统内存开销。
- 尽量在合适的场景下,使用对象池技术以提高系统性能,缩减系统内存开销。但是要注意对象池的尺寸不易过大,及时清除无效对象释放内存资源。综合考虑应用运行环境的内存资源限制,避免过高估计运行环境所提供内存资源的数量。
输入输出
输入输出类中有关于文件操作的类File
,关于以字节方式访问文件的类InputStream
和类OutputStream
,关于以字符方式访问文件的类Reader
和类Writer
。
File
类提供了与文件或目录相关的信息,是唯一代表磁盘文件对象的类。
当具体到对文件访问时,就会涉及流(Stream
)的概念。流就是数据流向某个对象,并且到达这个对象的过程。
输入流和输出流
输入流:从目标程序中,将数据以流的形式复制到流对象中,然后,再从流对象中将数据读取出来。
输出流:将数据以流的形式复制到流对象中去,再从这些流对象中取出流,写入到目标中。
程序读取数据称为打开输入流,程序向其他源写入数据称为打开输出流。
字节流
数据流的父类是InputStream
和OutputStream
,为抽象类。提供了read
、write
、close
方法。处理以字节为单位的数据
- 文件字节流
- 字节流的子类,继承了父类的方法
-
FileInputStream
输入流、FileOutputStream
输出流
- 多字节数据读取Filter类
-
Filter
类能够进行多字节数据的读取,可以方便地处理那些除字节数据类型以外的数据。有FileInputstream
和FilterOutputStream
。 - 先通过
FileInputstream
类读取数据,然后通过FilterInputStream
类对数据进行组合,最后再输出数据 - 先通过
FilterOutputStream
类,将所有这些类型的数据分解成字节类型的数据,再将字节类型的数据通过FileOutputStream
类,向目标对象输出数据 -
FilterOutputStream
类和FilterInputStream
类同样很难处理整型、字符串型等数据 - 常见的子类有
DataInputStream
,BufferedInputStream
以及PushBackInputStream
等,总结为:
-
DataInputStream
:数据输入流,以机器无关的方式读取Java
的基本类型 -
BufferedInputStream
:缓冲输入流,由于基础输入流一个字节一个字节读取,频繁与磁盘进行交互,造成读取速度较低。缓冲流的存在就是先将数据读取到缓冲流(内存中),然后一次性从内存中读取多个字符,提高读取的效率 -
PushInputStream
:回退输入流,Java
中读取数据的方式是顺序读取,如果某个数据不需要读取,需要程序处理,PushBackInputStream
就可以将某些不需要的数据回退到缓冲中
- 增强的多字节流
- 引进了
DataInput
接口和DataOutput
接口,同时DataInputStream
类和DataOutputStream
类分别实现了以上两个接口,并继承了FilterInputStream
类和FilterOutputStream
类
字符流
可以一次性处理两个字节的流,称为字符流。字符流分为两个类:Reader
类和Writer
类。Reader
类负责字符输入工作,而Writer
类负责字符输出工作
- 抽象字符流
作为父类,Reader
和Writer
,为抽象类。提供了read
、write
方法但没有被实现 - 带缓存的字符流
-
InputStreamReader
和BufferedReader
:先从文件中以字节形式读取数据,然后将数据组合成字符型数据,最后将所有读取的数据缓存起来一起输出 -
OutputStreamWriter
和BufferedWriter
:先是以字符的形式将数据缓存起来,然后将其变成字节的形式写入文件
- 字符流FileReader类和FileWrite
-
FileReader
类和FileWriter
类分别是InputStreamReader
类和OutputStreamWriter
类的子类,它们提供了将字符数据直接写入文件,或从文件中直接读出字符数据的简便方法
- 标准输入流
-
System.in
用于从标准键盘输入设备读入数据,其返回一个InputStream
类型
- 打印输入
-
PrintStream
与PrintWriter
类都是打印输出流,它们在许多方面提供了相似的功能。它们将各种基本类型的数据输出到字符串流中,并提供了自动刷新功能。这两个类的不同点,也是在自动刷新功能上 -
PrintStream
类会调用println()
方法,其输出会包含换行符。但是PrintWriter
类只有在调用println()
方法时才会自动刷新
- 随机文件访问RandomAccessFile类
- 随机文件访问,就是指可以读写任意位置数据的文件
-
RandomAccessFile
类实现了Datainput
与Dataoutput
接口,所以可以读取基本数据类型的数据。为了能够随机访问,必须先创建对象 - 存取模式总共有4种:分别
r
、rw
、rws
、rwd
。r
代表以只读方式打开文件,若此时进行写操作会出错;rw
、rws
、rwd
是以读写模式打开文件,若文件不存在,则创建它
对象序列化控制输入输出
对象序列化是将对象写入流,而序列化读取则指从流中获取数据后,重构对象的过程。只有实现了Serializable
接口的对象才是可序列化对象
- 对象序列化处理
-
ObjectOutputStream
类继承了OutputStream
类,同时实现了ObjectOutput
接口,提供将对象序列化并写入流中的功能:ObjectOutputStream (OutputStream out)
-
ObjectInputStream
类继承了InputStream
类,同时实现了ObjectInput
接口,提供将对象序列化并从流中读取出来的功能:ObjectInputStream (InputStream out)
数据结构
基本概念:
- 数据:对客观事物的符号的表示,是所有能输入到计算机中,并被计算机程序处理的符号的总称
- 数据元素:数据的基本单位,在计算机程序中通常作为一个整体来处理。一个数据元素由多个数据项组成,数据项是数据不可分割的最小单位
- 数据结构:相互之间存在一种或多种特定关系的数据元素的集合
数据元素相互之间的关系称为结构。根据数据元素之间关系的不同特性,通常分为下列4类基本结构:
- 集合:数据元素同属一个集合
- 线性结构:数据元素间存在一对一的关系
- 树形结构:结构中元素间是一对多的关系
- 图(网)状结构:结构中元素间是多对多的关系
数据又有逻辑结构和物理结构之分:
- 逻辑结构:数据元素之间存在的关系(逻辑关系)称为数据的逻辑结构
- 物理结构:数据结构在计算机中的表示称为数据的物理结构
- 一种逻辑结构可映像成不同的存储结构:顺序存储结构和非顺序存储结构(或称为链式存储结构和散列结构)
集合接口
Collection
接口是数据集合接口,它位于数据结构API
的最上部。构成Collection
的单位,被称之为元素。接口提供了添加、删除元素等管理数据的功能。常用的集合有List
集合、Set
集合和Map
集合,其中List
与Set
继承了Collection
接口,各接口还提供了不同的实现类。上述集合类的继承关系如图所示。
- List接口
有序的Collection,能精确地控制每个元素插入的位置。实现List
接口的常用类:
-
LinkedList
链表类:允许null
元素。此外在首部或尾部提供额外的get()
、remove()
、insert()
等方法。没有同步方法,如果多个线程同时访问一个List
,则必须自己实现访问同步,一种解决方法是在创建List
时构造一个同步的List
:
List list = Collections.synchronizedList(new LinkedList(...));
实现原理:LinkedList
内部是一个双向链表,add
新数据的时候,其实就是调用linklast
在链表尾部插入数据。删除的时候直接找到对应数据,替换掉链表的前后节点即可。
-
ArrayList
数组列表类:实现了可变大小的数组,允许存储所有元素,包括null。没有同步。
实现原理:ArrayList
内部就是一个默认大小为10
的动态对象数组容器,每当add
一个新数据的时候,如果大于原来的容器大小,则会通过Arrays.copyOf
把容器大小增加到原来的1.5
倍,以此类推。当可以预知数据大小,可以通过initialCapacity
来默认设置动态数据的大小,减少扩容带来的资源消耗。
- Set接口
不包含重复元素的Collection
,最多有一个null
元素。实现Set
接口的常用类:
-
HashSet
集合类:允许null
元素,不保证集合的迭代顺序,基本操作包括add()
、remove()
、contains()
和size()
等方法 -
TreeSet
集合类:实现了排序功能。存储在该集合中的元素默认按照升序排列元素,或者根据使用的构造方法不同,可能会按照元素的自然顺序进行排序,或者按照在创建Set
集合时所提供的比较器进行排序
- Map接口
不能包含相同的key
,每个key
只能映射一个value
。提供3种集合的视图,Map
的内容可以被当作一组key
集合、一组value
集合或一组key-value
映射。
-
Hashtable
散列表类:任何非空(non-null
)的对象都可作为key
或value
。添加数据使用put(key,value)
方法,取出数据使用get(key)
方法,这两个基本操作的时间开销为常数。通常默认的load factor=0.75
较好地实现了时间和空间的均衡,增大load factor
可以节省空间,但相应的查找时间将增大,这会影响像get
和put
这样的操作。 -
HashMap
散列映射类:非同步的,并且允许null
,即null value
和null key
。如将HashMap
视为Collection
时(values()
方法可返回Collection
),其迭代器操作时间开销和HashMap
的容量成比例。因此,迭代操作的性能相当重要,切记不要将HashMap
的初始化容量设得过高,或者将load factor
设得过低。
实现原理:HashMap
内部其实是一个数组,每个数组下是一个单向链表。HashMap
中的数组是一个取名为Entry
的类,类包含(key,value, next
)这几个属性。存放规则为,数组下标按hash(key)%len
获得,取得数组后则查找对应数组的值。HashMap
还有个负载因子(默认0.75
),当里面数组填满了75%
的时候,会进行扩展到原来大小的2
倍。在Java 8
之后hashmap
进行了优化:由于单向链表的查询时间复杂度为o(n)
,在极端情况下(每次都查找)可能存在性能问题,于是Java 8
针对链表长度大于8
的情况会使用时间复杂度为O(log n)
的红黑树进行存储来提升存储查询的效率,时间复杂度就从原来的O(1)+O(n)
变成了O(1)+O(log n)
,优化了极端情况导致的性能问题。 -
LinkedHashMap
实现原理:LinkedHashMap
内部是双向链表和**HashMap
的结合,支持多种迭代顺序,默认按插入顺序,也可以按访问**顺序。
- 访问顺序(
accessOrder=true
):调用过get
访问的元素会放到链尾,迭代会从链首开始 - 插入顺序(
accessOrder=false
) :按插入顺序迭代出来
-
TreeMap
实现原理:TreeMap
内部是基于红黑树实现的,并且默认会通过compareTo
按照key
类型进行自然排序。TreeSet
的底层是TreeMap
。
- Iterator迭代器接口
提供一种方法访问一个容器Container
对象中各个元素,而又不需暴露该对象的内部细节。Iterator
接口中定义了以下3个方法。所有Collection
接口的子类、子接口都支持Iterator
迭代器。
-
hasNext()
:是否还有下一个元素。 -
next()
:返回下一个元素。 -
remove()
:删除当前元素。
迭代器模式由以下角色组成:
- 迭代器角色
Iterator
:负责定义访问和遍历元素的接口。 - 具体迭代器角色
Concrete Iterator
:实现迭代器接口,并记录遍历中的当前位置。 - 容器角色
Container
:负责提供创建具体迭代器角色的接口。 - 具体容器角色
Concrete Container
:实现创建具体迭代器角色的接口——这个具体迭代器角色与该容器的结构相关。
迭代器模式给容器的应用带来以下好处:
- 支持以不同的方式遍历一个容器角色,根据实现方式的不同,效果上会有差别。
- 简化了容器的接口,但是在
java Collection
中为了提高可扩展性,容器还是提供了遍历的接口。 - 对同一个容器对象,可以同时进行多个遍历,因为遍历状态保存在每一个迭代器对象中。
迭代器模式的适用范围如下:
- 访问一个容器对象的内容而无须暴露它的内部表示。
- 支持对容器对象的多种遍历。
- 为遍历不同的容器结构提供一个统一的接口(多态迭代)。
XML
基础内容
XML
(Extensible Markup Language
, 可扩展的标记语言)是SGML
(Standard Generalized Markup Language
, 标准通用标记语言)的一个子集,其目标是在网络上以类似HTML
的方式实现文件的发送、接收和处理。XML
的出现极大地简化且提高了SGML
与HTML
之间的通用性。
为了能够让XML
的文档具有可读性,XML文档采取了数据与文档样式分离的原则。XML
文档只提供数据,而XSL
包括数据样式,文档的结构则使用DTD
。
-
XML
声明
在一个完整的XML
文档中必须包含一个XML
文档声明,该声明必须位于文档的第一行。这个声明表示该文档是一个XML
文档,以及遵循的是哪个XML
版本的规范:
<?xml 版本信息 (编码信息) (⽂档独⽴性信息)?>
<?xml version=1.0 encoding="gb2312" standalone="yes"?>
- 版本信息:是指
XML
目前使用的是1.0
版还是1.1
版,一般用1.0
版本。 - 编码信息:就是指定文档使用的是何种编码格式。如果使用中文编码格式,则可以通过下面代码表示
encoding=‘gb2312’
;如果不设定编码信息,默认使用英文编码格式。 - 文档独立性信息:文档独立指当前文档是否依赖外部文档。如果依赖可以通过下列方式表示:
standalone=‘yes’
;如果不独立,将通过下列方式表示:standalone=‘no’
。
- 文档类型的声明
DTD
是一种保证XML
文档格式正确的有效方法,可以通过校验XML文档内容来验证其是否符合规范,元素和标签使用是否正确。在XML
文档中直接定义文档类型:
<!DocTYPE BIRDS [<!ELEMENT AUCTIONBLOCK (ITEM,BIRDS)>]>
- 元素
一个XML
元素由一个标记来定义。
- 非空元素构成:元素=起始标签+元素内容+结束标签
- 空元素构成:空元素=“<”+元素名称(属性名值对)+“/>”
- 标签:
- 起始标签=“<”+标签名称(属性名值对)+“>”
- 结束标签=“</”+标签名称+“>”
- 元素的内容:元素内容=(子元素|字符数据|字符数据段|引用|处理指令|注释)
- 子元素:被嵌套在上层元素之内
- 字符数据:文本的内容没有标签,也没有实体的引用
- 字符数据段:嵌入HTML文本
- 引用:用符号代表指定的内容,或者用符号代表不能直接使用的其他符号
- 注释
格式:<!--注释的内容-->
- 处理指令
允许文档中包含由应用程序来处理的指令。例如要处理Excel/CSS
。
处理指令的语法是以<?
开头,以?>
结尾:
<?xml-stylesheet href="hello.css" type="text/css" ?>
此处的xml-stylesheet
就是处理指令,它调用Excel
或者使用CSS
来处理表格数据
- 空白处理
在元素中使用一个特殊的属性xml:space
,来通知应用程序保留此元素的空白,在使用时必须要进行声明。xml:space
属性必须被声明为Enumerated
(枚举)类型,它的值必须是default
和preserve
两者之一,或者是两者都取。
-
default
:表示对此元素使用应用程序的默认空白处理模式。 -
preserve
:表示应用程序保留所有的空白。
- 行尾处理
在XML
空白字符中,有两个标准的ASCII
码行尾控制字符,一个是回车(#xA
),一个是换行(#xD
)。在XML
处理器解析前,要将所有的两字符序列#xD#Xa
及单独的字符都转换成#xA
。 - 语言标识
在文档中插入一个属性xml:lang
,来指定文档所使用的语言。一旦设定,将适合于它所在元素中的所有属性及元素的内容,除非被元素内容中另一个元素的xml:lang
属性所覆盖。 - 简单实例
<?xml version="1.0" encoding="gb2312"?> <!—⽂档声明-->
<留⾔本>
<留⾔记录>
<留⾔者姓名>KAI</留⾔者姓名>
<电⼦邮件>[email protected]</电⼦邮件>
<⽹址>http://www.17xml.com </⽹址>
<留⾔内容>千⼭万⽔总是情,常来泡妞⾏不⾏?咔咔:_) </留⾔内容>
</留⾔记录>
</留⾔本>
良好的XML文档
- 文档必须从XML声明开始。
- XML声明必须位于该文件的最开始位置。
- XML必须紧跟在“<?”之后,中间不能有空格等字符。
- 唯一的根元素。
- 根元素必须唯一。
- 根元素嵌套其他所有的后代元素。
- 根元素必须有起始标签和结束标签。
- XML文档中的其他非元素节点不一定包含在根元素中。
- 标签必须是闭合的。
- 起始标签必须有一个相应的结束标签与之对应。
- 空标签的约定。
- 空标签必须用“/>”来结束。
- 空标签可以带有属性。
- 层层嵌套。
- 子元素必须嵌套在父元素内,不能互相交错。
- 同层元素必须互相并列,不能互相嵌套。
- 区分大小写。
- 起始标签与结束标签大小写必须要分清。
- 属性设定。
- 属性赋值时都必须使用引号。
- 特殊的字符表示法。
- 预定义实体用实体引用方式。
网络编程
**服务器:**能够提供信息的计算机或程序。
**客户机:**指请求信息的计算机或程序。
TCP/IP协议和UDP协议
TCP/IP
协议是整个网络通信的核心协议。其中TCP
协议运行在客户终端上,是集成在操作系统内的一套协议软件,它的任务是在网络上的两个机器之间实现端到端的、可靠的数据传输功能。IP
协议运行在组成网络的核心设备路由器上,它也是集成在系统内的一层协议软件,负责将数据分组从源端发送到目的端,通过对整个网络拓扑结构的理解为分组的发送选择路由。
IP协议和IP地址
IP
地址是一个32
位的二进制序列,点分的每个部分占一个字节,使用十进制表达。
IP
地址由网络部分和主机部分组成。网络部分表示一个通信子网,子网内的主机可以不通过路由器而直接通信,主机部分标识该通信子网内的主机。
为了区分IP
地址的网络部分和主机部分给出了掩码的概念,掩码也用点分十进制表达。并且还可以用IP
地址后加一个/
跟上掩码的全部1
的数量表达掩码,如掩码255.255.255.0
也可以表达为/24
。
通过主机的IP
地址和网络掩码就可以计算该主机所在的网络,如主机的IP
地址为192.168.2.155/24
,则网络地址的计算方式是把网络掩码同IP地址进行二进制与运算。则上述主机的网络号为192.168.2.0
。
- URL
统一资源定位符,用于标识网络上的某种资源,如一个网页链接、一个视频文件等
如http://www.abc.com/bbs/index.jsp
。http
表示一种应用层的传输协议超文本传输协议,www.abc.com
是域名,而bbs
是网页所在的路径,index.jsp
是要访问的网页。其中应用层协议不只是HTTP
协议,还有FTP
协议、FILE
协议等。 - 网络域名
域名是从叶子节点开始上溯到根节点的路径,每个部分之间用点分割。DNS
的主要用途就是将主机名字和主机的IP地址进行映射,将名字映射为IP
地址。DNS
是一个分布式数据库服务器系统,存储域名和对应IP的信息。当用户使用域名访问时,本机的DNS
协议会向已经设置的DNS
服务器发出请求完成域名到IP
地址的转换,直到搜索到对应的IP
地址。 - TCP协议和端口
保障分组可靠地到达目的地是IP
协议无法解决的。此时需要它的上层协议TCP
来处理。
TCP
协议实现可靠通信的基础是采用了握手机制实现了数据的同步传输,即在通信的双方发送数据前首先建立连接,协商一些参数,如发送的数据字节数量、缓冲区大小等。一旦连接建立再传送数据,并且对于收到的每一个分组进行确认,这样很好地保证了数据的可靠传输。
TCP
协议提供了端口号的概念,每个端口号对应一个应用进程,如端口号80
代表HTTP
连接,端口号21
代表FTP
连接服务。这样TCP
协议软件通过端口号识别不同的进程。
端口号的设置有一定的限制,最大数是65535
,在1024
之前是well-known
端口号,是全世界统一的,如FTP
服务进程的端口号是25
,HTTP
服务进程的端口号是80
等。而1024~65535
之间是用户自己选择使用。 - UDP协议
用户数据报协议。该协议运行在TCP/IP
模型的传输层,该协议可以直接封装成IP
分组,不需要事先建立连接就可以发送这些封装好的IP
分组。
一个UDP
报文有两个端口,即源机器端口和目的机器端口、UDP
长度、UDP
校验和UDP
净荷组成,通过目的端口目的主机的传输层就知道把该报文递交给哪个处理进程。而源端口知道从目标主机返回的UDP
报文到达源主机后可以正确地提交给上层进程处理。UDP
数据段由**8
字节的头部和净荷部分组成,净荷中包含要传输的真实数据**。
UDP
协议不考虑流量控制、差错控制和损坏数据处理,即使收到的是受损的数据也不要求发送端重传。所有上述问题都要求应用层软件处理。但是,因为它是无连接的协议,所以也不需要事先建立连接,从而节约了建立连接的时间,传输数据是异步的,使得数据及时地发送到网络上,减少了数据处理和传输的时延。
套接字
网络程序中的套接字用来将应用程序与端口连接起来,套接字是一个软件实现,也是一个假想的装置。
在Java API
中,将套接字抽象化成为类,所以程序只需创建Socket
类的对象,就可以使用套接字。Java
使用Socket
的流对象进行数据传输,Socket
类中有输入流和输出流。
Java
中的**TCP
网络程序设计是指利用Socket
类编写通信程序**。设计TCP
程序的过程是:服务器的套接字等待客户机连接请求,当服务器接收到请求后就可以通过相应的方法获取输入流和输出流,从而实现相应的功能。
TCP/UDP程序设计
利用ServerSocket
和Socket
类来编写面向TCP
协议的程序步骤:
- (1) 服务器程序编写。
- 调用
ServerSocket(int port)
,创建一个服务器端套接字,并绑定到指定端口上。 - 调用
accept()
,监听连接请求,如客户端请求连接,则接受连接,返回通信套接字。 - 调用
Socket
类的getOutputStream()
和getInputStream()
,获取输出流和输入流,开始网络数据的发送和接收。 - 最后关闭通信流套接字。
- (2) 客户端程序编写。
- 调用
Socket()
,创建一个流套接字,并连接到服务器端。 - 调用
Socket
类的getOutputStream()
和getInputStream()
,获取输出流和输入流,开始网络数据的发送和接收。 - 最后关闭通信流套接字。
利用DatagramSocket
类来编写面向UDP
协议的程序步骤:
- (1) 接收端程序代码编写
- 调用
DatagramSocket(int port)
创建一个数据报套接字,并且绑定到指定端口上。 - 调用
DatagramPacket(byte[]buf,int length)
,建立一个字节数组以接收UDP
包。 - 调用
DatagramSocket
类的receive()
,接收UDP
包。 - 关闭数据报套接字。
- (2) 发送端程序编写
- 调用
DatagramSocket()
,创建一个数据包套接字。 - 调用
DatagramPacket(byte[]buf,int offset,int length,InetAddress address,int port)
,建立要发送的UDP
包。 - 调用
DatagramSocket
类的send()
,发送UDP
包。 - 关闭数据包套接字。
零碎小知识点
1、Java
文件的扩展名区分大小写
2、JDK
工具库:javac
编译器、java
解释器、appletviewer
小程序浏览器、javadoc
文档生成器、jdb
调试器、javah
生成C过程头文件、javap
反汇编器
3、类与类之间最常见的关系主要有3种:依赖(uses–a
) 、聚合(has–a
) 、继承(is–a
)
4、对于枚举的构造函数,必须放在枚举常量的后面,同时构造函数的修饰符必须是private
。枚举类型的自定义构造函数不能覆盖默认执行的构造函数,只会在其后面执行。
5、通过自动装箱方式返回同一数值的对象时,如果该数值在-128~127
之间(包含它们自己),返回的对象会引用同一对象;否则则创建新的对象。
常见疑难解答
1、一般的程序可否用分支语句来代替条件语句
这个要视具体情况而定,如果条件在三重之内,最好使用条件语句。如果超过了三重,最好使用分支语句。
2、普通循环是使用for语句还是while语句
根据情况不同而定,for
循环语句主要针对有限循环而言,也就是说,当循环有上限的时候,一般使用for
循环。while
循环语句则针对那些无限循环的代码而言,当循环没有明确上限,上限只是根据程序中的条件而定。
3、equals和“==”的区别
如果操作两边都是对象句柄,就比较两个句柄是否指向同一个对象。如果两边是基本类型,比较的就是值。
equals
比较的是两个对象的内容,如果不重载equals
方法,自动调用object
的equals
方法,则和“==”
样。在JDK
中像String
、Integer
,默认重载了equals
方法,则比较的是对象的内容。在实际编程中,建议使用equals
方法。
4、String类为何被定义成final约束
主要是考虑“效率”和“安全性”的缘故。若String
允许被继承,则其频繁地被使用,可能会降低程序的性能,所以String
被定义成final
。
5、如何设计继承
- 把通用操作与方法放到父类中,因为一个父类可以有好几个子类。把通用的操作放到父类中,带来的好处是多方面的:一是避免代码重复,二是避免了人为因素导致的不一致。
- 不要使用受保护字段,也就是
protected
字段。 - 尽管类的继承给开发带来了好处和方便,但如果不希望自己的类再被扩展,也就是不希望再产生子类时,可在类的声明之前加上
final
关键字,这样此类就不能再被继承。
6、动态和静态编译是什么
允许对对象进行不同的操作,但具体的操作却取决于对象的类型。
程序在编译的时候,什么函数对哪个对象执行什么操作都已经确定,这就称作静态编译。多态是动态编译,动态编译就是在程序执行的过程中,根据不同对象类型有不同的绑定,其通过一个方法接口,实现多个不同的实现过程。这依赖于编译时编译器对同一个方法不同参数的识别。
7、多态与重载的区别
重载是在一个类里,名字相同但参数不同的方法。多态是为了避免在父类里大量重载,而引起代码臃肿且难于维护的解决方案。多态有两种表现形式:重载和覆盖。
8、抽象与接口的区别
共同点:
- 都不能创建实例对象,因为它们都是抽象的。
- 虽然不能直接通过关键字“new”创建对象实例,但可以声明变量,通过变量指向子类或实现类的对象,来创建对象实例。
不同点:
-
Java
不支持多重继承,即一个子类只能有一个父类,但一个子类可以实现多个接口。 - 接口内不能有实例字段,只能有静态变量,抽象类可以拥有实例字段。
- 接口内方法自动设置为
public
的,抽象类中的方法必须手动声明访问控制符。
9、线程与线程之间怎么通信
不同线程共享一个变量,并对此变量的访问进行同步,因为它们共享一个内存空间,所以相比之下,它比进程之间通信要简单容易得多。
10、进程的死锁和饥饿
饥饿是指系统不能保证某个进程的等待时间上界,从而使该进程长时间等待,当等待时间给进程推进和响应带来明显影响时,称发生了进程饥饿。当饥饿到一定程度的进程所赋予的任务即使完成也不再具有实际意义时称该进程被饿死。饥饿是个异步过程。
当一个或多个进程,在一个给定的任务中,协同作用、互相干涉,而导致一个或者更多进程永远等待下去,死锁就发生了。
与此类似,当一个进程永久性地占有资源,使得其他进程得不到该资源,就发生了饥饿。在饥饿的情形下,系统不处于死锁状态中,因为有一个进程仍在处理之中,只是其他进程永远得不到执行的机会。
死锁的发生情况:
- 相互排斥:一个线程或者进程永远占有共享资源,例如,独占该资源。
- 循环等待:进程A等待进程B,而后者又在等待进程C,而进程C又在等待进程A。
- 部分分配:资源被部分分配,例如,进程A和B都需要访问一个文件,并且都要用到打印机,进程A获得了文件资源,进程B获得了打印机资源,但是两个进程不能获得全部的资源。
- 缺少优先权:一个进程访问了某个资源,但是一直不释放该资源,即使该进程处于阻塞状态。
死锁和饥饿的不同点:
- 死锁进程都处于等待状态,处于运行或就绪状态的进程并非处于等待状态,但却可能被饿死
- 死锁进程等待永远不会被释放的资源;饿死进程等待会被释放但却不会分配给自己的资源,表现为等待时限没有上界
- 死锁一定发生了循环等待;而饿死则不然
- 死锁一定涉及多个进程;而饥饿或被饿死的进程可能只有一个
- 在饥饿的情形下,系统中有至少一个进程能正常运行,只是饥饿进程得不到执行机会;而死锁则可能会最终使整个系统陷入死锁并崩溃
11、什么时候会涉及线程程序
当遇到一个对象要做出多个动作,并且多个动作又是穿插在一起时,就要使用线程的概念来编写程序。
在网络编程中,网络上不同的用户操作一个对象时,也可以借助线程来完成程序。
12、多线程的死锁问题
由于线程会进入阻塞状态,并且对象同步锁的存在,使得只有获得对象的锁才能访问该对象。因此很容易发生循环死锁。如线程A等待线程B释放锁,而线程B等待线程C释放锁,线程C又等待线程A释放锁,这样就造成一个轮回等待。3个线程都无法继续运行。
避免死锁的基本原则:
- 避免使用
suspend()
和resume()
方法,这些方法具有与生俱来产生死锁的缺点。 - 不要对长时间
I/O
操作的方法施加锁。 - 使用多个锁时,确保所有线程都按相同的顺序获得锁。
13、多线程的缺点
- **等待访问共享资源时使得程序运行变慢。**如果用户访问网络数据库,而改善数据库的访问是互斥的,所以一个线程在访问大量数据或修改大量数据时,其他线程就只有等待而不能执行,同时如果把网络链接和数据传输的时间计算在内,则等待的时间或许是“不可忍受的”。
- **当线程数量增多时,对线程的管理要求额外的CPU开销。**虽然线程是轻量级进程和其他线程共享一些数据,但是毕竟每个线程都需要自己的管理资源,而这些资源的管理会耗费
CPU
时间片,如果线程数量增多到一定程度,如100
个以上,则线程的管理开销代价会增大。 - **死锁是难以避免的,只有依靠程序员谨慎地设计多线程程序。**任何语言都不可能提供预防死锁的方法,
Java
也不例外,除了尽量不使用控制线程的一些方法如suspend()
,resume()
,需要认真地分析线程的执行过程,以避免线程间的死锁。 - 随意使用线程技术有时会耗费系统资源,所以要求程序员知道何时使用多线程,以及何时避免使用该技术。
14、为什么抛出的异常一定是检查异常
RuntimeException
与Error
可以在任何代码中产生。它们不需要由程序员显式地抛出,一旦出现错误,那么相应的异常会被自动抛出。
而检查异常是由程序员抛出的,这分为两种情况:程序员调用会抛出异常的库函数、程序员自己使用throw
语句抛出异常。
遇到Error
,程序员一般是无能为力的,遇到RuntimeException
,那么一定是程序存在逻辑错误,要对程序进行修改。
只有检查异常才是程序员所关心的,程序应该抛出或处理检查异常。覆盖父类某方法的子类方法,不能抛出比父类方法更多的异常。有时设计父类的方法时,会声明抛出异常,但实现方法的代码却并不抛出异常,这样做的目的就是,方便子类方法覆盖父类方法时可以抛出异常。
15、字节流与字符流的主要区别
字节流是最基本的,所有的InputStrem
和OutputStream
的子类都是字节流,其主要用于处理二进制数据,并按字节来处理。
实际开发中很多的数据是文本,这就提出了字符流的概念,它按虚拟机的encode
来处理,也就是要进行字符集的转化。
这两者之间通过InputStreamReader
和OutputStreamWriter
来关联。实际上,通过byte[]
和String
来关联在实际开发中出现的汉字问题,这都是在字符流和字节流之间转化不统一而造成的。在从字节流转化为字符流时,实际上就是byte[]
转化为String
。
至于java.io
中还出现了许多其他的流,主要是为了提高性能和使用方便,如BufferedInputStream
、PipedInputStream
等。
16、什么是管道流
管道流是输入输出并用。例如,将数据从输出管道进,从输入管道出。
17、Collection集合接口和Collections集合类的区别
-
Collections
是java.util
下的类,它包含各种有关集合操作的静态方法。 -
Collection
是java.util
下的接口,它是各种集合结构的父接口。 -
List
、Set
是继承自Collection
接口,Map
不是继承自Collection
接口。
18、ArrayList数组列表类和Vector存储类的区别
- 同步性。
Vector
是线程安全的,是同步的。而ArrayList
是线程不安全的,不是同步的。 - 数据增长。当需要增长时,
Vector
默认增长为原来一倍,而ArrayList
却是原来的一半。
19、HashMap散列映射和Hashtable散列表的区别
二者都属于Map
接口的类,作用都是将唯一键映射到特定的值上。它们的区别有两点:
-
HashMap
类没有分类或排序,它允许一个null
键和多个null
值。 -
Hashtable
类似于HashMap
,但是不允许null
键和null
值,它也比HashMap
慢,因为它是同步的。
20、数据结构的种类有哪些
数据结构一般分为两大类:线性数据结构和非线性数据结构。
线性数据结构包括:线性表、栈、队列、串、数组和文件;
非线性数据结构包括:树、图等。
21、List接口和Set接口的区别
-
Set
:从Collection
接口继承而来,但没有提供新的抽象的实现方法,Set
不能包含重复元素。 -
List
:是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式。这里的有序就是指有顺序的排放,并不是排序。
22、为什么Map接口不继承Collection接口
Map
接口是键值对映射(即key-value
映射),而Collection
接口提供的是一组数据,这两个集合存储的数据类型就不同。
如果Map
继承了Collection
接口,那么所有实现了Map
接口的类到底是用Map
的键值对映射数据还是用Collection
的一组数据呢
Map
继承Collection
,违反了接口分离原则。数据结构不同,操作就不一样,所以接口是分开的。(接口分离原则:客户端不应该依赖它不需要的接口,目的是解耦,接口尽量小)
23、哪些是线程安全的数据结构
-
Vector
:比ArrayList
多了个同步化机制(线程安全)。 -
Stack
:堆栈类,先进后出。 -
Hashtable
:比HashMap
多了个线程安全。 -
Enumeration
:枚举,相当于迭代器。
除了这些之外,其他的都是非线程安全的类和接口。线程安全类的方法是同步的,每次只能一个访问,是重量级对象,效率较低。对于非线程安全的类和接口,在多线程中需要程序员自己处理线程安全问题。
24、Vector是什么样的数据结构
一般用数组列表代替它,因为它们的使用方法几乎一样,唯独不同的就在线程安全方面。数组列表是非线程安全类,在实现线程编程时,要自己处理安全问题,而Vector
则是线程安全类,自动会处理安全问题。
Vector
类提供实现可增长数组的功能,随着更多元素加入其中,数组变得更大。在删除一些元素之后,数组变小。
25、XML与HTML的区别
XML
和HTML
的目标不同:HTML
的设计目标是显示数据并集中于数据外观,而XML
的设计目标是描述数据并集中于数据的内容。
与HTML
相似,XML
不进行任何操作。程序中必须编写代码,来实现对XML
格式数据的操作。
与HTML
不同,XML
标记由架构或文档的作者定义,并且是无限制的。HTML
标记则是预定义的,HTML
作者只能使用当前HTML
标准所支持的标记。
26、TCP和UDP的区别
UDP
不提供可靠的数据传输,事实上,该协议不能保证数据准确无误地到达目的地。UDP
在许多方面非常有效。当某个程序的目标是尽快地传输尽可能多的信息时(其中任意给定数据的重要性相对较低),可使用UDP
;ICQ
短消息使用UDP
协议发送消息。
TCP
的目的是提供可靠的数据传输,并在相互进行通信的设备或服务之间保持一个虚拟连接。TCP
在数据包接收无序、丢失或在交付期间被破坏时,负责数据恢复。它通过为其发送的每个数据包提供一个序号来完成此恢复。记住,较低的网络层会将每个数据包视为一个独立的单元,因此,数据包可以沿完全不同的路径发送,即使它们都是同一消息的组成部分。这种路由与网络层处理分段和重新组装数据包的方式非常相似,只是级别更高而已。
为确保正确地接收数据,TCP
要求在目标计算机成功收到数据时,发回一个确认(即ACK
)。如果在某个时限内未收到相应的ACK
,将重新传送数据包。如果网络拥塞,这种重新传送将导致发送的数据包重复,但是,接收计算机可使用数据包的序号来确定它是否为重复数据包,并在必要时丢弃它。
27、什么是TCP/IP协议,分为几层,什么功能
TCP/IP
(Transmission Control Protocol/Internet Protocol
,传输控制协议/互联网协议)协议族包含了TCP/IP
层次模型,协议共分为4
层:应用层、传输层、网络层、数据链路层。
应用层是用户所面向的应用程序的统称。TCP/IP
协议族在这一层面有很多协议来支持不同的应用,大家所熟悉的基于Internet
应用的实现,就离不开这些协议。如进行万维网(WWW
)访问用到了HTTP
协议,文件传输用FTP
协议,电子邮件发送用SMTP
,域名的解析用DNS
协议,远程登录用Telnet
协议等,都是属于TCP/IP
应用层。就用户而言,看到的是由一个个软件所构筑的、大多数为图形化的操作界面,而实际后台运行的便是上述协议。
传输层的功能主要是提供应用程序间的通信,TCP/IP
协议族在这一层的协议有TCP
和UDP
。
网络层是TCP/IP
协议族中非常关键的一层,主要定义了IP
地址格式,从而能够使得不同应用类型的数据在Internet
上传输,IP
协议就是一个网络层协议。
数据链路层是TCP/IP
软件的最底层,负责接收IP
数据包并通过网络发送之,或者从网络上接收物理帧,抽出IP数据报,交给IP
层。
TCP(Transmission Control Protocol)
和UDP(User Datagram Protocol)
协议属于传输层协议。其中TCP
提供IP
环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复用,通过面向连接、端到端和可靠的数据包发送。它是事先为所发送的数据开辟出连接好的通道,然后再进行数据发送。而UDP
则不为IP
提供可靠性、流控或差错恢复功能。
一般来说,TCP
对应的是可靠性要求高的应用,而UDP
对应的则是可靠性要求低、传输经济的应用。TCP
支持的应用协议主要有:Telnet
、FTP
、SMTP
等。UDP
支持的应用层协议主要有:NFS
(网络文件系统)、SNMP
(简单网络管理协议)、DNS
(主域名称系统)、TFTP
(通用文件传输协议)等。
IP
协议(Internet Protocol
)又称互联网协议,是支持网间互联的数据报协议,它与TCP
协议(传输控制协议)一起构成了TCP/IP
协议族的核心。它提供网间连接的完善功能,包括IP
数据包规定互连网络范围内的IP
地址格式。在互联网上,为了实现连接到网上的结点之间的通信,必须为每个结点(入网的计算机)分配一个地址,并且应当保证这个地址是全网唯一的,这便是IP
地址。
目前的IP
地址(IPv4
:IP
第4
版本)由32
个二进制位表示,每8
位二进制数为一个整数,中间由小数点间隔,如159.223.41.98
。整个IP
地址空间有4
组8
位二进制数,由表示主机所在的网络的地址,以及主机在该网络中的标识共同组成。为了便于寻址和层次化的构造网络,IP
地址被分为A、B、C、D、E
共5
类,商业应用中只用到A、B、C
这3
类。
A
类地址:A
类地址的网络标识由第一组8
位二进制数表示,网络中的主机标识占3
组8
位二进制数,A
类地址的特点是网络标识的第1
位二进制数取值必须为“0
”。不难算出,A
类地址允许有126
个网段,每个网络大约允许有1670
万台主机,通常分配给拥有大量主机的网络(如主干网)。
B
类地址:B
类地址的网络标识由前2
组8
位二进制数表示,网络中的主机标识占2
组8
位二进制数,B
类地址的特点是网络标识的前2
位二进制数取值必须为“10
”。B
类地址允许有16384
个网段,每个网络允许有65533
台主机,适用于结点比较多的网络(如区域网)。
C
类地址:C
类地址的网络标识由前3
组8
位二进制数表示,网络中主机标识占1
组8
位二进制数,C
类地址的特点是,网络标识的前3
位二进制数取值必须为“110
”。具有C
类地址的网络允许有254
台主机,适用于结点比较少的网络(如校园网)。
由于网络地址紧张、主机地址相对过剩,采取子网掩码的方式来指定网段号。TCP/IP
协议与低层的数据链路层和物理层无关,这也是TCP/IP
的重要特点,正因为如此,它能广泛地支持由低两层协议构成的物理网络结构。目前已使用TCP/IP
连接成洲际网、全国网与跨地区网。
面试题
1、Java的引用和C++的指针有什么区别
Java
的引用和C++
的指针都是指向一块内存地址的,通过引用或指针来完成对内存数据的操作。但是它们在实现、原理、作用等方面却有区别。
**1)类型:**引用其值为地址的数据元素,Java
封装了的地址,可以转化成字符串查看,长度可以不必关心。C++
指针是一个装地址的变量,长度一般是计算机字长,可以认为是个int
。
**2)所占内存:**引用声明时没有实体,不占空间。C++
指针如果声明后被用到才会赋值,如果用不到不会分配内存。
**3)类型转换:**引用的类型转换,也可能不成功,运行时会抛出异常或者不能通过编译。C++
指针只是个内存地址,指向哪里,对程序来说都还是一个地址,但可能所指的地址不是程序想要的。
**4)初始值:**引用初始值为Java
关键字null
。C++
指针是int
类型,如不初始化指针,那它的值就不是固定的了,这很危险。
**5)计算:**引用是不可以计算的。C++
指针是int
,它可以计算,所以经常用指针来代替数组下标。
**6)控制:**引用不可以计算,所以它只能在自己程序里,可以被控制。C++
指针是内存地址,也可以计算,所以它有可能指向了一个不归自己程序使用的内存地址,对于其他程序来说是很危险的,对自己程序来说也是不容易被控制的。
7)内存泄露:Java
引用不会产生内存泄露。C++
指针是容易产生内存泄露的,所以程序员要小心使用,及时回收。
8)作为参数:Java
的方法参数只是传值,引用作为参数使用的时候,函数内引用的是值的COPY
,所以在函数内交换两个引用参数是没意义的,因为函数只交换参数的COPY
值,但在函数内改变一个引用参数的属性是有意义的,因为引用参数的COPY
所引用的对象和引用参数是同一个对象。C++
指针作为参数给函数使用,实际上就是它所指的地址
在被函数操作,所以函数内用指针参数的操作都将直接作用到指针所指向的地址(变量、对象、函数等)。
总的来说,Java
中的引用和C++
中的指针本质上都是想通过一个叫做引用或者指针的东西,找到要操作的目标,方便在程序里操作。所不同的是,Java
的办法更安全、方便一些,但没有C++
的指针那么灵活。
2、类和对象有什么区别
面向对象的思想把程序里的一切都看成是对象,对象拥有各种属性和动作。同时,这些对象拥有一种共性,也就是它们同属于一类。
在Java
里,把这种共性称为类(class
),每一个实例称为对象(object
)。在类里定义属性和方法,每个对象都用new
关键字和类名来创造。
类的构造方法是一种比较特殊的方法,它不能被程序员显式地调用,只能在创建对象时由系统自动调用。构造方法的名字必须与类名完全相同。另外,在没有提供任何的构造方法时,系统会为类创建一个默认的构造方法,该构造方法也是无参数的,它什么也不做,但是,一旦提供了任何一种构造方法,该默认的构造方法就不会被自动提供了。
当需要使用对象的属性和方法的时候,只需要通过对象的引用小数点号.
进行调用即可。
类也可以有属于它的属性和方法,也就是静态(static
)成员,通过static
关键字进行定义。这些静态属性和方法属于类所有,被该类的所有对象共享,但是它只有一份,并不会随着对象的创建而新增。
3、说明private、protected、public和default的区别
除了default
以外,其他都是Java
语言的关键字。default
代表的是对类成员没有进行修饰的情况,它本身也代表了一种访问控制符。对于这4
种访问控制符来说,它们都可以修饰类的成员(包括静态和非静态成员),它们的修饰就控制了被它们修饰的成员能被其他的地方访问的限制情况。
对于范围概念来说,Java
指的范围包括:类内部、所在包下、子父类之间和外部包4
种情况。如果一个成员需要被外部包所访问,则必须使用public
修饰符;如果一个成员需要被定义在不同包下的子类所访问,则可以使用public
或protected
修饰符;如果一个成员需要被本包下的其他类所访问,则可以不用写任何的修饰符,使用public
或protected
也行;若一个成员想使用同类里边的其他成员,则使用任意一个修饰符即可;若一个成员不想被任何一个外部的类所访问,则使用private
关键字比较恰当。
1)对于public
修饰符,它具有最大的访问权限,可以访问任何一个在CLASSPATH
下的类、接口、异常等。它往往用于对外的情况,也就是对象或类对外的一种接口形式。
2)对于protected
修饰符,它主要的作用就是用来保护子类的。它的含义在于子类可以使用它修饰的成员,其他的不可以,它相当于传递给子类的一种继承的东西。
3)对于default
来说,有的时候也称为friendly
(友员),它是针对本包访问而设计的,任何处于本包下的类、接口、异常等,都可以互相访问,即使是父类没有用protected
修饰的成员也可以。
4)对于private
来说,它的访问权限仅限于类的内部,是一种封装的体现,例如,大多数的成员变量都是修饰为private
的,它们不希望被其他任何外部的类访问。
Java
的访问控制是停留在编译层的,也就是它不会在class
文件里留下任何的痕迹,只在编译的时候进行访问控制的检查。其实,通过反射的手段可以访问任何包下任何类里边的成员,访问类的私有成员也是可能的。
4、Java可以用非0来代表true吗
Java
是一种强类型的语言,它对条件表达式有非常严格的规定,只能使用boolean
型的数据进行条件判断。如果使用整型的非0
数进行条件判断,则体现为语法错误。
5、StringBuffer和StringBuilder存在的作用是什么
通过String
直接相加来拼接字符串的效率是很低的,其中可能会产生多余的String
对象。如果程序中需要拼接的字符串数量成千上万的话,那么JVM
的负荷是非常大的,严重地影响到程序的性能。
如果遇到有大量字符串需要拼接的话,应该使用StringBuffer
和StringBuilder
类,它们是对String
的一种补充。
StringBuffer
无法保证线程的安全,StringBuilder
可以保证线程的安全。
6、二维数组的长度是否固定
可以不固定
7、符合什么条件的数据集合可以使用foreach循环
foreach
循环就是遍历一个集合里的元素,起到替代迭代器的作用。从语法上来讲,数组或者实现了Iterable
接口的类实例,都是可以使用foreach
循环的。
数组是Java规定的东西,开发人员无法改变它,只能遵照它的使用语法来使用。但是,对于第二条规定“实现了Iterable
接口的类实例”,开发人员则可以自定义一个集合类。该自定义集合类主要需要做以下一些事情:
- 定义一个类,包含一个整型下标成员变量和一个集合对象(如数组或链表)。
- 将该类实现
Iterable
接口。 - 提供一个
Iterator
接口的实现,或者它本身就实现Iterator
接口。 - 使用下标成员变量和集合对象来完成
Iterator
接口所需要的方法。
foreach
的运行原理也是比较简单的,它的主要运行步骤如下:
- 调用指定集合对象的
iterator()
方法,得到迭代器。 - 使用迭代器的
hasNext()
方法判断是否有下一个元素来进行循环。 - 每调用一次循环
next()
方法,就得到下一个元素。
7、如何序列化和反序列化一个Java对象
对于对象的输入和输出,Java
的I/O
体系里主要提供了ObjectOutputStream
和ObjectInputStream
两个类供开发者使用,它们的大致使用思路如下:
- 让需要序列化的类实现
java.io.Serializable
接口。 - 提供静态的
long
型的常量serialVersionUID
。 - 如果是序列化对象,则用一个输出流创建一个
ObjectOutputStream
对象,然后调用writeObject()
方法。 - 如果是反序列化,则用一个输入流创建一个
ObjectInputStream
对象,然后调用readObject()
方法,得到一个Object
类型的对象,然后再做类型的强制转换。 - 最后关闭流。
8、如何使用Java的线程池
Java
提供了java.util.concurrent.ThreadPoolExecutor
类来使用线程池,通过它构造的对象,可以很容易地管理线程,并把线程代码与业务代码进行分离。
9、如何利用反射实例化一个类
根据调用构造方法的不同,用反射机制来实例化一个类,可以有两种途径。
- 如果使用无参数的构造方法,则直接使用
Class
类的newInstance()
方法即可; - 若需要使用特定的构造方法来创建对象,则需要先获取
Constructor
实例,再用newInstance()
方法创建对象。
10、TCP协议的通信特点是什么
- 面向连接的传输。
- 端到端的通信。
- 高可靠性,确保传输数据的正确性,不出现丢失或乱序。
- 采用字节流方式,即以字节为单位传输字节序列。
11、JDBC操作数据库的编程步骤
- 注册驱动程序。
- 获取数据库连接。
- 创建会话。
- 执行
SQL
语句。 - 处理结果集。
- 关闭连接。
12、如何使用连接池技术
数据库连接池技术是为了避免重复创建连接而设计的,它作为一个单独的程序模块运行,负责维护池子里面装的数据库的连接(Connection
)。程序员打开连接和关闭连接并不会造成真正意义上的连接创建和关闭,而只是连接池对连接对象的一种维护手段。
对于开发者来说,连接池与传统的JDBC
提供连接的方式不太一样,程序员必须使用数据源(Data source
)的形式获取连接池的连接,而数据源对象往往是以JNDI
的形式提供的。对于Java Web
和EJB
开发人员来说,需要参考一下具体的JavaEE
服务器关于连接池的使用手册。
13、接口和抽象类的区别
抽象类是一种功能不全的类,接口只是一个抽象方法声明和静态不能被修改的数据的集合,两者都不能被实例化。
从某种意义上说,接口是一种特殊形式的抽象类,在Java
语言中,抽象类表示一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
14、根据代码判断创建的对象个数
public static void main(String[] args) {
String str = new String("good"); //执⾏到这⼀⾏时,创建了⼏个对象?
String str1 = "good"; //执⾏到这⼀⾏时,创建了⼏个对象?
String str2 = new String("good"); //执⾏到这⼀⾏时,创建了⼏个对象?
System.out.println(str == str1); //输出结果是什么?
System.out.println(str.equals(str2)); //输出结果是什么?
System.out.println(str2 == str1); //输出结果是什么?
}
第2行处的答案是:两个对象。
第3行处的答案是:没有对象。
第4行处的答案是:1个对象。
第5行处的答案是:false。
第6行处的答案是:true。
第7行处的答案是:false。
15、ArrayList、Vector、LinkedList的存储性能和特性
ArrayList
和Vector
都使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。由于Vector
使用了synchronized
方法(线程安全),通常性能上较ArrayList
差,而LinkedList
使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。