黑马程序员——Java基础---IO流

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-

一.简述IO流

Java对数据的操作是通过流的方式,操作流的对象都在IO包中,所以IO流(InputOutputStream)可以用来处理设备之间的数据传输。

处理数据时,一定要先明确数据源,与数据目的(数据汇)。数据源可以是文件,键盘。数据目的可以是文件,显示器或者其他设备。而流只是在帮助数据进行传输,并对传输的数据进行处理,比如过滤处理,转换处理等。

流按操作数据分类可分为:字节流,字符流。

流按流向分类可分为:输入流,输出流。

1. IO流常用基类

字节流的抽象基类:InputStream,OutputStream。

字符流的抽象基类:Reader,Writer。

由这四个类派生出来的子类,名称都是以其父类名作为子类名的后缀。如:InputStream的子类FileInputStream;Reader的子类FileReader。

2. IO程序的书写

导入IO包中的类。
进行IO异常处理。
在finally中对流进行关闭。
示例代码:

/* 导入包中的类 */
import java.io.*;

public static void main(String[] args) {
    FileReader fw = null;

    /* 进行IO异常处理 */
    try {
        fw = new FileWriter("source.txt");
        fw.write("anything");
    } catch (IOException e) {
        throw new RuntimeException("写入失败");
    }
    /* 在finally中关闭流资源 */
    finally {
        if (fw != null) {
            try {
                fw.close();
            } catch (IOException e) {
                throw new RuntimeException("关闭写入流失败");
            }
        }
    }
}

注意:

(1)必须自行关闭流资源。虽然Java有垃圾回收机制,但是垃圾回收机制只能处理Java虚拟机所产生的内存垃圾,而流所消耗的操作系统资源、硬件、网络资源等外部资源,不在垃圾回收器的处理范围内。

(2)必须处理IO异常。因为你调用的方法向你抛出了异常,你必须选择接受它,或者抛给调用你方法的方法,否则程序会报错。

二. 字符流

字符流继承关系:
黑马程序员——Java基础---IO流_第1张图片

2. 字符流创建文件

字符流创建文件步骤:

(1)创建流对象,建立数据存放文件。
(2)调用流对象写入文件,将数据写入流。
(3)关闭流资源,并将流中的数据清空到文件中。
示例代码:

FileWriter fw = new Filewriter("Test.txt");

fw.write("text");

fw.close();

注意:关闭流后,就不能对文件进行读取和写入的操作了。

3. 字符流读取文件

字符流读取文件步骤:

(1)建立一个流对象,将已存在的一个文件加载进流。
(2)创建一个临时存放数据的数组。
(3)调用流对象的读取方法将流中的数据读入到数组中。
(4)关闭流资源
代码示例:

FileReader fr = new FileReader("Test.txt");

char[] ch = new char[1024];

fr.read(ch);

fr.close();

注意:

定义文件路径时,用“\”。
在创建文件时,目录下如果有同名文件将会被覆盖。
在读取文件时,必须保证该文件已存在,否则出异常。
定义数组时,根据内存的进位机制,建议定义大小为1024的整数倍。

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/*
 * 需求:复制一个文本文件
 */
public class CopyText {
    public static void main(String[] args) {
        charCopy();
        strCopy();
    }

    public static void charCopy() {    // 按字符读
        FileReader fr = null;
        FileWriter fw = null;

        try {
            fr = new FileReader("D:\\1.txt");
            fw = new FileWriter("D:\\2.txt");

            for (int len = 0; (len = fr.read()) != -1; ) {
                fw.write(len);
            }
        } catch (IOException e) {
            throw new RuntimeException("读写失败");
        } finally {
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e){
                    throw new RuntimeException("关闭读取流失败");
                }
                try {
                    fw.close();
                } catch (IOException e) {
                    throw new RuntimeException("关闭写入流失败");
                }
            }
        }
    }

    public static void strCopy() {    // 按字符数组读
        FileReader fr = null;
        FileWriter fw = null;

        try {
            fr = new FileReader("D:\\1.txt");
            fw = new FileWriter("D:\\2.txt");

            char[] str = new char[1024];

            for (int len = 0; (len = fr.read(str)) != -1; ) {
                fw.write(str, 0, len);
            }
        } catch (IOException e) {
            throw new RuntimeException("读写失败");
        } finally {
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    throw new RuntimeException("关闭读取流失败");
                }
                try {
                    fw.close();
                } catch (IOException e) {
                    throw new RuntimeException("关闭写入流失败");
                }
            }
        }
    }
}

