Java基础知识(七) 输入输出流

上一篇:Java基础知识(六) 异常处理

输入输出流

1. Java IO流的实现机制是什么

在Java 语言中,输人和输出都被称为抽象的流,流可以被看作一组有序的字节集合,即
数据在两设备之间的传输。

流的本质是数据传输,根据处理数据类型的不同,流可以分为两大类:字节流和字符流。
字节流以字节(8bit)为单位,包含两个抽象类:InputStream(输入流)和 OutputStream(输
出流)。字符流以字符(16 bit)为单位,根据码表映射字符,一次可以读多个字节,它包含两
个抽象类:Reader(输入流)和 Writer(输出流)。字节流和字符流最主要的区别为:字节流
在处理输入输出时不会用到缓存,而字符流用到了缓存,每个抽象类都有很多具体的实现类。

Java IO类在设计时采用了Decorator(装饰者)设计模式。Decorator设计模式在InputStream的使用,如下类图:

在这里插入图片描述
其中,ByteArraylnputSitream、StringBufferlnputStream、FilelnputStream 和 PipedInputStream 是
Java 提供的最基本的对流进行处理的类,FilteInputStream 为一个封装类的基类,可以对基本
的 IO 类进行封装,通过调用这些类提供的基本的流操作方法来实现更复杂的流操作。
使用这种设计模式的好处是可以在运行时动态地给对象添加一些额外的职责,与使用继承
的设计方法相比,该方法具有很好的灵活性。

假如现在要设计一个输入流的类,该类的作用为在读文件时把文件中的小写字母转换为大写字母。在设计时,可以通过继承抽象装饰者奖(FilterInputStream)来实现一个装饰类,通过调用 InputStream 类或其子类提供的一些方法再加上逻辑判断
代码从而可以很简单地实现这个功能,示例如下:

import java.io.*;

/**
 * 描述:InputStream测试,把小写换成大写
 *
 * @author Ye
 * @version 1.0
 * @date 2021/8/10 10:47
 */
class MyOwnInputStream extends FilterInputStream{
    public MyOwnInputStream(InputStream in) {
        super(in);
    }
    public int read() throws IOException{
        int c = 0;
        if ((c = super.read()) != -1){
            //把小写换成大写
            if (Character.isLowerCase((char)c)){
                return Character.toUpperCase((char)c);
            }else {
                return c;
            }
        }else {
            return -1;
        }
    }
}
public class TestFilterInputStream{
    public static void main(String[] args) {
        int c;
        try{
            InputStream is = new MyOwnInputStream(new BufferedInputStream(new FileInputStream("E:\\javaproject\\demo\\src\\main\\resources\\test.txt")));
            while ((c = is.read()) >= 0){
                System.out.print((char)c);
            }
            is.close();
        }catch (IOException e){
            System.out.println(e.getMessage());
        }
    }
}

test.txt 文件:
在这里插入图片描述
运行结果:
在这里插入图片描述

2. 管理文件和目录的类是什么

对文件或目录进行管理与操作在编程中有着非常重要的作用,Java 提供了一个非常重要的类(File)来管理文件和文件夹,通过类不仅能够查看文件或目录的属性,而且还可以实现对文件或目录的创建、删除与重命名等操作。下面主要介绍 File 类中常用的几个方法:

方法作用
File(String pathname)根据指定的路径创建一个 File 对象
createNewFile()若目录或文件存在,则返回 false,否则创建文件或文件夹
delete()删除文件或文件夹
isFile()判断这个对象表示的是否是文件
isDirectory()判断这个对象表示的是否是文件夹
listFiles()若对象代表目录,则返回目录中所有文件的 File 对象
mkdir()根据当前对象指定的路径创建目录
exists()判断对象对应的文件是否存在

下面一个代码,列出文件夹下的所有目录和文件:

import java.io.File;

/**
 * 描述:列出某个文件夹的所有目录和文件
 *
 * @author Ye
 * @version 1.0
 * @date 2021/8/10 13:07
 */
public class TestDir {

    public static void main(String[] args) {
        File file = new File("F:\\Chrome下载\\spring-cloud-alibaba-master");
        //判断目录是否存在
        if (!file.exists()){
            System.out.println("Directory is empty");
            return;
        }
        File[] files = file.listFiles();
        for (int i = 0;i< files.length;i++){
            //判断是否为目录
            if (files[i].isDirectory()){
                System.out.println("Directory is: " + files[i].getName());
            }else{
                System.out.println("File is: " + files[i].getName());
            }
        }
    }
}

该文件夹:
在这里插入图片描述

运行截图:
在这里插入图片描述
也可以递归列出子目录的所有文件和文件夹

import java.io.File;

/**
 * 描述:列出某个文件夹的所有目录和文件
 *
 * @author Ye
 * @version 1.0
 * @date 2021/8/10 13:07
 */
public class TestDir {

    public static void getFile(File file){
        File[] files = file.listFiles();
        for (int i=0;i<files.length;i++){
            if (files[i].isDirectory()){
               getFile(files[i]);
            }else {
                System.out.println("File is: " + files[i].getName());
            }
        }

    }

    public static void main(String[] args) {
        File file = new File("F:\\Chrome下载\\spring-cloud-alibaba-master");
        //判断目录是否存在
        if (!file.exists()){
            System.out.println("Directory is empty");
            return;
        }
        File[] files = file.listFiles();
        for (int i = 0;i< files.length;i++){
            //判断是否为目录
            if (files[i].isDirectory()){
                System.out.println("Directory is: " + files[i].getName());
                getFile(files[i]);
            }else{
                System.out.println("File is: " + files[i].getName());
            }
        }
    }
}

