IO(输入输出)

IO流按照操作数据的不同,分为字节流和字符流,按照数据传输方向分为输入流和输出流。
字节流
计算机中,所有文件都是以二进制(字节)形式存在,IO流中针对字节的输入输出提供了一系列的流称为字节流。
在JDK中提供了两个抽象类:InputStream和OutputStream,它们是字节流的顶级父类。
InputStream的常用方法
方法声明
功能描述
int read()
从输入流读取一个8位的字节,把它转换为0~255之间的整数,并返回这一整数
int read(byte[] b)
从输入流读取若干字节,把它们保存到参数b指定的字节数组中,返回的整数表示读取字节数
int read(byte[] b,int off,int len)
从输入流读取若干字节,把它们保存到参数b指定的字节数组中,off指定字节数组开始保存数据的起始下标,len表示读取的字节数目
void close()
关闭此输入流并释放与该流关联的所有系统资源
OutputStream的常用方法
方法声明
功能描述
void write()
向输出流写入一个字节
void write(byte[] b)
把参数b指定的字节数组的所有字节写道输出流
void write(byte[] b,int off,int len)
将指定byte数组中从偏移量off开始的len个字节写入输出流
void flush()
刷新此输入流并强制写出所有缓冲的输出字节
void close()
关闭此输出流并释放与此流相关的所有系统资源
针对文件的读写,JDK专门提供了两个类,FileInputStream和FileOutputStream。
先在当前文件创建一个写有“itcast”的“t.txt”文件。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        //创建一个文件字节输入流
        FileInputStream in = new FileInputStream("t.txt");
        int b = 0;  
        while (true){
            b = in.read();  //变量b记住读取的一个字节
            if (b == -1){   //如果读取的字节为-1,跳出循环
                break;
            }
            System.out.println(b);
        }
        in.close();
    }
}
运行结果
105
116
99
97
115
116
如果指定的文件不存在,就会先创建文件,再写入数据;如果文件存在,则会先清空文件内容再进行写入。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        //创建一个文件字节输出流
        FileOutputStream out = new FileOutputStream("example.txt");
        String str = "好好学习";
        byte[] b = str.getBytes();
        for (int i=0;i<b.length;i++){
            out.write(b[i]);
        }
        out.close();
    }
}
运行结果是当前目录会生成一个内容为“好好学习”的example.txt文件。
若想在存在的文件内容后追加新内容,可使用构造函数
FileInputStream(String fileName,boolean append)
来创建文件输出流对象,把append参数设为true。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        //创建一个文件字节输出流
        FileOutputStream out = new FileOutputStream("example.txt",true);
        String str = "天天向上";
        byte[] b = str.getBytes();
        for (int i=0;i<b.length;i++){
            out.write(b[i]);
        }
        out.close();
    }
}
运行结果是在原文件内容中加入了“天天向上”。
IO流在进行数据读写操作时会出现异常,为了代码整洁,使用throws关键字将异常抛出。然而一旦遇到异常,IO流的close()方法无法得到执行,所以,为了close()方法能够执行,通常将关闭流的操作写在finally代码块中:
finally{
    try{
        if(in!=null)   //如果in不为空,关闭输入流
            in.close();
    }catch (Exception e){
        e.printStackTrace();
    }
}
finally{
    try{
        if(out!=null)   //如果out不为空,关闭输出流
            out.close();
    }catch (Exception e){
        e.printStackTrace();
    }
}
还可以进行文件的拷贝
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        //创建一个字节输入流,用于读取当前目录下source文件中的wav文件
        FileInputStream in = new FileInputStream("source/BGM.wav");
        //创建一个字节输出流,用于将读取的数据写入target目录下的文件中
        FileOutputStream out = new FileOutputStream("target/BGM.wav");
        int b;    //定义一个int类型的变量,记住每次读取的一个字节
        long begintime = System.currentTimeMillis();//获取拷贝前系统时间
        while((b=in.read())!=-1){ //读取一个字节并判断是否读到文件末尾
            out.write(b); //将读到的字节写入文件
        }
        long endtime = System.currentTimeMillis();//获取拷贝后系统时间
        System.out.println("拷贝文件所消耗时间:"+(endtime-begintime)+"毫秒");
        in.close();
        out.close();
    }
}
运行结果
拷贝文件所消耗时间:29202毫秒
注:定义文件路径在Windows中的目录符号是反斜线\,但反斜线在Java中是特殊字符,表转义符,所以用\\,也可以用正斜线/来表示。
字节流的缓冲区
由于一个字节的读取使得操作文件效率低下,所以可以定义一个字节数组作为缓冲区。(通俗的讲,就是由一个一个搬变成了一车一车拉)
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        //创建一个字节输入流,用于读取当前目录下source文件中的wav文件
        FileInputStream in = new FileInputStream("source\\BGM.wav");
        //创建一个字节输出流,用于将读取的数据写入target目录下的文件中
        FileOutputStream out = new FileOutputStream("target\\BGM.wav");
        byte[] buff = new byte[1204];   //定义一个字节数组作为缓冲区
        int len;    //定义一个int类型的变量,记住读入缓冲区的字节数
        long begintime = System.currentTimeMillis();//获取拷贝前系统时间
        while((len=in.read(buff))!=-1){ //读取一个字节并判断是否读到文件末尾
            out.write(buff,0,len); //从第一个字节开始,向文件写入len个字节
        }
        long endtime = System.currentTimeMillis();//获取拷贝后系统时间
        System.out.println("拷贝文件所消耗时间:"+(endtime-begintime)+"毫秒");
        in.close();
        out.close();
    }
}
运行结果
拷贝文件所消耗时间:34毫秒
可以看出消耗时间明显减少。程序中的缓冲区就是一块内存,用于存放暂时输入输出的数据,减少了对文件的操作次数,提高了读写数据的效率。
装饰设计模式
在程序设计中,可以通过“装饰”一个类增强它的功能,装饰设计模式就是通过包装一个类,动态的为它增加功能的一种设计模式。
class Car{
    private String carName;
    public Car(String carname){
        this.carName = carname;
    }
    public void show(){
        System.out.println("我是"+carName+",具有基本功能");
    }
}
class RadarCar{
    public Car myCar;
    public RadarCar(Car myCar){ //通过构造方法接收被包装的对象
        this.myCar = myCar;
    }
    public void show(){
        myCar.show();
        System.out.println("新增倒车雷达功能");
    }
}
public class Test{
    public static void main(String[] args) {
        Car car = new Car("benz");  //创建Car对象
        System.out.println("包装前-->");
        car.show();
        RadarCar radarCar = new RadarCar(car);  //创建RadarCar对象并传入Car的对象
        System.out.println("包装后-->");
        radarCar.show();
    }
}
运行结果
包装前-->
我是benz,具有基本功能
包装后-->
我是benz,具有基本功能
新增倒车雷达功能
字节缓冲流
在IO包中提供了两个带缓冲的字节流BufferedInputStream和BufferedOutputStream,这两个流都使用了装饰设计模式。它们的构造方法中分别接收InputStream和OutputStream类型的参数作为包装对象,在读写数据时提供缓冲功能。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        //创建一个带缓冲区的输入流
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source/BGM.wav"));
        //创建一个带缓冲区的输出流
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("target/BGM.wav"));
        int b;
        long begintime = System.currentTimeMillis();
        while ((b=bis.read())!=-1){
            bos.write(b);
        }
        long endtime = System.currentTimeMillis();
        System.out.println("拷贝文件所消耗时间:"+(endtime-begintime)+"毫秒");
        bis.close();
        bos.close();
    }
}
运行结果
拷贝文件所消耗时间:78毫秒
创建了BufferedInputStream和BufferedOutputStream两个缓冲流对象,两个流内部都定义了大小为8192的字节数组。
字符流
字符流也有两个抽象的顶级父类,分别是Reader和Writer。Reader是字符输入流,用于从某个原设备读取字符,Writer是字符输出流,用于向某个目标设备写入字符。
字符流操作文件有字符输入流FileReader和字符输出流FileWriter。
字符流的read()方法返回的是int类型的值,想获得字符就得强制类型转换。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        //创建一个FileReader对象用来读取文件中的字符
        FileReader reader = new FileReader("t.txt");
        int ch; //定义一个变量用于记录读取的字符
        while((ch=reader.read())!=-1){  //循环判断是否读取到文件的末尾
            System.out.println((char)ch);
        }
        reader.close();
    }
}
运行结果
i
t
c
a
s
t
下面是写入文件,如果指定的文件不存在,就会先创建文件,再写入数据;如果文件存在,则会先清空文件内容再进行写入。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        //创建一个FileWriter对象用于向文件中写入数据
        FileWriter writer = new FileWriter("example.txt");
        String str = "好好学习,天天向上";
        writer.write(str);  //将字符串写入到文本文件中
        writer.write("\r\n");   //将输出语句换行
        writer.close();
    }
}
同样可以通过调用重载的构造方法在文件末尾追加数据:
FileWriter writer = new FileWriter("example.txt",true);
字符流同样提供了带缓冲区的包装流,分别是BufferedReader和BufferedWriter,需要注意的是在BufferedReader中有一个重要的方法readLine(),用于一次读取一行文本。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        FileReader reader = new FileReader("src.txt");
        FileWriter writer = new FileWriter("des.txt");
        //创建一个BufferedReader缓冲对象
        BufferedReader br = new BufferedReader(reader);
        //创建一个BufferedWriter缓冲对象
        BufferedWriter bw = new BufferedWriter(writer);
        String str;
        while ((str=br.readLine())!=null){  //每次读取一行文本,判断是否到文件末尾
            bw.write(str);
            bw.newLine();   //写入一个换行符,该方法会根据不同的操作系统生成相应的换行符
        }
        br.close();
        bw.close();
    }
}
注:当缓冲区写满或调用close()方法时,缓冲区中的字符才会被写入到目标文件,因此一定要调用close()方法。
LineNumberReader
是可以追踪行号的输入流,是BufferedReader的直接子类。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        FileReader fr = new FileReader("src/Loop.java");   //创建字符输入流
        FileWriter fw = new FileWriter("copy.java");    //创建字符输出流
        LineNumberReader lr = new LineNumberReader(fr); //包装
        lr.setLineNumber(0);    //设置读取文件的起始行号为0
        String line = null;
        while ((line=lr.readLine())!=null){
            fw.write(lr.getLineNumber()+":"+line);  //将行号写入到文件中
            fw.write("\r\n");   //写入换行
        }
        lr.close();
        fw.close();
    }
}
从运行结果可以看出,行号是从1开始的,因为LineNumberReader类在读取换行符、回车符或者回车后紧跟换行符时,行号自动加1。
转换流
JDK提供了两个类可以将字节流转换成字符流,InputStreamReader和OutoutStreamWriter分别是Reader和Writer的子类。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        FileInputStream in = new FileInputStream("src.txt");    //创建字节输入流
        InputStreamReader isr = new InputStreamReader(in);  //将字节流输入转换成字符流输入
        BufferedReader br = new BufferedReader(isr);    //对字符流对象进行包装
        FileOutputStream out = new FileOutputStream("des.txt"); //创建字节输出流
        OutputStreamWriter osw = new OutputStreamWriter(out);   //将字节输出流转换成字符输出流
        BufferedWriter bw = new BufferedWriter(osw);    //对字符输出流对象进行包装
        String line;
        while ((line=br.readLine())!=null){
            bw.write(line);
            bw.newLine();
        }
        br.close();
        bw.close();
    }
}
注:只针对操作文本文件的字节流进行转换,如果字节流操作操作的是一张图片,会造成数据丢失。
其他IO流
ObjectInputStream和ObjectOutputStream
程序运行时,会在内存中创建多个对象,程序结束后这些对象都会被当作垃圾回收。如果想永久保存这些对象,可以将对象转为字节数据写入到硬盘上,这个过程称为对象序列化。ObjectOutputStream(对象输出流)来实现序列化。
注:当对象序列化时,必须保证该对象实现Serializable接口,否则会异常。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        Person p = new Person("Payne","xiaochen",24);
        System.out.println("-----写入文件前-----");
        System.out.println("Person对象id:"+p.getId());
        System.out.println("Person对象name:"+p.getName());
        System.out.println("Person对象age:"+p.getAge());
        //创建文件输出流对象,将数据写入objectStream.txt文件中
        FileOutputStream fos = new FileOutputStream("objectStream.txt");
        //创建对象输出流对象,用于处理输出流对象写入的数据
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        //将Person对象输出到输出流中
        oos.writeObject(p);
    }
}
class Person implements Serializable {
    private String id;
    private  String name;
    private int age;
    public Person(String id,String name,int age){
        super();
        this.id = id;
        this.name = name;
        this.age = age;
    }
    public String getId(){
        return id;
    }
    public String getName(){
        return name;
    }
    public int getAge(){
        return age;
    }
}
运行结果
-----写入文件前-----
Person对象id:Payne
Person对象name:xiaochen
Person对象age:24
Person对象被序列化后会生成二进制数据保存在“objectStream.txt”文件中,通过这些二进制数据可以恢复序列化之前的Java对象,此过程称为反序列化。ObjectInputStream类(对象输入流),可以实现对象的反序列化。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        //创建文件输入流对象,用于读取指定文件的数据
        FileInputStream fis = new FileInputStream("objectStream.txt");
        //创建对象输入流对象,并且从指定的输入流中读取数据
        ObjectInputStream ois = new ObjectInputStream(fis);
        //从objectStream.txt中读取Person对象
        Person p = (Person) ois.readObject();
        System.out.println("-----从文件中读取后-----");
        System.out.println("Person对象id:"+p.getId());
        System.out.println("Person对象name:"+p.getName());
        System.out.println("Person对象age:"+p.getAge());
    }
}
class Person implements Serializable {
    private String id;
    private  String name;
    private int age;
    public Person(String id,String name,int age){
        super();
        this.id = id;
        this.name = name;
        this.age = age;
    }
    public String getId(){
        return id;
    }
    public String getName(){
        return name;
    }
    public int getAge(){
        return age;
    }
}
运行结果
-----从文件中读取后-----
Person对象id:Payne
Person对象name:xiaochen
Person对象age:24
DataInputStream和DataOutputStream
当不需要存储整个对象的信息,而只需要存储对象的成员数据的基本数据类型时,可以使用DataInputStream和DataOutputStream。
可以用writeUTF()方法向输出流中写入采用UTF-8字符编码的字符串,用readUTF()方法从输入流中读取UTF-8字符编码的字符串。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
       FileOutputStream fos = new FileOutputStream("C:/Users/13403/Desktop/dataStream.txt");
       BufferedOutputStream bos = new BufferedOutputStream(fos);    //字节缓冲流
       DataOutputStream dos = new DataOutputStream(bos);
       dos.writeByte(12);   //写一个字节
       dos.writeChar('1');  //写一个字符
       dos.writeBoolean(true);  //写一个布尔值
       dos.writeUTF("同学,你好");   //写一个转换成UTF-8的字符串
       dos.close();
       FileInputStream fis = new FileInputStream("C:/Users/13403/Desktop/dataStream.txt");
       BufferedInputStream bis = new BufferedInputStream(fis);    //字节缓冲流
       DataInputStream dis = new DataInputStream(bis);
        System.out.println(dis.readByte()); //读一个字节
        System.out.println(dis.readChar()); //读一个字符
        System.out.println(dis.readBoolean());  //读一个布尔值
        System.out.println(dis.readUTF());  //读一个转换成UTF-8的字符串
        dis.close();
    }
}
运行结果
12
1
true
同学,你好
注:只有读取数据的顺序和写数据的顺序一样才能保证准确性。
PrintStream
之前通过write()方法只能输出字节或者字符类型数据,输出int类型或者对象等时,得先转为字符串在输出。为了方便操作可以使用PrintStream类用于打印数据的print()和println()方法,称为打印流。
PrintStream可以实现将基本数据类型的数据或者引用数据类型的对象格式化成字符串后再输出。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
       //创建一个PrintStream对象,将FileOutputStream读取到的数据输出
        PrintStream ps = new PrintStream(new FileOutputStream("printStream.txt",true));
        Student stu = new Student();
        ps.print("这是一个数字:");
        ps.println(19);
        ps.println(stu);    //打印Student对象
    }
}
class Student{
    //重写Object的toString()方法
    public String toString(){
        return "你好,同学";
    }
}
标准输入输出流
再System类中定义了三个常量:in和out、err。in为InputStream类型,标准输入流,默认情况下读取键盘输入的数据,out为PrintStream类型,标准输出流,默认情况下将数据输出到命令行窗口,err为PrintStream类型,标准错误流,和out一样将数据输出到控制台,但输出的的程序运行时的错误信息。
public class Test{
    public static void main(String[] args) throws Exception{
        StringBuffer sb = new StringBuffer();
        int ch;
        //while用于读取键盘输入的数据
        System.out.print("输入:");
        while((ch=System.in.read())!=-1){
            if(ch=='\r'||ch=='\n'){ //对字符进行判断,如果是回车或换行,跳出循环
                break;
            }
            sb.append((char)ch);
        }
        System.out.print("输出:"+sb);
    }
}
运行结果
输入:123abcd
输出:123abcd
可以对标准输入流和输出流进行重定向。
重定向流常用的静态方法
方法声明
功能描述
void setIn(InputStream in)
对标准输入流重定向
void setOut(PrintStream out)
对标准输出流重定向
void Err(PrintStream out)
对标准错误流输出重定向
下面利用标准输入输出流重定向拷贝文件。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        System.setIn(new FileInputStream("source.txt"));    //对输入流进程重定向
        System.setOut(new PrintStream("target.txt"));   //对输出流进程重定向
        //使用转换流将标准输出流转换为字符流,再用BufferedReader包装流包装
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String line;
        while((line=br.readLine())!=null){
            System.out.println(line);
        }
    }
}
PipedInputStream和PipedOutputStream
分别为管道输入流和管道输出流,是一种比较特殊的流,必须先建立连接才能彼此间通信。PipedOutputStream用于向管道写入数据,PipedInputStream用于从管道读取写入的数据。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        final PipedInputStream pis = new PipedInputStream();    //创建PipedInputStream对象
        final PipedOutputStream pos = new PipedOutputStream();  //创建PipedOutputStream对象
        //二者建立连接,也可以写成pos.connect(pis)
        pis.connect(pos);
        //创建一个线程,这里用了匿名内部实现类
        new Thread(new Runnable() {
            public void run() {
                BufferedReader br = new BufferedReader(new InputStreamReader(System.in));//将转换流进行包装
                //将从键盘读取的数据写入管道流
                PrintStream ps = new PrintStream(pos);
                while(true){
                    try{
                        System.out.print(Thread.currentThread().getName()+"要求输入内容:");
                        ps.println(br.readLine());//将读取的一行转换成字符串打印
                        Thread.sleep(1000);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        },"发送数据的线程").start();
        //创建一个线程
        new Thread(new Runnable() {
            public void run() {
                //下面代码是从管道流中读出数据,每读一行数据输出一次
                BufferedReader br = new BufferedReader(new InputStreamReader(pis));
                while(true){
                    try{
                        System.out.println(Thread.currentThread().getName()+"收到的内容:"+br.readLine());
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        },"接收数据的线程").start();
    }
}
运行结果
发送数据的线程要求输入内容:你好呀
接收数据的线程收到的内容:你好呀
发送数据的线程要求输入内容:Hello
接收数据的线程收到的内容:Hello
发送数据的线程要求输入内容:
可以看到两个对象通过connect()方法建立管道连接后,开启两个线程,一个用于将键盘输入的数据写入管道输出流,一个用于从管道中读取写入的数据,完成线程间的通信。
在字符流中也有一对PipedReader和PipedReader用于管道的通信,用法相似。
ByteArrayInputStream和ByteArrayOutputStream
之前的都是将文件直接存储到硬盘,JDK中提供了一个ByteArrayOutputStream类,在创建对象时就创建一个byte型数组的缓冲区,会把所有数据写入缓冲区,最后一次性写入文件。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        FileInputStream fis = new FileInputStream("source.txt");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();    //创建一个字节数组缓冲区
        FileOutputStream fos = new FileOutputStream("target.txt");
        //下面的代码是循环读取缓冲区中的数据,并将数据一次性写入文件
        int b;
        while((b=fis.read())!=-1){
            bos.write(b);   //数据写入缓冲区
        }
        fis.close();
        bos.close();
        fos.write(bos.toByteArray());   //将缓冲区中的数据一次性写入文件
        fos.close();
    }
}
注:缓冲区会根据存入数据的多少而自动变化,但读取的文件非常大时,就不能使用这个类,否则会造成内存溢出。
ByteInputStream是读取缓冲区中的数据,通过构造方法ByteArrayInputStream(byte[] b),从字节数组中每次读取一个字节。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        byte[] bt = new byte[]{97,98,99,100};   //创建一个字节数组
        ByteArrayInputStream bis = new ByteArrayInputStream(bt);    //读取字节数组中的数据
        //下面的代码是循环读取缓冲区中的数据
        int b;
        while ((b=bis.read())!=-1){
            System.out.println((char)b);
        }
    }
}
运行结果
a
b
c
d
再举一个例子,将source.txt文本中的数据写入缓冲区再读取。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        FileInputStream fis = new FileInputStream("source.txt");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        int b;
        while((b=fis.read())!=-1){
            bos.write(b);   //数据写入缓冲区
        }