4 .字符流缓冲区

字符流缓冲区的出现提高了数据的读写效率。

缓冲区是提高效率用的,给谁提高呢?

是给字符输出流提高效率用的,那就意味着,缓冲区对象建立时,必须要先有流对象。明确要提高具体流对象的效率。

该缓冲区提供了一个一次读一行的方法readLine(),方便于对文本数据的获取。当返回null时,表示读到文件末尾。该方法返回的时候只返回回车之前的数据内容,并不返回回车符。

该缓冲区还提供了一个跨平台的换行符newLine()。

字符流缓冲区对应类:

BufferedWriter
BufferedReader
注意:

缓冲区要结合流才可以使用。
在流的基础上对流的功能进行了增强。
练习一:

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

/*
 * 需求:读取文本文件
 */
public class BufferReaderDemo {
    public static void main(String[] args) {
        FileReader fr;
        try {
            //创建一个读取流对象和文件相关联
            fr = new FileReader("D:\\buf2.txt");

        } catch (FileNotFoundException e) {
            throw new RuntimeException("找不到文件");
        }
        //为了提高效率,加入缓冲技术。将字符读取流对象作为参数传递给缓冲对象的构造函数
        BufferedReader bufr = new BufferedReader(fr);

        try {
            for (String line = null; (line = bufr.readLine()) != null; ) {
                System.out.println(line);
            }
        } catch (IOException e) {
            throw new RuntimeException("读取文件失败");
        } finally {
            try {
                bufr.close();
            } catch (IOException e) {
                throw new RuntimeException("关闭读取流失败");
            }
        }   
    }
}

练习二:

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

/*
 * 需求:创建文本文件并写入数据
 */
public class BufferedWriterDemo {
    public static void main(String[] args) {
        FileWriter fw;
        try {
            fw = new FileWriter("D:\\buf2.txt");
        } catch (IOException e) {
            throw new RuntimeException("创建文件失败");
        }

        BufferedWriter bufw = new BufferedWriter(fw);
        try {
            for (int x = 1; x < 5; x++) {
                try {
                    bufw.write("Line" + x);
                } catch (IOException e) {
                    throw new RuntimeException("写入数据失败");
                }
                try {
                    bufw.newLine();
                } catch (IOException e) {
                    throw new RuntimeException("插入行失败");
                }
                try {
                    bufw.flush();
                } catch (IOException e) {
                    throw new RuntimeException("刷新流失败");
                } 
            } 
        } finally {
            try {
                bufw.close();
            } catch (IOException e) {
                throw new RuntimeException("关闭写入流失败");
            }   
        }
    }
}

三. 装饰设计模式

装饰设计模式是对原有类进行了功能的改变和增强。

装饰类是通过构造方法将已有对象传入,基于已有的功能,并提供增强功能的一个自定义类。

装饰和继承的比较:

装饰设计模式可提高扩展性。
装饰设计模式比继承要灵活,避免了继承体系臃肿,降低了类与类之间的关系。
装饰类是具备已有对象的功能后,提供更强的功能,所以装饰类和被装饰类通常属于一个体系。

/*
 * 假设有一个专门用于读取数据的类MyReader,其子类有MyTextReader,MyMediaReader,MyDataReader。现在要新增一个缓冲功能MyBufferedReader。
 */

class MyBufferedReader {
    MyBufferedReader(MyTextReader text) {}
    MyBufferedReader(MyMediaReader media) {}
    MyBufferedReader(MyDataReader data) {}
}
// 通过继承使每一个子类都具备缓冲功能。这样的继承体系很复杂,并不利于扩展。
class MyBufferedReader extends MyReader {
    private MyReader r;
    MyBufferedReader(MyReader r) {
        this.r = r;
    }
}

class MyTextReader {
    public static void main(String[] args) {
        MyBufferedReader myBuf = new MyBufferedReader(new MyTextReader("*.txt"));
    }
}
//单独描述缓冲内容,将需要被缓冲的对象传递给缓冲区。这样的继承体系就变得简单。

代码示例:

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

/*
 * 需求:模拟BufferdReader,自定义一个类包含与readLine一致的方法。
 */
class MyBufferedReader extends Reader {
    private Reader r;

    MyBufferedReader(Reader r) {
        this.r = r;
    }

