Java IO和网络(下

Author Avatar
EmptinessBoy 6月 01, 2020
  • 在其它设备中阅读本文章

过滤器流

通常情况下,为了提高数据的处理速度,或是能对诸如 int、double 之类的数据直接进行操作,会联合使用被称之为过滤器(Filter)类的流,以提高流的处理效率。

过滤器流的不能单独使用,必须与相应的节点流一起使用,才能实现数据流的读写功能。

上一篇文章中和大家提到了 BufferedInuputStream 类和 BufferedOutputStream 就是两个常用的带缓冲的过滤器流

数据输入输出流

有时需要处理的数据不一定是字节数据。如读写 int型、float型、double型 的数据时,一次需要读写几个字节,需要专门的数据输入输出流来处理。

DateInuputStream类 和 DateOutputStream类 能够直接读写 Java 基本类型的数据 和 Unicode 编码格式的字符串。

DataInputStream 类的读方法DataOutputStream 类的写方法
int read(byte[] b)void write(byte[] b, int off, int len)
int read(byte[] b, int off, int len)void write(int b)
boolean readBoolean()void writeBoolean(boolean v)
byte readByte()void writeByte(int v)
char readChar()void writeBytes(String s)
double readDouble()void writeChar(int v)
float readFloat()void writeChars(String s)
void readFully(byte[] b)void writeDouble(double v)
void readFully(byte[] b, int off, int len)void writeFloat(float v)
int readInt()void writeInt(int v)
long readLong()void writeLong(long v)
short readShort()void writeShort(int v)
String readUTF()void writeUTF(String str)

写入 DEMO:

import java.io.*;

public class DataInputOutput {
    public static void main(String[] args) {
        File f = new File("src/test-a.txt");
        try {
            OutputStream op = new FileOutputStream(f);
            DataOutputStream dp = new DataOutputStream(op);
            int a = 666;
            String b = "emptinessboy";
            boolean c = true;
            dp.writeInt(a);
            dp.writeUTF(b);
            dp.writeBoolean(c);
            dp.close();
            op.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

运行后,程序输出了 test-a.txt。因为是字节流,所以显示乱码。

dataopstream.png

读取 DEMO:

import java.io.*;

public class DataInPutTest {
    public static void main(String[] args) {
        File f = new File("src/test-a.txt");
        try {
            InputStream in = new FileInputStream(f);
            BufferedInputStream bi = new BufferedInputStream(in);
            DataInputStream di = new DataInputStream(bi);
            boolean flag = true;  //避免系统认为while下面的代码不可达
            while (flag){
                System.out.println(di.readInt());
                System.out.println(di.readUTF());
                System.out.println(di.readBoolean());
            }
            di.close(); //关闭流
            bi.close();
            in.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("文件结束");
            //e.printStackTrace();
        }

    }
}

运行效果(可以看到所有的文件信息【基本数据类型】都被输出出来了):

dataipstream.png

对象I/O

如果要对除了基本数据类型以外的内容进行 IO 操作,JAVA也是支持的。

Java 提供了一个 Serializable 的接口,只要类实现了该接口,就可以使用 ObjectInputStream类ObjectOutputStream类 对该类的对象进行 I/O 处理了。

DEMO

这是一个简单的圆类:

package 学校课程527实验;
import java.io.*;
public class circleClass {
    double x1,y1,x2,y2;

    public circleClass(double x1,double y1,double x2,double y2) {
        this.x1=x1;
        this.x2=x2;
        this.y1=y1;
        this.x2=y2;
    }

    public double getArea(){
        double r = Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2));
        return Math.PI*r*r;
    }
}

我们给这个类实现 Serializable 接口:

public class circleClass implements Serializable { …… }

创建对象并写入对象到文件:

class TestCircle{
    public static void main(String[] args) {
        //初始化两个对象
        circleClass a = new circleClass(0,0,8.5,9);
        System.out.printf("圆1的面积为:%.2f\n",a.getArea());
        circleClass b = new circleClass(2,3.5,9,6);
        System.out.printf("圆2的面积为:%.2f\n",b.getArea());

        //将对象写入文件
        File f = new File("src/test-c.dat");
        try {
            FileOutputStream fo = new FileOutputStream(f);
            ObjectOutputStream op = new ObjectOutputStream(fo);
            op.writeObject(a);
            op.writeObject(b);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

再写一个类来读取刚才写入的对象:

import java.io.*;
import 学校课程527实验.*;

public class ReadCircle {
    public static void main(String[] args) {
        File f = new File("src/test-c.dat");
        try {
            FileInputStream fi = new FileInputStream(f);
            ObjectInputStream oi = new ObjectInputStream(fi);
            circleClass a = (circleClass) oi.readObject();
            circleClass b = (circleClass) oi.readObject();
            System.out.printf("圆1的面积为:%.2f\n",a.getArea());
            System.out.printf("圆2的面积为:%.2f\n",b.getArea());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

先运行 TestCircle 来创建对象并写入文件:

RunObjectOutPutBuffer.png

可以看到输出的 test-c.dat 里面已经存入了对象(字节形式存储,因此 IDE 打开乱码)。接下来我们使用下面这段程序读取刚才的对象。

import java.io.*;
import 学校课程527实验.*;

public class ReadCircle {
    public static void main(String[] args) {
        File f = new File("src/test-c.dat");
        try {
            FileInputStream fi = new FileInputStream(f);
            ObjectInputStream oi = new ObjectInputStream(fi);
            //读取两个对象,并转化为circleClass
            //因为.readObject()方法默认返回Object所以要转换
            circleClass a = (circleClass) oi.readObject();
            circleClass b = (circleClass) oi.readObject();
            System.out.printf("圆1的面积为:%.2f\n",a.getArea());
            System.out.printf("圆2的面积为:%.2f\n",b.getArea());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

RunObjectInPutBuffer.png

可以看到,对象已经成功被读取,对象的方法也都可以正常操作!

随机文件操作

RandomAccessFile 类直接继承自 Object 类,可以从文件的任何位置开始进行读或者写操作。

RandomAccessFile 类同时实现了 DataInput 接口和 DataOutput 接口,可以对常用数据类型直接操作。

构造方法

RandomAccessFile(String name, String mode)

其中 mode 是访问模式。

四种模式

四种模式:R RW RWD RWS

模式含义
r以只读的方式打开文本,也就意味着不能用write来操作文件
rw读操作和写操作都是允许的
rws每当进行写操作,同步的刷新到磁盘,刷新内容和元数据
rwd每当进行写操作,同步的刷新到磁盘,刷新内容

方法

randomAccessFile.png

1、seek: 指定文件的光标位置,通俗点说就是指定你的光标位置然后下次读文件数据的时候从该位置读取。

2、getFilePointer: 我们注意到这是一个 long 类型的返回值,字面意思就是返回当前的文件光标位置。这样方便我们后面读取插入。

=====================

3、length: 文件的长度,返回long类型。注意它并不会受光标的影响。只会反应客观的文本长度。

=====================

4、read()、read(byte[] b)、read(byte[] b,int off,int len) : 这些方法跟 readstream 中的方法一样,例如最后一个:定义缓冲数组,从数组的 off 偏移量位置开始写,读取转换为数组数据达到 len 个字节。总之这是一个读文件内容的标准操作 api。

5、readDouble() readFloat() readBoolean() readInt() readLong() readShort() readByte() readChar(): 这些方法都是去read每一个字符。比如readLong就是要求你的文本内容必须有八个字符,不然会报错。

伴随着也就是 writeDouble() writeFloat() writeBoolean() writeInt() writeLong() writeShort() writeByte() writeChar()

6、readFully(byte[] b): 这个方法的作用就是将文本中的内容填满这个缓冲区b。如果缓冲b不能被填满,那么读取流的过程将被阻塞,如果发现是流的结尾,那么会抛出异常。这个过程就比较像“凑齐一车人在发车,不然不走”。

=====================

7、getChannel: 它返回的就是 nio 通信中的 file 的唯一 channel

=====================

8、skipBytes(int n): 跳过n字节的位置,相对于当前的 point。

DEMO

追加日志:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class RandomAccessFiletest1 {
    public static void main(String[] args) {
        File f = new File("src/test.log");
        try {
            RandomAccessFile rf = new RandomAccessFile(f, "rws");
            //获取文件长度并定位到文件结尾
            rf.seek(rf.length());
            //输入十个日期信息
            for (int i=0;i<10;i++) {
                rf.writeBytes(new java.util.Date() + "\r\n");
            }
            rf.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

连续两次运行程序后,可以看到,第二次的时候,程序在第一次书写的末尾继续书写日志文件。

RandomAccessFile.png

网络 IO

套接字(Socket)是Java支持网络通信的一种对象,程序间在网络上进行通信和程序对文件的操作是十分类似的。

SeverSock 是用来监听,等待建立连接的。一旦建立连接,就会生成一个 Socket 对象进行实际的数据传输

JAVA socket模型

Java-socket.png

简单服务器客户机 DEMO

这段程序用来实现从服务器不断发文字到客户机,然后客户端读取并显示文字:

服务器端程序:

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class SimpleServer {
    public static void main(String[] args) {
        try {
            //先使用ServerSocket监听端口
            ServerSocket ss = new ServerSocket(6666);
            //接受连接并获得Socket
            Socket s = ss.accept();
            OutputStream op = s.getOutputStream();
            DataOutputStream dp = new DataOutputStream(op);
            System.out.println("连接已建立,请输入发给客户端的消息(输入 quit 退出):");
            //基本输入输出流
            Scanner sc = new Scanner(System.in);
            String str;
            while (!(str = sc.nextLine()).equals("quit")){
                dp.writeUTF(str);
            }
            //关闭流
            sc.close();
            dp.close();
            op.close();
            s.close();
            ss.close();
            System.out.println("连接已经关闭");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端程序:

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;

public class SimpleClient {
    public static void main(String[] args) {
        try {
            //建立一个连接到服务器的socket(IP地址+端口)
            Socket s = new Socket("127.0.0.1", 6666);
            InputStream is = s.getInputStream();
            DataInputStream di = new DataInputStream(is);
            System.out.println("连接已建立,正在接收服务端的消息:");
            boolean flag = true;
            while (flag)
                System.out.println(di.readUTF());
            //关闭流
            di.close();
            is.close();
            s.close();
            System.out.println("连接已经关闭");
        } catch (IOException e) {
            System.out.println("连接已经中断");
            //e.printStackTrace();
        }
    }
}

运行效果(服务器端):

SimpleServerdemo.png

运行效果(客户端):

SimpleClientdemo.png

简单文件传输 DEMO

将客户机某文件传到服务器

服务器端程序:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class FileUploadServer {
    public static void main(String[] args) {
        ServerSocket ss = null;
        Socket s;
        int temp = 0;  //记录文件长度
        byte[] b = new byte[1024];  //字节流缓冲区
        InputStream fi; //输入流
        BufferedInputStream bi; //输入缓冲流
        FileOutputStream fo;  //文件输出流
        BufferedOutputStream bo;  //输出缓冲流
        try {
            ss = new ServerSocket(8888);  //尝试监听端口
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (true){   //服务端程序始终运行
            try {
                s = ss.accept();  //如有连接则建立连接并拿到 socket
                System.out.println("已经建立连接");
                fi = s.getInputStream();  //从socket拿到字节流
                bi = new BufferedInputStream(fi);  //输入缓冲流初始化
                File file;  //创建文件对象
                fo = new FileOutputStream("src/file.txt");
                bo = new BufferedOutputStream(fo);  //输出缓冲流初始化
                try {
                    //如果有连接且文件没有读取完毕则进行循环
                    while (s.isConnected()&&(temp = bi.read(b))!=-1){
                        //输出缓冲流写入
                        bo.write(b,0,temp);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                //按顺序关闭流
                bo.close();
                fo.close();
                bi.close();
                fi.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客户端程序:

import java.io.*;
import java.net.Socket;

public class FileUploadClient {
    public static void main(String[] args) {
        //创建文件对象
        File f = new File("src/test-d.txt");
        try {
            //尝试建立连接
            Socket s = new Socket("127.0.0.1", 8888);
            OutputStream so = s.getOutputStream();  //从socket拿到输出字节流
            BufferedOutputStream bo = new BufferedOutputStream(so);  //输出缓冲流初始化
            FileInputStream fin = new FileInputStream(f);  //文件输入流初始化
            BufferedInputStream bi = new BufferedInputStream(fin);  //输入缓冲流初始化
            byte[] b = new byte[1024];  //字节流缓冲区
            int temp;  //记录当前读取的长度
            while ((temp = bi.read(b))!= -1){
                //当文件还没读完,就执行循环
                bo.write(b,0,temp); //写入输出缓冲流
            }
            //按序关闭流
            bo.close();
            so.close();
            bi.close();
            fin.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

做完这一切,在 src 目录下创建 test-d.txt,然后启动服务端程序。最后运行客户端程序。

运行效果:

fileuploadsuccess.png

可以看到,服务端成功接收了文件并保存为 src/file.txt。😁😉

尾图4

This blog is under a CC BY-NC-ND 4.0 Unported License
本文链接:https://coding.emptinessboy.com/2020/06/Java-IO%E5%92%8C%E7%BD%91%E7%BB%9C%EF%BC%88%E4%B8%8B/