fis.close();
bos.close();
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());    //读取缓冲区数组中的数据
        //下面的代码是循环读取缓冲区中的数据
        int c;
        while ((c=bis.read())!=-1){
            System.out.print((char)c);
        }
    }
}
运行结果
www
456
xyz
CharArrayReader和CharArrayWriter
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        FileReader fr = new FileReader("source.txt");
        CharArrayWriter cw = new CharArrayWriter(); //在内存中创建一个字符数组缓冲区
        //下面的代码是将数据写入缓冲区
        int b;
        while ((b=fr.read())!=-1){
            cw.write(b);
        }
        fr.close();
        cw.close();
        char[] c = cw.toCharArray();    //将缓冲区中的数组转换成字符型数组
        CharArrayReader cr = new CharArrayReader(c);    //读取字符数组中的数据
        //下面的代码时从缓冲区中读取数据并打印
        int i;
        while ((i=cr.read())!=-1){
            System.out.print((char)i);
        }
    }
}
运行结果
www
456
你好
SequenceInputStream
之前对文件进行操作都是通过一个流对数据进行处理。SequenceInputStream类可以将几个输出流串联在一起,合并为一个输出流。例如在下载文件时,可以分段下载最后再合并。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        //下面的代码是创建了两个流对象in1、in2
        FileInputStream in1 = new FileInputStream("stream1.txt");
        FileInputStream in2 = new FileInputStream("stream2.txt");
        //创建一个序列流,合并两个字节流in1和in2
        SequenceInputStream sis = new SequenceInputStream(in1,in2);
        FileOutputStream out = new FileOutputStream("stream.txt");
        int len;
        byte[] b = new byte[1024];  //创建一个1024个字节数组作为缓冲区
        //下面代码用于循环读取三个流中的文件
        while ((len=sis.read(b))!=-1){
            out.write(b,0,len); //将缓冲区的数据输出
            out.write("\r\n".getBytes());   //换行
        }
        sis.close();
        out.close();
    }
}
运行结果
在创建SequenceInputStream对象时使用的构造方法只有两个参数,若想合并更多流,需要接收一个Enumeration类型的对象。
import java.io.*;
import java.util.*;
public class Test{
    public static void main(String[] args) throws Exception{
        Vector vector = new Vector();   //创建Vector对象
        //下面的代码是创建了三个输入流对象in1、in2、in3
        FileInputStream in1 = new FileInputStream("stream1.txt");
        FileInputStream in2 = new FileInputStream("stream2.txt");
        FileInputStream in3 = new FileInputStream("stream3.txt");
        //下面代码是向Vector中添加三个输入流对象
        vector.addElement(in1);
        vector.addElement(in2);
        vector.addElement(in3);
        Enumeration e = vector.elements();  //获取Vector对象中的元素
        //将Enumeration对象中的流合并
        SequenceInputStream sis = new SequenceInputStream(e);
        FileOutputStream out = new FileOutputStream("stream.txt");
        int len;
        byte[] b = new byte[1024];  //创建一个大小为1024个字节数组的缓冲区
        while ((len=sis.read(b))!=-1){
            out.write(b,0,len);
        }
        sis.close();
        out.close();
    }
}
运行结果