    public String myReadLine() throws IOException {
        StringBuilder sb = new StringBuilder();

        for (int ch = 0; (ch = r.read()) != -1; ) {    // 如果读到回车就返回字符集,如果没有就添加一个字符
            if (ch == '\r') {
                continue;
            }   
            if (ch == '\n') {
                return sb.toString();
            } else {
                sb.append((char)ch);
            }
        }
        if (sb.length() != 0) {    // 如果在文件的最后一行没有输入回车,也需要显示出该行
            return sb.toString();
        }
        return null;
    }

    // 复写父类中的读取流方法
    public int read(char[] cbuf, int off, int len) throws IOException {
        return r.read(cbuf, off, len);
    }

    // 复写父类中的关闭流方法
    public void close() throws IOException {
        r.close();
    }
}

public class MyBufferedReaderDemo {
    public static void main(String[] args) throws IOException {
        MyBufferedReader myBuf = null;
        try {   
            myBuf = new MyBufferedReader(new FileReader("D:\\buf.txt"));
            for (String line = null;(line = myBuf.myReadLine()) != null; System.out.println(line));
        } catch (IOException e) {
            throw new RuntimeException("读取数据失败");
        } finally {
            try {
                if (myBuf != null) {
                    myBuf.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("关闭读取流失败");
            }       
        }
    }
}
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

/*
 * 需求:模拟一个LineNumberReader类,读取文本文档带行号输出。
 */
class MyLineNumberReader extends MyBufferedReader
{
    private int lineNumber;

    MyLineNumberReader(Reader r) {
        super(r);
    }

    public String myReadLine() throws IOException {
        lineNumber ++;
        return super.myReadLine();
    }

    public void setLineNumber(int lineNumber) {
        this.lineNumber = lineNumber;
    }

    public int getLineNumber() {
        return lineNumber;
    }
}

public class LineNumberReaderDemo {
    public static void main(String[] args) {
        FileReader fr;
        try {
            fr = new FileReader("D:\\1.txt");
        } catch (FileNotFoundException e) {
            throw new RuntimeException("找不到文件");
        }

        MyLineNumberReader lnr = new MyLineNumberReader(fr);

        lnr.setLineNumber(0);

        try {
            for (String line = null; (line = lnr.myReadLine()) != null; ) {
                System.out.println(lnr.getLineNumber() + ":" + line);
            }
        } catch (IOException e) {
            throw new RuntimeException("读取文件失败");
        } finally {
            try {
                lnr.close();
            } catch (IOException e) {
                throw new RuntimeException("关闭流失败");
            }
        }
    }
}

四. 字节流

字节流不仅可以操作字符,还可以操作其他媒体文件。基本操作与字符流类相同。

1 字节流继承体系

黑马程序员——Java基础---IO流_第2张图片

2 字节流创建文件

字节流创建文件步骤:

创建流对象,建立数据存放文件。
调用流对象写入文件,将数据写入流。
关闭流资源,并将流中的数据清空到文件中。

FileOutputStream fos = new FileOutputStream("fos.txt");

fos.write("abcde".getBytes());

fos.close();

3. 字节流读取文件

字节流读取文件步骤:

建立一个流对象,将已存在的一个文件加载进流。
创建一个临时存放数据的数组。
调用流对象的读取方法将流中的数据读入到数组中。
关闭流资源

FileInputStream fis = new FileInputStream("Test.txt");

byte[] buf = new char[fis.available()];

fr.read(buf);

fr.close();

其他方法:

int available():返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。

4. 字节流缓冲区

字节流缓冲区提高了字节流的读写效率。

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/*
 * 需求:拷贝mp3文件,并比较字节流与缓冲字节流操作文件的效率。
 */
public class CopyMp3 {
    public static void main(String[] args) {

        long start = System.currentTimeMillis();
        noBuffered();
        long end = System.currentTimeMillis();

        System.out.println("未加入缓冲区: " + (end - start) + "毫秒");

        start = System.currentTimeMillis();
        buffered();
        end = System.currentTimeMillis();

        System.out.println("加入缓冲区: " + (end - start) + "毫秒");
    }

