1、什么是 IO?
I/O为input(输入)/ output(输出)的简称
input
代表将数据从其他地方写入程序,例如从文件中读取文件,将文件内容保存在程序内存中
output
代表程序将数据输出到某些地方,例如利用程序向文件中保存内容等,在Linux系统中,有一切皆文件的概念,不管对于任何文件,设备,网络设备等,在Linux下都被当做文件来进行处理;
2、常用的 IO 类有哪些?
在整个Java.io包中最重要的就是5个类和一个接口。
5个类指的是: File、InputStream、OutputStream、Writer、Reader;
一个接口指的是: Serializable
常用的IO 类
FileInputStream
FileOutputStream
BufferedInputStream
BufferedOutputStream
FileReader
FileWriter
BufferedReader
BufferedWriter
常用流的介绍
1)对文件进行操作:FileInputStream
(字节输入流),FileOutputStream
(字节输出流),FileReader
(字符输入流),FileWriter
(字符输出流)
2)对管道进行操作:PipedInputStream
(字节输入流),PipedOutStream
(字节输出流),PipedReader
(字符输入流),PipedWriter
(字符输出流)
PipedInputStream的一个实例要和PipedOutputStream的一个实例共同使用,共同完成管道的读取写入操作。主要用于线程操作。
3)字节/字符数组:ByteArrayInputStream
,ByteArrayOutputStream
,CharArrayReader
,CharArrayWriter
是在内存中开辟了一个字节或字符数组。
4)Buffered缓冲流:BufferedInputStream
,BufferedOutputStream
,BufferedReader
,BufferedWriter
,是带缓冲区的处理流,缓冲区的作用的主要目的是:避免每次和硬盘打交道,提高数据访问的效率。
5)转化流:InputStreamReader
:在读入数据的时候将字节转换成字符。OutputStreamWriter
:在写出数据的时候将字符转换成字节。
6)数据流:DataInputStream
,DataOutputStream
。因为平时若是我们输出一个8个字节的long类型或4个字节的float类型,那怎么办呢?可以一个字节一个字节输出,也可以把转换成字符串输出,但是这样转换费时间,若是直接输出该多好啊,因此这个数据流就解决了我们输出数据类型的困难。数据流可以直接输出float类型或long类型,提高了数据读写的效率。
7)打印流:printStream
,printWriter
,一般是打印到控制台,可以进行控制打印的地方和格式,其中的 print方法不会抛出异常,可以通过checkError方法来查看异常。
8)对象流:ObjectInputStream
,ObjectOutputStream
,把封装的对象直接输出,而不是一个个在转换成字符串再输出。
9)RandomAccessFile随机访问文件
10)ZipInputStream、ZipOutputStream读取zip文档 getNextEntry、putNextEntry 得到或创建ZipEntry对象。
3、你怎么理解 IO、BIO、NIO、AIO?
-
IO: 阻塞IO。
-
BIO: 同步阻塞IO。
服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器需要启动一个线程进行处理,如果这个链接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
- NIO: 同步非阻塞IO。
服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到
多路复用器
上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这需要用户进行不停的去询问。NIO的包括三个核心概念:缓冲区(Buffer)
、通道(Channel)
、选择器(Selector)
。
- AIO: 异步非阻塞IO。
最大的特性时具有异步能力,这种能力对 socket 与文件I/O都起作用。AIO其实是一种在读写操作结束之前允许进行其他操作的I/O处理。
4、什么是比特(Bit)、字节(Byte)、字符(Char)?
位(bit):是计算机中存储数据的最小单位,是二进制数中的一个位数,值为“0”或“1”。
字节(byte):计算机中存储数据的单元,是一个8位的二进制数,一个具体的存储空间。如0x01,0xFA。。。(计算机内部,一个字节可表示一个数据、一个英文字母,两个字节可表示一个汉字。1B=8bit)
字符(char):人们使用的一个记号,只是抽象意义上的一个符号。如‘1’,‘中’,‘¥’。。。
ASCII 编码 一个英文字母(不分大小写)占1个字节的空间,一个中文汉字占2个字节的空间。
GB 2312/GBK 编码中,一个汉字字符存储需要2个字节。
UTF-8 编码中,一个英文字母字符存储需要1个字节,一个汉字字符储存需要3到4个字节
5、Java 有哪几种类型的流?
首先应该从两个角度来看:
从输入输出方面来讲: Java中有 输入流
和 输出流
从流的编码方式来讲: Java中有 字节流
(byte)和 字符流
(char)
输出流:OutputStream, OutputStreamReder
输入流:InputStream, InputStreamReader
对于字节流而言:主要继承的抽象类为 InputStream和OutputStream
对于字符流而言:主要继承的抽象类为 InputStreamReader和OutputStreamReder
6、字节流和字符流的区别?
字节流在操作的时候本身是不会用到缓冲区(内存)的,是与文件本身直接操作的,而字符流在操作的时候是使用到缓冲区
的
字节流与字符流主要的区别是他们的的处理方式
字节流是最基本的,所有的InputStream和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的
但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的encode来处理,也就是要进行字符集的转化
这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联
在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的
在从字节流转化为字符流时,实际上就是byte[]转化为String时,
public String(byte bytes[], String charsetName)
有一个关键的参数字符集编码,通常我们都省略了,那系统就用操作系统的lang
而在字符流转化为字节流时,实际上是String转化为byte[]时,
byte[] String.getBytes(String charsetName)
也是一样的道理
至于java.io中还出现了许多其他的流,按主要是为了提高性能和使用方便,
如BufferedInputStream,PipedInputStream等
7、Java 序列化是什么?
序列化:把Java对象转换为字节序列的过程。
反序列化:把字节序列恢复为Java对象的过程。
8、怎么序列化一个对象?
序列化和反序列化的详解
实现序列化:
-
1)用流操作序列化: 让类实现Serializable接口,标注该类对象是可被序列, 使用输出流ObjectOutputStream/ 输入流ObjcetInputStream 进行序列化和反序列化
-
2)JSON工具: JSON.toJSONString(obj对象) 进行序列化, JSON.parseObject(str, 类.class) 进行反序列化 (String类实现了Serializable接口)
序列化的意义
-
①将对象转为字节流
存储到硬盘
上,当JVM停机的话,字节流还会在硬盘上默默等待,等待下一次JVM的启动,把序列化的对象,通过反序列化为原来的对象,并且序列化的二进制序列能够减少存储空间(永久性保存对象)。 -
②序列化成字节流形式的对象可以进行
网络传输
(二进制形式),方便了网络传输。 -
③通过序列化可以在进行远程调用时
进程间传递对象
。
9、Java 有哪两种序列化方式?
-
Serializable
:一个对象想要被序列化,那么它的类就要实现 此接口,这个对象的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。 -
Externalizable
:他是Serializable接口的子类,有时我们不希望序列化那么多,可以使用这个接口,这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性;
但是如果你只想隐藏一个属性,比如用户对象user的密码pwd,如果使用Externalizable,并除了pwd之外的每个属性都写在writeExternal()方法里,这样显得麻烦,可以使用Serializable接口,并在要隐藏的属性pwd前面加上transient
(瞬态的变量)就可以实现了。
10、怎么控制类中的某些变量不被序列化?
-
Externalizable
接口:他是Serializable接口的子类,有时我们不希望序列化那么多,可以使用这个接口,这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性; -
Serializable
接口,并在要隐藏的属性前面加上transient
(瞬态的变量)就可以实现了。
11、静态变量能不能被序列化?阐述静态变量和实例变量的区别。
静态变量(static
)不属于对象,属于类。不能被序列化。
还有瞬态的变量(transient
)也不能被序列化 。如果您不想序列某变量/字段就将其标记为瞬态。比如银行余额,信用卡详细信息等。
这里的不能序列化的意思,是序列化信息中不包含这个静态成员域
静态变量和实例变量的区别
静态变量: 是被static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝, 可以实现让多个对象共享内存, 同时也存在线程安全问题;
实例变量: 就是相当于该类的属性, 必须依存于某一实例, 需要先创建对象然后通过对象才能访问到它。
区别如下:
1,两个变量的生命周期不同。
成员变量: 随着对象的创建而存在,随着对象的回收而释放
静态变量: 随着类的加载而存在,随着类的卸载而释放(类不消失一直存在(不考虑回收机制),生命周期长)
2,调用方式不同。
成员变量: 只能被对象调用
静态变量: 可以被对象调用,还可以被类名调用(建议使用类名调用,便于区分)
3,内存分配方式不同。
成员变量: 必须创建了实例对象, 并初始化
静态变量: 不用创建任何实例对象,静态变量就会在类加载的时候被分配空间
4,数据存储位置不同。
成员变量: 数据存储在堆内存的对象中,所以也叫对象的特有数据。
静态变量: 数据存储在方法区(共享数据区)的静态区,所以也叫对象的共享数据
12. 写一个方法,输入一个文件名和一个字符串,统计这个字符串在这个文件中出现的次数。
public static int countWordInFile(String filename, String word) {
int counter = 0;
try (FileReader fr = new FileReader(filename)) {
try (BufferedReader br = new BufferedReader(fr)) {
String line = null;
while ((line = br.readLine()) != null) {
int index = -1;
while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) {
counter++;
line = line.substring(index + word.length());
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return counter;
}
13. 如何用Java代码列出一个目录下所有的文件?
class Test12 {
public static void main(String[] args) {
File f = new File("/Users/Hao/Downloads");
for(File temp : f.listFiles()) {
if(temp.isFile()) {
System.out.println(temp.getName());
}
}
}
}
如果需要对文件夹继续展开,代码如下所示:
class Test12 {
public static void main(String[] args) {
showDirectory(new File("/Users/Hao/Downloads"));
}
public static void showDirectory(File f) {
_walkDirectory(f, 0);
}
private static void _walkDirectory(File f, int level) {
if(f.isDirectory()) {
for(File temp : f.listFiles()) {
_walkDirectory(temp, level + 1);
}
}
else {
for(int i = 0; i < level - 1; i++) {
System.out.print("\t");
}
System.out.println(f.getName());
}
}
}
在Java 7中可以使用NIO.2的API来做同样的事情,代码如下所示:
class ShowFileTest {
public static void main(String[] args) throws IOException {
Path initPath = Paths.get("/Users/Hao/Downloads");
Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
System.out.println(file.getFileName().toString());
return FileVisitResult.CONTINUE;
}
});
}
}
14、用Java的套接字编程实现一个多线程的回显(echo)服务器。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class EchoServer {
private static final int ECHO_SERVER_PORT = 6789;
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(ECHO_SERVER_PORT)) {
System.out.println("服务器已经启动...");
while(true) {
Socket client = server.accept();
new Thread(new ClientHandler(client)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static class ClientHandler implements Runnable {
private Socket client;
public ClientHandler(Socket client) {
this.client = client;
}
@Override
public void run() {
try(BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter pw = new PrintWriter(client.getOutputStream())) {
String msg = br.readLine();
System.out.println("收到" + client.getInetAddress() + "发送的: " + msg);
pw.println(msg);
pw.flush();
} catch(Exception ex) {
ex.printStackTrace();
} finally {
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意:上面的代码使用了Java 7的TWR语法,由于很多外部资源类都间接的实现了AutoCloseable接口(单方法回调接口),因此可以利用TWR语法在try结束的时候通过回调的方式自动调用外部资源类的close()方法,避免书写冗长的finally代码块。(java TWR是怎么优雅我们的代码的?) 此外,上面的代码用一个静态内部类实现线程的功能,使用多线程可以避免一个用户I/O操作所产生的中断影响其他用户对服务器的访问,简单的说就是一个用户的输入操作不会造成其他用户的阻塞。当然,上面的代码使用线程池可以获得更好的性能,因为频繁的创建和销毁线程所造成的开销也是不可忽视的。
下面是一段回显客户端测试代码:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class EchoClient {
public static void main(String[] args) throws Exception {
Socket client = new Socket("localhost", 6789);
Scanner sc = new Scanner(System.in);
System.out.print("请输入内容: ");
String msg = sc.nextLine();
sc.close();
PrintWriter pw = new PrintWriter(client.getOutputStream());
pw.println(msg);
pw.flush();
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
System.out.println(br.readLine());
client.close();
}
}
如果希望用NIO的多路复用套接字实现服务器,代码如下所示。NIO的操作虽然带来了更好的性能,但是有些操作是比较底层的,对于初学者来说还是有些难于理解。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class EchoServerNIO {
private static final int ECHO_SERVER_PORT = 6789;
private static final int ECHO_SERVER_TIMEOUT = 5000;
private static final int BUFFER_SIZE = 1024;
private static ServerSocketChannel serverChannel = null;
private static Selector selector = null; // 多路复用选择器
private static ByteBuffer buffer = null; // 缓冲区
public static void main(String[] args) {
init();
listen();
}
private static void init() {
try {
serverChannel = ServerSocketChannel.open();
buffer = ByteBuffer.allocate(BUFFER_SIZE);
serverChannel.socket().bind(new InetSocketAddress(ECHO_SERVER_PORT));
serverChannel.configureBlocking(false);
selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void listen() {
while (true) {
try {
if (selector.select(ECHO_SERVER_TIMEOUT) != 0) {
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
handleKey(key);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static void handleKey(SelectionKey key) throws IOException {
SocketChannel channel = null;
try {
if (key.isAcceptable()) {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
channel = serverChannel.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
channel = (SocketChannel) key.channel();
buffer.clear();
if (channel.read(buffer) > 0) {
buffer.flip();
CharBuffer charBuffer = CharsetHelper.decode(buffer);
String msg = charBuffer.toString();
System.out.println("收到" + channel.getRemoteAddress() + "的消息:" + msg);
channel.write(CharsetHelper.encode(CharBuffer.wrap(msg)));
} else {
channel.close();
}
}
} catch (Exception e) {
e.printStackTrace();
if (channel != null) {
channel.close();
}
}
}
}
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
public final class CharsetHelper {
private static final String UTF_8 = "UTF-8";
private static CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder();
private static CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();
private CharsetHelper() {
}
public static ByteBuffer encode(CharBuffer in) throws CharacterCodingException{
return encoder.encode(in);
}
public static CharBuffer decode(ByteBuffer in) throws CharacterCodingException{
return decoder.decode(in);
}
}
15、XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式?
XML文档定义方式:有两种定义形式,dtd文档类型定义和schema模式
本质区别:schema本身是xml的,可以被XML解析器解析(这也是从DTD上发展schema的根本目的)
普通区别:
-
schema 是内容开放模型,可扩展,功能性强,而DTD可扩展性差。
-
shema 支持丰富的数据类型,而 DTD不支持元素的数据类型,对属性的类型定义也很有限。
-
schema 支持命名空间机制,而DTD不支持。
-
schema 可针对不同情况对整个XML 文档或文档局部进行验证;而 DTD缺乏这种灵活性。
-
schema 完全遵循XML规范,符合XML语法,可以和DOM结合使用,功能强大;而DTD 语法本身有自身的语法和要求,难以学习。
解析XML文档方式:
-
DOM解析: DOM的全称是Document Object Model,也即文档对象模型。在应用程序中,基于DOM的XML分析器将一个XML文档转换成一个对象模型的集合(通常称DOM树),应用程序正是通过对这个对象模型的操作,来实现对XML文档数据的操作。通过DOM接口,应用程序可以在任何时候访问XML文档中的任何一部分数据,因此,这种利用DOM接口的机制也被称作随机访问机制。
-
SAX解析:SAX的全称是Simple APIs for XML,也即XML简单应用程序接口。与DOM不同,SAX提供的访问模式是一种顺序模式,这是一种快速读写XML数据的方式。当使用SAX分析器对XML文档进行分析时,会触发一系列事件,并激活相应的事件处理函数,应用程序通过这些事件处理函数实现对XML文档的访问,因而SAX接口也被称作事件驱动接口。
-
JDOM解析:JDOM采用了Java中的Collection架构来封装集合,是Java爱好者更加熟悉的模式
-
DOM4J解析:xml解析器一次性把整个xml文档加载进内存,然后在内存中构建一颗Document的对象树,通过Document对象,得到树上的节点对象,通过节点对象访问(操作)到xml文档的内容
16、你在项目中哪些地方用到了XML?
XML的主要作用:数据交换
和信息配置
。
在做数据交换时,XML将数据用标签组装起来,然后压缩打包加密后通过网络传送给接收者,接收者解密与解压缩后再从XML文件中还原相关信息进行处理,XML曾经是异构系统间交换数据的事实标准,但此项功能几乎已经被JSON(JavaScript Object Notation)取而代之。
当然,目前很多软件仍然使用XML来存储配置信息,我们在很多项目中通常也会将作为配置信息的硬代码写在XML文件中,Java的很多框架也是这么做的,而且这些框架都选择了dom4j作为处理XML的工具,因为Sun公司的官方API实在不怎么好用。
补充:现在有很多时髦的软件(如Sublime)已经开始将配置文件书写成JSON格式,我们已经强烈地感受到XML的另一项功能也将逐渐被业界抛弃。