运行截图:
(太多了,只给出部分的截图)
在这里插入图片描述

3. Java Socket是什么

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个双向链路的一端称为一
个Socket。Socket也称为套接字,可以用来实现不同虚拟机或不同计算机之间的通信。在 Java
语言中。Socket 可以分为两种类型:面向连接的 Socket 通信协议(TCP,Tranemission Control
Protocol,传输控制协议)和面向无连接的 Socket 通信协议(UDP,User Datagram Protocol,用
户数据报协议)。任何一个 Socket 都是由 IP 地址和端口号唯一确定的。

基于TCP 的通信过程如下:首先,Server(服务器)端 Listen(监听)指定的某个端口(建议使用大于 1024的端口,是否有连接请求;其次,Client(客户)端向Server 端发出 Connect(连接)请求;最后,Server 端向Client 端发回 Accept(接受)消息。一个连接就建立起来了,会话随即产生。Server 端和 Client 端都可以通过 Send 、Write 等方法与对方通信。

Socket 的生命周期可以分为3个阶段:打开 Socket、使用 Socket收发数据和关闭 Socket。在 Java 语言中,可以使用SeverSocket来作为服务器端,Socket 作为客户端来实现网络通信。

下面是一个例子:
客户端:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * 描述:socket客户端
 *
 * @author Ye
 * @version 1.0
 * @date 2021/8/10 14:52
 */
public class SocketClient {
    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.println("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();
                pw.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

服务端:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 描述:socket服务
 *
 * @author Ye
 * @version 1.0
 * @date 2021/8/10 14:34
 */
public class SocketServer {
    public static void main(String[] args) {
        BufferedReader br = null;
        PrintWriter pw = null;
        try{
            ServerSocket server = new ServerSocket(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();
                pw.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

客户端运行截图:
在这里插入图片描述

4. Java NIO是什么

在非阻塞 IO(Nonblocking IO,NIO)出现之前,Java 是通过传统的 Socket来实现基本的网络通信功能的。

如果客户端还没有对服务器端发起连接请求,那么 accept就会阻塞(阻塞指的是暂停一个线程的执行以等待某个条件发生,例如某资源就绪)。如果连接成功,当数据还没有准备好时,对 read 的调用同样会阻塞。当要处理多个连接时,就需要采用多线程的方式,由于每个线程都拥有自己的栈空间,而且由于阻塞会导致大量线程进行上下文切换,使得程序的运行效率非常低下,因此在 J2SE 1.4 中引入了 NIO
来解决这个问题。

NIO 非阻塞的实现主要采用了 Reactor(反应器)设计模式,这个设计模式与 Observer
(观察者)设计模式类似,只不过 Observer 设计模式只能处理一个事件源,而 Reactor 设计模
式可以用来处理多个事件源。

5. 什么是Java序列化

Java提供了两种对象持久化的方式,分别为序列化和外部序列化。
(1)序列化(Serialization)
在分布式环境下,当进行远程通信时,无论是何种类型的数据,都会以二进制序列的形式
在网络上传送。序列化是一种将对象以一连串的字节描述的过程,用于解决在对对象流进行读
写操作时所引发的问题。序列化可以将对象的状态写在流里进行网络传输,或者保存到文件、
数据库等系统里,并在需要时把该流读取出来重新构造一个相同的对象。

如何实现序列化呢?其实,所有要实现序列化的类都必须实现 Serializable 接口,Serializable 接口位于 java.lang 包中,它里面没有包含任何方法。使用一个输出流(例如 FileOuputStream)来构造一个 ObjectOuputStream(对象流)对象,紧接着,使用该对象的 writeObject(Object obj)方法就可以将 obj 对象写出(即保存其状态),要恢复时可以使用其对应的输入流。

序列化有以下两个特点:

  1. 如果一个类能被序列化,那么它的子类也能够被序列化。
  2. 由于 slatic(静态)代表类的成员,transient (Java 语言关键字,如果用 transient 声明
    一个实例变量,当对象存储时,它的值不需要维持。)代表对象的临时数据,因此被声明为这
    两种类型的数据成员是不能够被序列化的。

由于序列化的使用会影响系统性能,因此如果不是必须要使用序列化,应尽可能不要使用序列化。在如下情况建议使用序列化:

  1. 需要通过网络来发送对象,或对象的状态需要被持久化到数据库或文件中。
  2. 序列化能实现深复制,即可以复制引用的对象。

(2)外部序列化
Java语言还提供了另外一种方式来实现对象持久化,即外部序列化。其接口如下:

import java.io.ObjectOutput;
import java.io.ObjectInput;
public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

外部序列化与序列化主要的区别在于序列化是内置的 API,只需要实现 Serializable 接口,
开发人员不需要编写任何代码就可以实现对象的序列化,而使用外部序列化时,Extemalizable
接口中的读写方法必须由开发人员来实现。因此与实现 Serializable 接口的方法相比,使用 Extermalizable 编写程序的难度更大,但是由于把控制权交给了开发人员,在编程时有更多的灵活
性,对需要持久化的那些属性可以进行控制,可能会提高性能。

6. System.out.println()方法使用时注意的问题

Java 中的 System.out.printn()方法提供了一种非常有效简单的方法来实现控制台的输出,
该方法默认接收一个字符串类型的变量作为参数。当然,在使用时可以传递任意能够转换为
Sring 类型的变量作为参数(例如基本类型imt,或者一个实现 toString 方法的自定义类等)

参考:《Java程序员面试笔试宝典》 何昊、薛鹏、叶向阳 编著

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值