    //未加入缓冲区
    public static void noBuffered() {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("d:\\1.mp3");
            fos = new FileOutputStream("d:\\2.mp3");

            byte[] buf = new byte[fis.available()];    //FileInputStream的available方法通过文件描述符获取文件的总大小

            int len = 0;

            while ((len = fis.read(buf)) != -1) {
                fos.write(buf, 0, len);
            }
        } catch (IOException e) {
            throw new RuntimeException("复制文件失败");
        } finally {
            try {
                fos.close();
            } catch (IOException e){
                throw new RuntimeException("关闭输出流失败");
            }
            try {
                fis.close();
            } catch (IOException e) {
                throw new RuntimeException("关闭写入流失败");
            }
        }   
    }

    //加入缓冲区
    public static void buffered() {
        BufferedInputStream bufis = null;
        BufferedOutputStream bufos = null;

        try {
            bufis = new BufferedInputStream(new FileInputStream("d:\\3.mp3"));
            bufos = new BufferedOutputStream(new FileOutputStream("d:\\4.mp3"));
            for (int len = 0; (len = bufis.read()) != -1; ) {           
                bufos.write(len);
            }

        } catch (IOException e) {
            throw new RuntimeException("复制文件失败");
        } finally {
            try {
                bufos.close();

            } catch (IOException e) {
                throw new RuntimeException("关闭输出流失败");
            } catch (NullPointerException e) {
                throw new RuntimeException("找不到文件");
            }
            try {
                bufis.close();

            } catch (IOException e) {
                throw new RuntimeException("关闭写入流失败");
            }
        }
    }
}

五. 转换流

转换流方便了字符流与字节流之间的操作,是字符流与字节流之间的桥梁。

转换流的应用:当字节流中的数据都是字符时,转成字符流操作更高效。

转换流抽象基类:

InputStreamReader
OutputStreamWriter
字符流对象转成字节流对象:

// 获取键盘录入对象
InputStream in = System.in;
// 将字符流对象转成字节流对象
InputStreamReader isr = new InputStreamReader(in);
// 为了提高效率,加入缓冲流
BufferedReader bufr = new BufferedReader(isr);

/* 获取键盘录入的常见写法 */
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

字节流对象转成字符流对象:

// 把对象输出到显示器
OutputStream out = System.out;
// 将字节流对象转成字符流对象
OutputStreamWriter osw = new OutputStreamWriter(out);
// 为了提高效率,加入缓冲流
BufferedWriter bufw = new BufferedWriter(osw);

/* 输出到显示器的常见写法 */
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

六.标准输入输出流

System类中的字段是in,out。它们各代表了系统标准的输入和输出设备。默认输入设备是键盘,输出设备是显示器。例如,获取键盘录入数据,然后将数据流向显示器,那么显示器就是目的地。

System.in的类型是InputStream。

System.out的类型是PrintStream。OutputStream中FilterOutputStream的子类。

改变默认设备:通过System类的setIn,setOut方法对默认设备进行改变。

System.setIn(new FileInputStream("1.txt"));    //将源改成文件1.txt。
System.setOut(new FileOutputStream(“2.txt”));    //将目的改成文件2.txt

标准输入输出形式:

BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

代码示例:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;

/*
 * 需求:在控制台中输入字符,输出到文本文件中
 */