File类
File类常用的构造方法
方法声明
功能描述
File(String pathname)
通过指定的一个字符串类型的文件路径创建一个新的File对象
File(String parent,String child)
根据指定的一个字符串类型的父路径和一个字符串类型的子路径(包括文件名)创建一个File对象
File(File parent,String child)
根据指定的File类的父路径和字符串类型的子路径(包括文件名称)创建一个File对象
File类常用方法
方法声明
功能描述
boolean exists()
判断File对象对应的文件或目录是否存在,若存在返回true,否则返回false
boolean delete()
删除File对象对应的文件或目录,若成功删除则返回true,否则返回false
boolean createNewFile()
当File对象对应的文件不存在时,该方法将新建一个此File对象所指定的新文件,若创建成功则返回true,否则返回false
String getName()
返回File对象表示的文件或文件夹的名称
String getPath()
返回File对象对应的路径
String getAbsolutePath()
返回File对象对应的绝对路径(在UNIX/Linux等系统上,如果路径是以正斜线/开始,则这个路径是绝对路径;在Windows等系统上,如果路径是从盘符开始,则这个路径是绝对路径)
String getParent()
返回File对象对应目录的父目录(即返回的目录不包含最后一级子目录)
boolean canRead()
判断File对象对应的文件或目录是否可读,若可读返回true,否则返回false
boolean canWrite()
判断File对象对应的文件或目录是否可写,若可写返回true,否则返回false
boolean isFile()
判断File对象对应的是否是文件(不是目录),若是文件返回true,否则返回false
boolean isDirectory()
判断File对象对应的是否是目录(不是文件),若是目录返回true,否则返回false
boolean isAbsolute()
判断File对象对应的文件或目录是否是绝对路径
long lastModified()
返回1970.01.01.00:00:00到文件最后修改时间的毫秒值
long length()
返回文件内容的长度
String []list()
列出指定目录的全部内容,只列出名称
File[] listFiles()
返回一个包含了File对象所有子文件和子目录的File数组
import java.io.File;
public class Test{
    public static void main(String[] args) throws Exception{
        File file = new File("example.txt");    //创建File文件对象,表示一个文件
        System.out.println("文件名称:"+file.getName()); //获取文件名称
        System.out.println("文件的相对路径:"+file.getPath());//获取文件的相对路径
        System.out.println("文件的绝对路径:"+file.getAbsolutePath());//获取文件的绝对路径
        System.out.println("文件的父路径:"+file.getParent());//获取文件的父路径
        System.out.println(file.canRead()?"文件可读":"文件不可读");//判断文件是否可读
        System.out.println(file.canWrite()?"文件可写":"文件不可写");//判断文件是否可写
        System.out.println(file.isFile()?"是一个文件":"不是一个文件");//判断是否是一个文件
        System.out.println(file.isDirectory()?"是一个目录":"不是一个目录");//判断是否是一个目录
        System.out.println(file.isAbsolute()?"是绝对路径":"不是绝对路径");//判断是否是一个绝对路径
        System.out.println("最后修改时间:"+file.lastModified());//得到文件最后修改时间
        System.out.println("文件大小:"+file.length()+"byte");//获取文件大小
        System.out.println("是否成功删除文件:"+file.delete());//是否成功删除文件
    }
}
运行结果
文件名称:example.txt
文件的相对路径:example.txt
文件的绝对路径:F:\IntelliJ IDEA-workspace\Test\example.txt
文件的父路径:null
文件可读
文件可写
是一个文件
不是一个目录
不是绝对路径
最后修改时间:1571371868243
文件大小:6byte
是否成功删除文件:true
遍历目录下的文件
import java.io.File;
public class Test{
    public static void main(String[] args) throws Exception{
        File file = new File("f:\\MyBooks");    //创建File对象,表示一个目录
        if(file.isDirectory()){ //判断File对象对应的目录是否存在
            String[] names = file.list();   //获取目录下的所有文件的文件名
            for (String name:names) {   //遍历文件名
                System.out.println(name);
            }
        }
    }
}
运行结果
程序员的数学 1 数学思维.pdf
程序员的数学 1 数学思维.txt
程序员的数学 2 概率统计.pdf
程序员的数学 2 概率统计.txt
程序员的数学 3 线性代数.pdf
程序员的数学 3 线性代数.txt
如果想得到某种后缀名的文件比如.txt的,File类中提供了一个重载的list(FilenameFilter filter)方法。FilenameFilter是一个接口,被称作文件过滤器,当中定义了一个抽象方法accept(File dir,String name),在调用list()方法时,需要实现文件过滤器,在accept()方法中做出判断。
import java.io.File;
import java.io.FilenameFilter;
public class Test{
    public static void main(String[] args) throws Exception{
        File file = new File("f:\\MyBooks");    //创建File对象,表示一个目录
        //创建过滤器对象
        FilenameFilter filter = new FilenameFilter() {
            //实现accept()方法
            public boolean accept(File dir, String name) {
                File currFile = new File(dir,name);
                //如果文件名以.txt结尾返回true,否则返回false
                if(currFile.isFile()&&name.endsWith(".txt")){
                    return true;
                }else{
                    return false;
                }
            }
        };
        if(file.exists()){//判断File对象对应的目录或文件(这里的对象是目录)是否存在
            String[] lists = file.list(filter);//获得过滤后的所有文件名数组
            for(String name:lists){
                System.out.println(name);
            }
        }
    }
}
运行结果
程序员的数学 1 数学思维.txt
程序员的数学 2 概率统计.txt
程序员的数学 3 线性代数.txt
如果想得到所有子目录要使用listFiles()方法,当对数组中的元素进行遍历时,如果元素中还有子目录需要遍历则需使用递归。
import java.io.File;
public class Test{
    public static void main(String[] args) throws Exception{
        File file = new File("f:\\新建文件夹");//创建一个代表目录的File对象
        fileDir(file);
    }
    public static void fileDir(File dir){
        File[] files = dir.listFiles(); //获得表示目录下所有文件的数组
        for(File file:files){   //遍历所有子目录和文件
            if(file.isDirectory())
                fileDir(file);  //如果是目录,递归调用fileDir()
            System.out.println(file.getAbsolutePath()); //输出文件绝对路径
        }
    }
}
运行结果
f:\新建文件夹\新建文件夹\新建 DOCX 文档.docx
f:\新建文件夹\新建文件夹
f:\新建文件夹\新建文件夹 (2)\新建文件夹
f:\新建文件夹\新建文件夹 (2)\新建文件夹 (2)\新建文本文档.txt
f:\新建文件夹\新建文件夹 (2)\新建文件夹 (2)
f:\新建文件夹\新建文件夹 (2)
f:\新建文件夹\新建文件夹 (3)
f:\新建文件夹\新建文件夹 (4)\新建 PPTX 演示文稿.pptx
f:\新建文件夹\新建文件夹 (4)
f:\新建文件夹\新建文本文档 (2).txt
f:\新建文件夹\新建文本文档.txt
删除文件及目录
import java.io.File;
public class Test{
    public static void main(String[] args) throws Exception{
        File file = new File("f:\\新建文件夹");//创建一个代表目录的File对象
        if(file.exists()){
            System.out.println(file.delete());
        }
    }
}
运行结果
false
可以看到删除失败了,因为该目录下包含了子目录或文件。所以这种情况需要用递归方式将整个目录以及其中的文件全部删除。
import java.io.File;
public class Test{
    public static void main(String[] args) throws Exception{
        File file = new File("f:\\新建文件夹");//创建一个代表目录的File对象
        deleteDir(file);
        System.out.println("是否删除成功:"+!file.exists());
    }
    public static void deleteDir(File dir){
        if(dir.exists()){   //判断传入的File对象是否存在
            File[] files = dir.listFiles(); //获得目录下所有文件的数组
            for(File file:files){
                if(file.isDirectory())
                    deleteDir(file);//如果是目录,递归
                else
                    file.delete();//如果是文件,直接删除
            }
            dir.delete();//删除完一个目录里的所有文件后,就删除这个目录
        }
    }
}
运行结果
是否删除成功:true
RandomAccessFile
在IO包中,提供了一个类RandomAccessFile,它不属于流类,但具有读写文件数据的功能,可以随机地从文件的任意位置开始执行读写数据的操作。
它可以将文件以只读或者读写的方式打开,它的两个构造方法,RandomAccessFile(File file,String mode)【参数file指定被访问的文件,参数mode指定访问文件的模式】和RandomAccessFile(String name,String mode)【参数name指定被访问文件的路径,参数mode指定访问文件的模式】。
参数mode有四个值,最常用的有两个“r”(只读方式打开文件,如过执行写入操作,会抛出IOException异常)和“rw”(读写方式打开文件,如果该文件不存在会自动创建)。
RandomAccessFile定位文件位置的方法
方法声明
功能描述
long getFilePointer()
返回当前读写指针所处的位置
void seek(long pos)
设定读写指针的位置,与文件开头相隔pos个字节数
int skipBytes(int n)
使读写指针从当前位置开始,跳过n个字节
void setLength(long newLength)
设置此文件的长度
当新创建一个RandomAccessFile对象时,该对象的文件记录指针位于文件头(也就是0处)。
创建一个文件“time.txt”里面写有“3”。
import java.io.RandomAccessFile;
public class Test{
    public static void main(String[] args) throws Exception{
        RandomAccessFile raf = new RandomAccessFile("time.txt","rw");
        //int times = 0;
        int times = Integer.parseInt(raf.readLine());//第一次读取文件时times为3
        if(times>0){
            //试用一次减少一次
            System.out.println("你还可以试用"+times--+"次!");
            raf.seek(0);    //使记录指针指向文件开头
            raf.writeBytes(times+"");   //将剩余次数再次写入文件
        }else{
            System.out.println("软件试用次数已到");
        }
        raf.close();
    }
}
5次运行结果
你还可以试用3次!
你还可以试用2次!
你还可以试用1次!
软件试用次数已到
软件试用次数已到
字符编码
字符码表是一种可以方便计算机识别的特定字符集,它是将每个字符和一个唯一的数字对应而形成的一张表。
解码和编码二进制的字节序列和明文的字符序列之间的转换。如果要把字节数组转换为字符串,可以用String类的构造方法String(byte[] bytes,String charsetName),反之可以通过String类的getBytes(String charsetName)方法把字符串按照指定的码表编码成字节数组。
import java.util.Arrays;
public class Test{
    public static void main(String[] args) throws Exception{
        String str = "中国";
        byte[] b1 = str.getBytes(); //利用默认的码表编码
        byte[] b2 = str.getBytes("GBK");    //使用GBK编码
        byte[] b3 = str.getBytes("UTF-8");  //使用UTF-8编码
        System.out.println(Arrays.toString(b1));    //打印字节数组的字符串形式
        System.out.println(Arrays.toString(b2));
        System.out.println(Arrays.toString(b3));
        String result1 = new String(b1);
        String result2 = new String(b2,"GBK");
        String result3 = new String(b3,"UTF-8");
        String result4 = new String(b2,"ISO8859-1");
        System.out.println(result1);
        System.out.println(result2);
        System.out.println(result3);
        System.out.println(result4);
    }
}
运行结果
[-42, -48, -71, -6]
[-42, -48, -71, -6]
[-28, -72, -83, -27, -101, -67]
中国
中国
中国
???ú
Windows系统默认使用GBK码表,可以看到ISO8859-1对GBK解码时出现四个问号,这是因为编码解码的码表不一致造成的。为此,可以将“中国”GBK编码→ISO8859-1解码→ISO8859-1编码→GBK解码。
public class Test{
    public static void main(String[] args) throws Exception{
        String str = "中国";
        byte[] b = str.getBytes("GBK");
        String temp = new String(b,"ISO8859-1");
        System.out.println(temp);
        byte[] b1 = temp.getBytes("ISO8859-1");
        String result = new String(b1,"GBK");
        System.out.println(result);
    }
}
运行结果
???ú
中国
注:不是每次解码的时候都可以通过逆向思维方式来得到正确结果的。
字符传输,当通过FileReader读取一个编码格式为GBK的文件,并将读取到的数据通过FileWrite写入一个编码格式为UTF-8的文件时,则会出现乱码现象。
转换流,通过构造方法InputStreamReader(InputStream in,String charsetName)和OutputStreamWriter(OutputStream in,String charsetName)创建流对象时,可以将需要读写的数据指定编码格式。
import java.io.*;
public class Test{
    public static void main(String[] args) throws Exception{
        //创建InputStreamReader对象,字节流转换为字符流
        Reader reader = new InputStreamReader(new FileInputStream("1.txt"),"GBK");
        //创建OutputStreamWriter对象
        Writer writer = new OutputStreamWriter(new FileOutputStream("2.txt",true),"UTF-8");
        char[] ch = new char[100];  //定义一个字符数组
        int len;
        len = reader.read(ch);  //将文件的内容读取到字符数组
        String string = new String(ch,0,len);   //使用字符数组创建字符串
        writer.write(string);   //向目标文件写入字符串
        reader.close();
        writer.close();
    }
}
运行结果

 

