Java基础知识——7.输入输出流
文章目录
1. Java IO流的实现机制是什么
输入和输出都称为抽象的流。流的本质是数据传输,根据处理数据类型的不同,流分为两大类:字节流和字符流。字节流以字节(8bit)为单位,包含两个抽象类:InputStream(输入流)和OutputStream(输出流)。字符流以字符(16bit)为单位,根据码表映射字符,一次可以读多个字节,它包含两个抽象类:Reader(输入流)和Writer(输出流)。字节流和字符流最主要的区别为:字节流在处理输入输出时不会用到缓存,而字符流用到了缓存。流的主要作用是为了改善程序性能并且使用方便。
2. 管理文件和目录的类是什么
File类常用的方法
方法 | 作用 |
---|---|
File(String pathname) | 根据指定的路径创建一个File对象 |
createNewFile() | 若目录或文件存在,则返回false,否则创建文件或文件夹 |
delete() | 删除文件或文件夹 |
isFile() | 判断这个对象表示的是否是文件 |
isDirectory | 判断这个对象表示的是否是文件夹 |
listFiles() | 若对象是目录,则返回目录中所有文件的File对象 |
mkdir() | 根据当前对象指定的路径创建目录 |
exists() | 判断对象对应的文件是否存在 |
如何列出某个目录下的所有目录和文件?
假设目录"D:\testDir"下有两个文件夹(dir1和dir2)和一个文件demo.txt,实现代码如下:
import java.io.File;
public class demo{
public static void main(String[] args){
File file = new File("D:\\testDir");
//判断目录是否存在
if (! file.exists()){
System.out.println("directory is empty");
return ;
}
File[] fileList = file.listFiles();
for (int i=0; i<fileList.length;i++){
//判断是否为目录
if (fileList[i].isDirectory()){
System.out.println("directory is:" +fileList[i].getName());
}else {
System.out.println("file is:" +fileList[i].getName());
}
}
}
}
程序运行结果:
directory is: dir1
directory is: dir2
file is: demo.txt
3. Java Socket是什么
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket也称为套接字,可以用来实现不同虚拟机或不同计算机之间的通信。Socket可以分为两种类型:面向连接的Socket通信协议(TCP,Transmission Control Protocol,传输控制协议)和面向无连接的Socket通信协议(UDP,User Datagram Protocol,用户数据报协议)。任何一个Socket都是由IP地址和端口号唯一确定的。
基于TCP的通信过程如下:首先,Server(服务器)端Listen(监听)指定的某个端口(建议使用大于1024的端口)是否有连接请求;其次Client(客户端)向Server端发出Connect(连接)请求;最后,Server端向Client端发回Accept(接收)消息。一个连接就建立起来了,会话随即产生。Server端和Client端都可以通过Send、Write等方式与对方通信。
Socket的生命周期可以分为三个阶段:打开Socket、使用Socket收发数据和关闭Socket。可以使用ServerSocket来作为服务器端,Socket作为客户端来实现网络通信。
用Socket实现客户端和服务端的通信,要求客户发送数据后能够回显相同的数据。
首先,创建一个Server.java的服务器代码
import java.net.*;
import java.io.*;
class Server{
public static void main(String[] args){
BufferedReader br = null;
PrintWriter pw =null;
try{
ServerSocket server = new Socket(2000);
Socket socket = server.accept();
//获取输入流
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//获取输出流
pw = new PrintWriter(socket.getOutputStream(), true);
String s = br.readLine(); //获取接收的数据
pw.println(s); //发送相同的数据给客户端
}catch(Exception e){
e.printStackTrace();
}finally{
try{
br.close();
pe.close();
}catch(Exception e)
}
}
}
然后,创建一个Client.java的客户端程序
import java.net.*;
import java.io.*;
class Client{
public static void main(String[] args){
BufferedReader br = null;
PrintWriter pw =null;
try{
Socket socket = new Socket("localhost", 2000);
//获取输入流与输出流
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
pw = new PrintWriter(socket.getOutputStream(), true);
//向服务器发送数据
pw.print("Hello");
String s =null;
while(true){
s = br.readLine();
if (s != null)
break;
}
System.out.println(s);
}catch(Exception e){
e.printStackTrace();
}finally{
try{
br.close();
pe.close();
}catch(Exception e)
}
}
}
最后启动服务器端程序,然后运行客户端程序,客户端将会把从服务器端转发过来的"Hello"打印出来
4. Java NIO是什么
在非阻塞IO(Nonblocking IO,NIO)出现之前,Java是通过传统的Socket来实现基本的网络通信功能的。以服务器端为例,其实现基本流程为:
如果客户端还没有对服务器端发起连接请求,那么accept就会阻塞(阻塞指的是暂停一个线程的执行以等待某个条件发生,例如某资源就绪)。如果连接成功,当数据还没有准备好时,对read的调用同样会阻塞。当要处理多个连接时,就需要采用多线程的方式,由于每个线程都拥有自己的栈空间,而且由于阻塞会导致大量线程进行上下文切换,使得程序的运行效率非常低下,因此在 J2SE 1.4 中引入了NIO来解决这个问题。
NIO通过Selector、Channel和Buffer来实现非阻塞的IO操作,其实现原理如下:
NIO非阻塞的实现主要采用了Reactor(反应器)设计模式,这个设计模式与Observer(观察者 )设计模式类似,只不过Observer设计模式只能处理一个事件源,而Reactor设计模式可以用来处理多个事件源。
在上图中,Channel可以被看作是一个双向的非阻塞的通道,在通道的两边都可以进行数据的读写操作。Selector实现了用一个线程来管理多个通道(采用了复用与解复用的方式使得一个线程能够管理多个通道,即可以把多个流合并成为一个流,或者把一个流分成多个流的方式),它类似于一个观察者。在实现时,把需要处理的Channel的IO事件(例如connect、read或write等)注册给Selector。Selector内部的实现原理为:对所有注册的Channel进行轮询访问,一旦轮询到一个Channel 1 有注册的事件发生,例如有数据来了,它就通过传回SelectionKey的方式通知开发人员对Channel 1 进行数据的读或写操作 。Key(由SelectionKey类表示)封装一个特定的Channel 1 和一个特定的Selector之间的关系。这种通过轮询的方式在处理多线程请求时不需要上下文的切换,而采用多线程的实现方式在线程之间切换时需要上下文的切换,同时也需要进行压栈和弹栈操作。因此,NIO有较高的执行效率。
Buffer用来保存数据,可以用来存放从Channel 1 读取的数据,也可以存放使用Channel 1 进行发送的数据。Java提供了多种不同类型的Buffer,例如ByteBuffer、CharBuffer等,通过Buffer,大大简化了开发人员对流数据的管理。
NIO在网络编程中有着非常重要的作用,与传统的Socket方式相比,由于NIO采用了非阻塞的方式,在处理大量并发请求时,使用NIO要比使用Socket效率高出很多。
5. 什么是Java序列化
Java提供了两种对象持久化的方式,分别为序列化和外部序列化。
(1)序列化(Serialization)
在分布式环境下,当进行远程通信时,无论是何种类型的数据,都会以二进制序列的形式在网络上传送。序列化是一种将对象以一连串的字节描述的过程,用于解决在对对象流进行读写操作时所引发的问题。序列化可以将对象的状态写在流里进行网络传输,或者保存到文件、数据库等系统里,并在需要时把该流读取出来重新构造一个相同的对象。
所有实现序列化的类都必须实现Serializable接口,Serializable接口位于java.lang包中,它里面没有包括任何方法。使用一个输出流(例如FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,紧接着,使用该对象的writeObject(Object obj)方法就可以将obj对象写出(即保存其状态),要恢复时可以使用其对应的输入流。
序列化有以下两个特点:
① 如果一个类能被序列化,那么它的子类也能够被序列化。
② 由于static(静态)代表类的成员,transient(Java语言关键字,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持)代表对象的临时数据,因此被声明为这两种类型的数据成员是不能够被序列化的。
Java提供多个对象序列化的接口,包括ObjectOutput、ObjectInput、ObjectOutputStream和ObjectInputStream。
序列化会影响系统性能,什么情况下需要使用序列化?
① 需要通过网络来发送对象,或对象的状态需要被持久化到数据库或文件中
② 序列化能实现深复制,即可以复制引用的对象
与序列化相对的是反序列化,它将流转换为对象。在序列化与反序列化的过程中,serialVersionUID起着非常重要的作用,每个类都有一个特定的serialVersionUID,在反序列化的过程中,通过serialVersionUID来判定类的兼容性。如果待序列化的对象与目标对象的serialVersionUID不同,那么在反序列化时就会抛出InvalidClassException异常。作为一个好的编程习惯,最好在被序列化的类中显示地声明serialVersionUID(该字段必须定义为static final)。自定义serialVersionUID主要有如下三个优点:
① 提高程序运行效率。如果类中未显式声明serialVersionUID,那么在序列化时就会通过计算得到一个serialVersionUID值。通过显示声明serialVersionUID的方式省去了计算的过程,因此提高了程序的运行效率
② 提高程序在不同平台上的兼容性。由于各个平台的编译器在计算serialVersionUID时完全有可能会采用不同的计算方式,这就会导致在一个平台上序列化的对象在另一个平台上将无法实现反序列化。通过显式声明serialVersionUID的方法完全可以避免该问题的发生
③ 增强程序各个版本的可兼容性。在默认情况下,每个类都有唯一的serialVersionUID,因此,当后期对类进行修改时(例如加入新的属性),类的serialVersionUID值将会发生变化,这将会导致类在修改前对象序列化的文件在修改后将无法进行反序列化操作。同样,通过显式声明serialVersionUID也会解决这个问题
(2)外部序列化
Java语言还提供了另外一种方式来实现对象持久化,即外部序列化。其接口如下:
public interface Externalizable extends Serializable{
void readExternal(ObjcetInput in);?
void writeExternal(ObjectOutput out);?
}
外部序列化与序列化主要的区别在于序列化是内置的API,只需要实现Serializable接口,开发人员不需要编写任何代码就可以实现对象的序列化,而使用外部序列化时,Externalizable接口中的读写方法必须由开发人员来实现。因此与实现Serializable接口的方法相比,使用Externalizable编写程序的难度更大,但是由于把控制权交给了开发人员,在编程时有更多的灵活性,对需要持久化的那些属性可以进行控制,可能会提高性能。
6. System.out.println()方法使用需要注意哪些问题
默认接收一个字符串类型的变量作为参数,使用时可以传递任意能够转换为String类型的变量作为参数。
传入的参数是一个对象,就会调用这个对象的toString()方法,把返回的字符串打印出来。
上一篇: 谈个对象
下一篇: 爆笑熊孩子,冷出一脸瀑布汗