public class TransStreamDemo {
    public static void main(String args[]) {
        try {
            System.setOut(new PrintStream("d:\\b.txt"));
        } catch (FileNotFoundException e) {
            throw new RuntimeException("找不到文件");
        }

        BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bfw = new BufferedWriter(new OutputStreamWriter(System.out));

        String line = null;
        try {
            while ((line = bfr.readLine()) != null) {
                if (!line.endsWith("over")) {    // 输入over结束循环
                    bfw.write(line);
                    bfw.newLine();
                    bfw.flush();
                } else {
                break;
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("操作文件失败");
        } finally {
            try {
                bfr.close();
            } catch(IOException e) {
                throw new RuntimeException("关闭读取流失败");
            }
            try {
                bfw.close();
            } catch(IOException e) {
                throw new RuntimeException("关闭写入流失败");
            }
        }       
    }
}

七.流操作的基本规律

流对象:其实很简单,就是读取和写入。但是因为功能的不同,流的体系中提供了N多的对象。那么开始时,到底该用哪个对象更为合适呢?这就需要明确流的操作规律。

流操作需要明确三点:

(1)明确源和目的。
(2)操作的数据是否是纯文本。
(3)明确使用哪个对象。
(4)是否需要提高效率。

八.文件类

File类用来将文件或者文件夹封装成对象,方便对文件与文件夹的属性信息进行操作。

File对象可以作为参数传递给流的构造函数。

File类的常用方法:

boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。
boolean mkdir():创建文件夹。
boolean mkdirs():创建多级文件夹。

boolean delete():删除文件。如果文件正在被使用,在删除失败,返回false。
void deleteOnExit():在程序退出时删除指定文件。

boolean exists():测试此抽象路径名表示的文件或目录是否存在。
boolean isFile():测试此抽象路径名表示的文件是否是一个标准文件。
boolean isDirectory():测试此抽象路径名表示的文件是否是一个目录。
boolean isHidden():测试此抽象路径名指定的文件是否是一个隐藏文件。
boolean isAbsolute(): 测试此抽象路径名是否为绝对路径名。

String getName():返回由此抽象路径名表示的文件或目录的名称。
String getPath():将此抽象路径名转换为一个路径名字符串。
String getParent():返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。
代码示例:

import java.io.File;
import java.io.FilenameFilter;

/*
 * 需求:过滤.bmp后缀的文件
 */
public class FileDemo2 {
    public static void main(String[] args) {
        File dir = new File("d:\\java\\day18");

        String[] arr = dir.list(new FilenameFilter() {    //匿名内部类,直接完成accept方法的复写
            public boolean accept(File dir, String name) {
                return name.endsWith(".bmp");
            }
        } );

        System.out.println("Total:" + arr.length);

        for (String name: arr) {
            System.out.println(name);
        }   
    }
}

九. 递归

递归是函数调用函数自身。

什么时候用递归呢?

当一个功能被重复使用,而每一次使用该功能时的参数不确定,都由上次的功能元素结果来确定。
简单说:功能内部又用到该功能,但是传递的参数值不确定。(每次功能参与运算的未知内容不确定)。
递归的注意事项:
1:一定要定义递归的条件。
2:递归的次数不要过多。容易出现StackOverflowError栈内存溢出错误。
其实递归就是在栈中不断地加载同一个函数。
应用:当某一功能需要重复使用时使用。

代码示例:

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/*
 * 需求:获得某一目录下(含子目录)所有.java的文件,生成一个列表,导出成文本文件。
 */
public class JavaFileList {
    public static void main(String[] args) {
        File dir = new File("d:\\java");
        ArrayList list = new ArrayList();
        makeList(dir, list);

        File file = new File(dir, "filelist.txt");
        toFile(file.toString(), list);
    }

    /* 制作目录列表*/
    public static void makeList(File dir, List list) {
        File[] files = dir.listFiles();    // listFiles()返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。

        for (File file: files) {
            if (file.isDirectory()) {
                makeList(file, list);    // 如果查找到的文件是目录,就递归,查找其目录下的文件
            } else {
                if (file.getName().endsWith(".java")) {    // 如果不是目录,就添加该文件路径到list集合
                    list.add(file);
                }
            }
        }
    }

     /* 生成文件 */
    public static void toFile(String listFile, List list) {
        BufferedWriter bufw = null;

        try {
            bufw = new BufferedWriter(new FileWriter(listFile));
        } catch (IOException e) {
            throw new RuntimeException("创建文件失败");
        }
        try {   
            for (File file: list) {
                String path = file.getAbsolutePath();

                bufw.write(path);
                bufw.newLine();
                bufw.flush();
            }
        } catch (IOException e) {
            throw new RuntimeException("写入文件失败");
        } finally {
            if (bufw != null) {
                try {
                    bufw.close();
                } catch (IOException e) {
                    throw new RuntimeException("关闭输出流失败");
                }       
            }
        }
    }
}

练习二:

import java.io.File;

/*
 * 需求:删除一个带内容的目录
 */
public class RemoveDir {
    public static void main(String[] args) {
        File dir = new File("d:\\java");
        removeDir(dir);
    }

    public static void removeDir(File dir) {
        File[] files = dir.listFiles();

        for (int x = 0; x < files.length; x++) {    // 每个角标代表一个文件路径,因为要移除对应数组的角标,所以用普通for循环
            if (files[x].isDirectory()) {    // 如果判断结果是目录,就递归,进入到其目录下操作
                removeDir(files[x]);
            } else {
                System.out.println(files[x].toString() + " :File: " + files[x].delete());    // 先删除文件
            }
        }
        System.out.println(dir + " :Dir: " + dir.delete());    // 再删除目录
    }
}

十.打印流

打印流提供了打印方法,可以将各种数据类型的数据照原样打印。

方法:

PrintStream():字节打印流。

构造函数可以接收的参数类型
File对象:File
字符串路径:String
字节输出流:OutputStream
PrintWriter():字符打印流。

构造函数可以接收的参数类型
File对象:File
字符串路径:String
字节输出流:OutputStream
字符输出流:Writer

import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;

/*
 * 需求:在控制台中读入键盘录入字符,把字符转换成大写,输出到文件中
 */
public class PrintStreamDemo {
    public static void main(String[] args) throws IOException {
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

        PrintWriter out = null;

        try {
            out = new PrintWriter(new FileWriter("D:\\2.txt"), true);

            for (String line = null; (line = bufr.readLine()) != null; ) {
                if ("over".equals(line)) {
                    break;
                }
                out.println(line.toUpperCase());    //把所有字符转换成大写
            }
        } catch (IOException e) {
            throw new RuntimeException("操作文件失败");
        } finally {
            out.close();
            try {
                bufr.close();
            } catch (IOException e) {
                throw new RuntimeException("关闭读取流失败");
            }
        }   
    }
}

十一. 序列流

序列流(SequenceInputStream)可以对多个流进行合并。

示例:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/*
 * 需求:分割文件
 */
public class SplitFile {
    public static void main(String[] args) {
        FileInputStream fis;
        try {
            fis = new FileInputStream("d:\\1.mp3");
        } catch (FileNotFoundException e1) {
            throw new RuntimeException("找不到文件");
        }

        FileOutputStream fos = null;

        byte[] buf = new byte[1024 * 1024];    // 每个部分1MB

        try {
            for (int len = 0, count = 1; (len = fis.read(buf)) != -1; ) {
                fos = new FileOutputStream("d:\\" + (count ++) + ".part");
                fos.write(buf, 0, len); 
            }
        } catch (IOException e) {
            throw new RuntimeException("操作文件失败");
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    throw new RuntimeException("关闭输出流失败");  
                }
                try {
                    fis.close();
                } catch (IOException e) {
                    throw new RuntimeException("关闭输入流失败");
                }
            }
        }
    }
}
mport java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;