Java学习笔记7(IO)的更多相关文章

  1. java学习笔记之IO编程—内存流、管道流、随机流

    1.内存操作流 之前学习的IO操作输入和输出都是从文件中来的,当然,也可以将输入和输出的位置设置在内存上,这就需要用到内存操作流,java提供两类内存操作流 字节内存操作流:ByteArrayOutp ...

  2. 【原】Java学习笔记033 - IO

    package cn.temptation; public class Sample01 { public static void main(String[] args) { // 需求:继承关系中爷 ...

  3. Java学习笔记之——IO

    一. IO IO读写 流分类: 按照方向:输入流(读),输出流(写) 按照数据单位:字节流(传输时以字节为单位),字符流(传输时以字符为单位) 按照功能:节点流,过滤流 四个抽象类: InputStr ...

  4. Java学习笔记-10.io流

    1.输入流,只能从中读取数据,而不能向其写出数据.输出流,只能想起写入字节数据,而不能从中读取. 2.InputStream的类型有: ByteArrayInputStream 包含一个内存缓冲区,字 ...

  5. java学习笔记之IO编程—字节流和字符流

    1. 流的基本概念 在java.io包里面File类是唯一一个与文件本身有关的程序处理类,但是File只能够操作文件本身而不能操作文件的内容,或者说在实际的开发之中IO操作的核心意义在于:输入与输出操 ...

  6. java学习笔记之IO编程—对象序列化

    对象序列化就是将内存中保存的对象以二进制数据流的形式进行处理,可以实现对象的保存或网络传输. 并不是所有的对象都可以被序列化,如果要序列化的对象,那么对象所在的类一定要实现java.io.Serial ...

  7. java学习笔记之IO编程—打印流和BufferedReader

    1.打印流(PrintWriter) 想要通过程序实现内容输出,其核心一定是要依靠OutputStream类,但是OutputStream类有一个最大缺点,就是这个类中的输出操作功能有限,所有的数据一 ...

  8. java学习笔记之IO编程—目录和文件的拷贝

    进行文件或目录的拷贝时,要先判断处理对象是文件还是目录,如果是文件则直接拷贝,如果是目录还需要拷贝它的子目录及其文件,这就需要递归处理了 import java.io.*; class FileUti ...

  9. java学习笔记之IO编程—File文件操作类

    1. File类说明 在Java语言里面提供有对于文件操作系统操作的支持,而这个支持就在java.io.File类中进行了定义,也就是说在整个java.io包里面,File类是唯一一个与文件本身操作( ...

  10. Java学习笔记--文件IO

    简介 对于任何程序设计语言,输入和输出(Input\Output)都是系统非常核心的功能,程序运行需要数据,而数据的获取往往需要跟外部系统进行通信,外部系统可能是文件.数据库.其他程序.网络.IO设备 ...

随机推荐

  1. 关于Java Web结构和SSM框架的理解

    Java Web常见的三层结构 表现层:也就是Web层,常见的框架有Spring MVC.Struts2 ,并包括用于展示的界面,如JSP界面:业务层:Service层,专注于业务逻辑的实现:持久层: ...

  2. 解决python引包错误

    # coding=utf8# date = 2019/12/23 19:54# 清白丶之年__照林""" # Solve Import Lib Error Add Som ...

  3. 基于 abp vNext 和 .NET Core 开发博客项目 - 集成Hangfire实现定时任务处理

    上一篇文章(https://www.cnblogs.com/meowv/p/12956696.html)成功使用了Redis缓存数据,大大提高博客的响应性能. 接下来,将完成一个任务调度中心,关于定时 ...

  4. 将`VuePress`建立的博客部署到GitHub或Gitee上

    将VuePress建立的博客部署到GitHub或Gitee上 在上一篇中,我们详细介绍了如何利用VuePress搭建起个人博客系统,但这只是在本地debug启动的,接下来,我们把它部署到Github网 ...

  5. 线程池 & 线程调度

    线程池1. 第四种获取线程的方法:线程池,一个 ExecutorService,它使用可能的几个池线程之 一执行每个提交的任务, 通常使用 Executors 工厂方法配置. 2. 线程池可以解决两个 ...

  6. Docker 笔记一相关命令

    Centos 7 : Service network restart 重启网络 Ip addr 查看ip地址 Uname -r 查看内核版本 Yum install docker 安装docker 命 ...

  7. 泛微 e-cology远程代码执行漏洞

    影响版本:泛微 e-cology<=9.0 漏洞分析: 问题出现在 resin 下 lib 中的 bsh.jar 文件里,问题类 bsh.servlet.BshServlet,可 doGet 方 ...

  8. django-模型层(ORM语法)

    今日内容概要(重要) 模型层(ORM语法):跟数据库打交道的 单表查询(增删改查) 常见的十几种查询方法 神奇的双下划线查询 多表操作 外键字段的增删改查 跨表查询(重点) 子查询 联表查询 今日内容 ...

  9. vue 中引入使用jquery

    1.首先在配置文件中添加 cnpm install  下载jquery文件 2.在webpack配置文件中添加下面代码 3.接着在webpack.base.conf.js中module.exports ...

  10. java方法句柄-----1.方法句柄类型、调用

    目录 方法句柄 1.方法句柄的类型 1.1MethodType类的对象实例的创建 1.1.1 通过指定参数和返回值的类型来创建MethodType.[显式地指定返回值和参数的类型] 1.1.2 通过静 ...