/*
 * 需求:合并文件
 */
public class MergeFile {
    public static void main(String[] args) throws IOException {
        ArrayList al = new ArrayList();    // 定义一个集合,用字节流读取文件的部分,放入集合 
        for (int x = 1; x <= 3; x++) {
            al.add(new FileInputStream("d:\\" + x + ".part"));
        }

        final Iterator it = al.iterator();

        Enumeration en = new Enumeration() {    // 用枚举遍历取出
            public boolean hasMoreElements() {
                return it.hasNext();
            }
            public FileInputStream nextElement() {
                return it.next();
            }
        };

        SequenceInputStream sis = new SequenceInputStream(en);    // 导入序列流

        FileOutputStream fos = new FileOutputStream("d:\\1.mp3");    // 合并成一个文件

        byte[] buf = new byte[1024];

        for (int len = 0; (len = sis.read(buf)) != -1; ) {
            fos.write(buf, 0, len);
        }

        fos.close();
        sis.close();
    }
}

十二. 随机访问文件类

随机访问文件类(RandomAccessFile)具备读写的方法,通过skipBytes(int x),seek(int x)实现随机访问。

该类不算是io体系中的子类,而是直接继承自Object。但是它是IO包中的成员,因为它具备读和写的功能。其内部封装了字节输入流和输出流,通过指针对数组的元素进行操作。可以通过getFilePointer获取指针的位置,同时可以通过seek改变指针的位置。

通过构造函数可以看出,该类只能操作文件,而且操作的文件还有模式:r(只读),rw(读写)。

只读和读写的区别:

只读模式不会创建文件,而是去读取一个已存在的文件。如果该文件不存在,则会出现异常。
读写模式操作的文件如果不存在,会自动创建。如果存在,不会覆盖已存在的文件。
代码示例:

import java.io.IOException;
import java.io.RandomAccessFile;

/*
 * 需求:用随机访问文件把2个人的姓名和年龄信息写入一个文件,并读取第二个人的信息。
 */
public class RandomAccessFileDemo {
    public static void main(String[] args) {
        readFile();
        writeFile();
    }

    /* 读取文件 */
    public static void readFile() {
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile("D:\\ran.txt", "r");

            // 跳过指定的字节数
            raf.skipBytes(8);

            byte[] buf = new byte[4];
            raf.read(buf);
            String name = new String(buf);

            // 从此文件读取一个有符号的 32 位整数
            int age = raf.readInt();    

            System.out.println("name = " + name);
            System.out.println("age = " + age);

        } catch (IOException e) {
            throw new RuntimeException("操作文件失败");
        } finally {
            if (raf != null) {
                try {
                    raf.close();
                } catch (IOException e) {
                    throw new RuntimeException("关闭流失败");
                }
            }   
        }
    }

    /* 写入文件 */
    public static void writeFile() {
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile("D:\\ran.txt", "rw");

            raf.write("Lisi".getBytes());
            raf.writeInt(97);

            raf.write("Wang".getBytes());
            raf.writeInt(99);

        } catch (IOException e) {
            throw new RuntimeException("写入文件失败");
        } finally {
            if (raf != null) {
                try {
                    raf.close();
                } catch (IOException e) {
                    throw new RuntimeException("关闭流失败");
                }
            }
        }
    }
}

13. 管道流

管道流按字节流来分,分为管道输出流(PipedOutputStream)和管道输入流(PipedInputStream),利用 java.io.PipedOutputStream和java.io.PipedInputStream可以实现线程之间的二进制信息传输。如果要进行管道输出,则必须把输出流连在输入流上。

代码示例:

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;


class Read implements Runnable {
    private PipedInputStream in;

    Read(PipedInputStream in) {
        this.in = in;
    }

    public void run() {
        try {
            byte[] buf = new byte[1024];

            System.out.println("读取前……没有数据阻塞");
            int len = in.read(buf);
            System.out.println("读到数据……阻塞结束");

            String s = new String(buf, 0, len);
            System.out.println(s);

        } catch (IOException e) {
            throw new RuntimeException("管道输入流失败");
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                throw new RuntimeException("关闭管道输入流失败");
            }
        }
    }
}

class Write implements Runnable {
    private PipedOutputStream out;

    Write(PipedOutputStream out) {
        this.out = out;
    }

    public void run() {
        try {
            System.out.println("开始写入数据,等待6秒");
            Thread.sleep(6000);

            out.write("管道输出".getBytes());

        } catch (Exception e) {
            throw new RuntimeException("管道输出流失败");
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                throw new RuntimeException("关闭管道输出流失败");
            }
        }
    }
}

public class PipedStreamDemo {
    public static void main(String[] args) throws IOException {
        PipedInputStream in = new PipedInputStream();
        PipedOutputStream out = new PipedOutputStream();

        in.connect(out);

        Read r = new Read(in);
        Write w = new Write(out);
        new Thread(r).start();
        new Thread(w).start();
    }
}

十四. 操作对象类

ObjectInputStream与ObjectOutputStream:操作对象。被操作的对象需要实现Serializable(标记接口)。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/* 序列化处理对象 */
class Person implements Serializable {
    public static final long serialVersionUID = 42L;

    private String name;
    private int age;
    private String country;

    Person(String name, int age, String country) {
        this.name = name;
        this.age = age;
        this.country = country;
    }

    public String toString() {
        return name + ":" + age + ":" + country;
    }
}

public class ObjectStreamDemo {
    public static void readObj() throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\obj.txt"));

        Person p = (Person) ois.readObject();

        System.out.println(p);

        ois.close();
    }

    public static void writeObj() throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\obj.txt"));

        oos.writeObject(new Person("Lisi", 26, "cn"));

        oos.close();
    }

    public static void main(String[] args) throws Exception {
        writeObj();
        readObj();
    }
}

十五. 操作基本数据类型类

DataInputStream与DataOutputStream用于操作基本数据类型。

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class DataStreamDemo {

    /* 使用UTF-8编码读字节流 */
    public static void readUTFDemo () throws IOException {
        DataInputStream dis = new DataInputStream(new DataInputStream(new FileInputStream("d:\\utf.txt")));

        String s = dis.readUTF();

        System.out.println(s);

        dis.close();
    }

    /* 使用UTF-8编码写字节流 */
    public static void writeUTFDemo() throws IOException {
        DataOutputStream dos = new DataOutputStream(new DataOutputStream(new FileOutputStream("d:\\utf.txt")));

        dos.writeUTF("你好");

        dos.close();
    }

    /* 使用基本数据类型读字节流 */
    public static void readData() throws IOException {
        DataInputStream dis = new DataInputStream(new FileInputStream("d:\\data.txt"));

        int num = dis.readInt();
        boolean b = dis.readBoolean();
        double d = dis.readDouble();

        System.out.println("num = " + num);
        System.out.println("b = " + b);
        System.out.println("d = " + d);

        dis.close();
    }

    /* 使用基本数据类型写字节流 */
    public static void writeData() throws IOException {
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("D:\\data.txt"));

        dos.writeInt(234);
        dos.writeBoolean(true);
        dos.writeDouble(9887.543);

        dos.close();
    }

    public static void main(String[] args) throws IOException {
        readUTFDemo();
        writeUTFDemo();

        readData();
        writeData();
    }
}

十六.操作字节数组类

ByteArrayInputStream与ByteArrayOutputStream用于操作字节数组的流对象。

ByteArrayInputStream:在构造的时候,需要接收数据源,而且数据源是一个字节数组。

ByteArrayOutputStream:在构造的时候,不需要定义数据目的,因为该对象中已经内部封装了可变长度的字节数组,这就是数据的目的地。

因为这两个流对象都操作的是数组,并没有使用系统资源。所以,不用进行close关闭。

代码示例:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;


public class ByteArrayStream {
    public static void main(String[] args) {
        ByteArrayInputStream bis = new ByteArrayInputStream("ABCDE".getBytes());

        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        for (int by = 0; (by = bis.read()) != -1; ) {
            bos.write(by);
        }

        System.out.println(bos.size());
        System.out.println(bos.toString());
    }
}

十七.字符编码

字符流的出现是为了方便操作字符,更重要的是加入了编码转换。

字符编码的使用:通过子类转换流来完成。

InputStreamReader
OutputStreamWriter
在两个对象进行构造的时候可以加入字符集。

1 .编码表的由来

计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,并一一对应,形成一张表。这就是编码表。

2 .常见的编码表

ASCLL:美国标准信息交换码。用一个字节的7位表示。

ISO8859-1:拉丁码表,欧洲码表。用一个字节的8位表示。

GB2312:中国的中文编码表。

GBK:中国的中文编码表升级。融合了更多的中文文字符号。

Unicode:国际标准码。融合了多种文件。

UTF-8:最多用三个字节来表示一个字符。

3. 编解码应用

转换流的编解码应用:

可以将字符以指定编码格式存储,指定编码表的动作由构造函数完成。
可以对文本数据指定编码格式来解读。
注意:

如果编码失败,解码就没意义了。
如果编码成功,使用某解码表解出来的是乱码,则需对乱码用该解码表再次编码,然后再通过正确的编码表解码。适用于IOS8859-1。
如果用的是GBK编码,UTF-8解码,此时通过再次编码后解码的方式,就不能成功了。因为UTF-8解码的时候,会将对应的字节数改变。
字符串转成字节数组:

byte[] b = str.getBytes(charsetName);

字节数组转成字符串:

String str = new String(byte[],charsetName);
代码示例1:

import java.util.Arrays;

/*
 * 需求:编码后解码
 */
public class EncodeDemo {
    public static void main(String[] args) throws Exception {
        String s = "你好";

        // 字符串转成字节数组,用gbk编码
        byte[] b1 = s.getBytes("gbk");
        System.out.println(Arrays.toString(b1));

        // 字节数组转成字符串,用iso8859-1编码
        String s1 = new String(b1, "iso8859-1");
        System.out.println("s1 = " + s1);

        // 字符串转成字节数组,用iso8859-1解码
        byte[] b2 = s1.getBytes("iso8859-1");
        System.out.println(Arrays.toString(b2));

        // 字节数组转成字符串,用gbk解码
        String s2 = new String(b2, "gbk");
        System.out.println("s2 = " + s2);
    }
}

代码示例2:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;


public class EncodeStream {
    public static void main(String[] args) throws Exception {
        writeText();
        readText();
    }
    /* 未使用正确的编码进行解读将会读出乱码 */
    public static void readText() throws Exception {
        InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\utf.txt"));

        char[] buf = new char[10];
        int len = isr.read(buf);

        String str = new String(buf, 0, len);
        System.out.println(str);

        isr.close();
    }
    /* 对内容进行utf-8编码 */
    public static void writeText() throws Exception {
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\utf.txt"), "utf-8");

        osw.write("你好");

        osw.close();
    }
}

个人总结出的经验:对于IO流要多练,练的多了,不经大脑就会条件反射的知道该选择什么流。

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-

你可能感兴趣的:(java)