IO流

Posted xly1029

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IO流相关的知识,希望对你有一定的参考价值。

IO流

1 流的概念

在计算机中,流是个抽象的概念,是对输入输出设备的抽象。

在Java程序中,对于数据的输入/输出操作,都是以"流"的方式进行

数据以二进制的形式在程序和设备之间流动传输,就像水在管道里流动一样,所以把这种数据传输的方式称为流

1.1 流具有方向性

分为输入和输出

以java程序本身作为参照点,如果数据是从程序“流向”文件,那么这个流就是输出流,如果数据是从文
件“流向”程序,那么这个流就是输入流。

技术图片
技术图片

2 流的分类

  • 根据数据的流向:

    • 输入流 :把数据从其他设备上读取到程序中的流
    • 输出流 :把数据从程序中写出到其他设备上的流
  • 根据数据的类型:

    • 字节流 : 以字节为单位(byte),读写数据的流

      字节流分为字节输入流和字节输出流

    • 字符流 : 以字符为单位(char),读写数据的流

      字符流分为字符输入流和字符输出流

3 流的结构

技术图片

在Java中,和IO流相关的类,主要是在java.io 包下的定义的

IO流有四个最基础的抽象父类

  • InputStream : 字节输入流
  • OutputStream : 字节输出流
  • Reader : 字符输入流
  • Writer : 字符输出流

技术图片

3.1字节流和字符流的区别

  • 字节流:字节流可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的
  • 字符流: 字符流只能操作纯字符数据,比较方便。

由于IO流的子类太多,所以就只挑几个比较重要的子类展开讲

4 字节流

java.io.InputStream 是所有字节输入流的抽象父类型:

4.1 概述

字节流默认是不使用缓冲区进行读写数据的(包装类会使用)

字节流有两个父类

  • InputStream
  • OutputStream

4.1.1 常用方法

InputStream

InputStream中最核心的三个read方法

//每次读一个字节,返回值是本次读取的字节值
public abstract int read() throws IOException;

//每次读多个字节,并存放到指定的字节数组中,返回值是本次一共读取了多个字节(字节数)
public int read(byte b[]) throws IOException {
	return read(b, 0, b.length);
}

//每次读多个字节,并存放到指定的字节数组中,返回值是本次一共读取了多个字节(字节数)
//同时可以指定从数组的什么位置开始存放,以及在数组中最多存放多个字节
public int read(byte b[], int off, int len) throws IOException {
	if (b == null) {
		throw new NullPointerException();
	} else if (off < 0 || len < 0 || len > b.length - off) {
		throw new IndexOutOfBoundsException();
	} else if (len == 0) {
		return 0;
	}
	int c = read();
	if (c == -1) {
		return -1;
	}
	b[off] = (byte)c;
	int i = 1;
	try {
		for (; i < len ; i++) {
			c = read();
			if (c == -1) {
				break;
			}
			b[off + i] = (byte)c;
		}
	} catch (IOException ee) {
        ...
	}
	return i;
}

在使用read方法的时候, read(byte b[]) 和 read() 的方式用的较多,前者在读取效率上更高

OutputStream

OutputStream 中最核心的三个write方法:

//写出去一个字节值
public abstract void write(int b) throws IOException;

//把一个自己数组中的值全部写出去
public void write(byte b[]) throws IOException {
	write(b, 0, b.length);
}
//写出字节数组,指定开始位置,以及写出的字节数量
public void write(byte b[], int off, int len) throws IOException {
	if (b == null) {
		throw new NullPointerException();
		} else if ((off < 0) || (off > b.length) || (len < 0) ||
					((off + len) > b.length) || ((off + len) < 0)) {
			throw new IndexOutOfBoundsException();
		} else if (len == 0) {
		return;
	}
	for (int i = 0 ; i < len ; i++) {
		write(b[off + i]);
	}
}

使用write方法的时候, write(byte b[], int off, int len) 和 write(int b) 的方式用的较多,前者在读取效率上更高

4.1.2 控制台

使用字节流,从控制台读取数据,以及向控制台中写数据。

java.lang.System

//The "standard" input stream.
public final static InputStream in = null;

//The "standard" output stream.
public final static PrintStream out = null;

PrintStream 是 OutputStream 的子类

4.2 处理二进制数据

字节流通常是用来处理二进制数据,比如图片,歌曲等

public class CopyTest {

    public static void main(String[] args) throws Exception {

        //demo1();
        //复制歌曲
        InputStream is = new FileInputStream("E:\\Idea\\ばんばんしー - 秋姉妹のなく頃に in the autumn sky.mp3");
        OutputStream os = new FileOutputStream("E:\\Idea\\a.mp3");

        int len = 0;
        byte[] arr = new byte[1024*5];
        while ((len = is.read(arr)) != -1) {
            os.write(arr, 0, len);
        }

        os.close();
        is.close();

        return;

    }

    private static void demo1() throws IOException {

        //复制图片
        FileInputStream fis = new FileInputStream("E:\\Idea\\test.png");//(file, true) 表示追加, 默认是覆盖
        FileOutputStream fos = new FileOutputStream("E:\\Idea\\a.png");

        byte[] arr = new byte[1024];
        int len = 0;
        while ((len = fis.read(arr)) != -1) {
            fos.write(arr, 0, len);
        }
        System.out.println("---------");
        fos.close();
        fis.close();
    }

}

5 字符流

当使用字节流读取文本文件时,读出来的数据是一个个字节,不方便查看和操作,

并且一个字符可能会由多个字节组成。例如,中文字符

5.1 概述

当我们读文件时需要一个一个字符读取而不是一个一个字节读取时就需要用到字符流

字符流有两个父类

  • Reader
  • Writer

5.1.1 常用方法

Reader

Reader 中最核心的三个read方法:

//每次读取一个字符,返回这个字符的编码值
public int read() throws IOException {
	char cb[] = new char[1];
	if (read(cb, 0, 1) == -1)
		return -1;
	else
		return cb[0];
	}

//每次读取多个字符,并存放到指定字符数组中,返回值是本次读取到的字符个数
public int read(char cbuf[]) throws IOException {
	return read(cbuf, 0, cbuf.length);
}

//每次读取多个字符,并存放到指定字符数组中,返回值是本次读取到的字符个数
//同时可以指定从数组的什么位置开始存放,以及在数组中最多存放多个字符
abstract public int read(char cbuf[], int off, int len) throws IOException;

通常使用read() 和 read(char cbuf[]) 较多,但是一般使用字符流更多的是readline

Writer

Writer 中最核心的三个read方法(前三个):

//写出去一个字符,注意字符可以使用int值来表示
public void write(int c) throws IOException {
	synchronized (lock) {
		if (writeBuffer == null){
			writeBuffer = new char[WRITE_BUFFER_SIZE];
		}
		writeBuffer[0] = (char) c;
		write(writeBuffer, 0, 1);
	}
}

//写出去数组中的多个字符
public void write(char cbuf[]) throws IOException {
	write(cbuf, 0, cbuf.length);
}

//写出去数组中的多个字符
//可以指定从数组的什么位置开始写,以及多少个字符
abstract public void write(char cbuf[], int off, int len) throws IOException;

//写出去一个字符串
public void write(String str) throws IOException {
	write(str, 0, str.length());
}

//写出去一个字符串
//可以指定从字符串的什么位置开始写,以及多少个字符
public void write(String str, int off, int len) throws IOException {
	synchronized (lock) {
		char cbuf[];
		if (len <= WRITE_BUFFER_SIZE) {
			if (writeBuffer == null) {
				writeBuffer = new char[WRITE_BUFFER_SIZE];
			}
			cbuf = writeBuffer;
		} else {
			cbuf = new char[len];
		}
		str.getChars(off, (off + len), cbuf, 0);
		write(cbuf, 0, len);
	}
}

通常使用较多的是write(char cbuf[], int off, int len) 和 write(String str)

6 缓冲流

缓冲流:在创建流对象时,设置一个默认大小的缓冲区数组,通过缓冲区进行读写,减少系统磁盘的IO次数,从而提高读写的效率

缓冲流是一个包装类,是为了增强字节流和字符流的读写速度,提高效率

使用方式

  •   BufferedReader br = new BufferedReader(new FileReader("E:\\c.txt"));
    
    • BufferedReader中有一个readLine方法用的特别多 : 读取一整行
  •   BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\c.txt"));
    
    • BufferedWrriter中相对的有一个newLine方法 : 换行
  •   BufferedInputStream bi = new BufferedInputStream(new FileInputStream("E:\\c.txt"));
    
  •   BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream("E:\\c.txt"));
    

7 File

File中常用的构造器:

	File(String pathname):根据一个路径得到File对象,注意分隔符F:\\java

    File(String parent, String child):根据一个目录和一个子文件/目录得到File对象

    File(File parent, String child):根据一个父File对象和一个子文件/目录得到File对象

使用File既可以表示一个文件,也可以表示一个目录。

文件字节流:

FileInputStream ==> 从文件中读取数据(byte)

FileOutputStream ==> 把数据写入到文件中(byte)

文件字符流

FileReader ==> 从文件中读取数据(char)

FileWriter ==> 把数据写入到文件中(char)

8 Test

T1 reverse

将一个文本文档上的文本反转,第一行和倒数第一行交换,第二行和倒数第二行交换

a.txt

你好
我好
大家好
hello
world
/***
 * 将一个文本文档上的文本反转,第一行和倒数第一行交换,第二行和倒数第二行交换
 */
public class Test3_Reversal {

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

        //先把数据读取完再开始写入
        BufferedReader br = new BufferedReader(new FileReader("a.txt"));
        ArrayList<String> list = new ArrayList<>();
        String line;

        while ((line = br.readLine()) != null) {
            list.add(line);
        }
        br.close();

        //Collections list集合工具类
        //Arrays 数组工具类
        //逆序排列
        Collections.reverse(list);

        //打开一个输出流,会立马清空源文件的数据
        BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt"));
        for (String s : list) {
            bw.write(s);
            bw.newLine();
        }

        bw.close();


    }

}

结果:
	world
    hello
    大家好
    我好
    你好

T2 判断年份

读入 c.txt 文件的基础上,需要用户从键盘输入一个年份,系统会自动输出对应年份的世界杯冠军,

如果该年没有举办世界杯,则输出“没有举办世界杯”

c.txt

1986/阿根廷
1990/西德
1994/巴西
1998/法国
2002/巴西
2006/意大利
2010/西班牙
2014/德国
/***
 * 读入 c.txt 文件的基础上,需要用户从键盘输入一个年份,系统会自动输出对应年份的世界杯冠军,
 * 如果该年没有举办世界杯,则输出“没有举办世界杯”
 */
public class TestWork1 {

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

        Map<String, String> hm = new HashMap();
        BufferedReader br = new BufferedReader(new FileReader("E:\\Idea\\JavaSE\\src\\cn\\com\\Demo\\IO\\TestWork\\c.txt"));

        String line;

        while ((line = br.readLine()) != null) {
            String[] arr = line.split("/");
            hm.put(arr[0], arr[1]);
        }
        /*System.out.println();
        for (String s : hm.keySet()) {
            System.out.println(s + ": " + hm.get(s));
        }*/

        Scanner sc = new Scanner(System.in);
        System.out.println("输入:");
        String str = sc.nextLine();

        if (hm.containsKey(str)) {
            System.out.println(hm.get(str));
        }else {
            System.out.println("没有举办世界杯");
        }
        br.close();
    }
}

结果:
    输入:
    1996
    没有举办世界杯
        
    输入:
    1990
    西德 

T3 字符串去除重复

请编写程序读取该文件内容,要求去掉重复字母(区分大小写字母

并按照自然排序顺序后输出到 b.txt 文件中。

即 b.txt 文件内容应为“123...ABC...abc...”这样的顺序输出。

a.txt

AAdsddljas2342dsf23
/***
 *  请编写程序读取该文件内容,要求去掉重复字母(区分大小写字母)
 * 	并按照自然排序顺序后输出到 b.txt 文件中。
 * 	即 b.txt 文件内容应为“123...ABC...abc...”这样的顺序输出。
 */
public class TestWork2 {

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

        BufferedReader br = new BufferedReader(new FileReader("E:\\a.txt"));
        TreeSet<Character> ts = new TreeSet<>();

        String line;
        line = br.readLine();
        //把读取到的字符串转化成字符数组,通过对其遍历把字符加入TreeSet
        char[] arr = line.toCharArray();
        for (char c : arr) {
            ts.add(c);
        }

        BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\b.txt"));
        //TreeSet集合循环遍历时会默认使用中序遍历
        for (Character t : ts) {
            bw.write(t);
        }


        bw.close();
        br.close();
    }

}

b.txt

234Aadfjls

T4 将对象从集合写入文件

把ArrayList集合中的学生数据存储到文本文件

每一个学生数据作为文件中的一行数据

/***
 * 把ArrayList集合中的学生数据存储到文本文件
 *     每一个学生数据作为文件中的一行数据
 */
public class TestWork3 {

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

        List<Student> list = new ArrayList<>();
        list.add(new Student("it01", "001", 21, "aaa"));
        list.add(new Student("it02", "002", 23, "bbb"));
        list.add(new Student("it03", "003", 25, "ccc"));
        list.add(new Student("it04", "004", 19, "ddd"));
        //System.out.println(list);

        //对集合遍历,用字符串接收集合里的对象放进文本
        BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\d.txt"));
        for (Student s : list) {
            String str = s.getId() + ","+ s.getName() + "," + s.getAge() + "," + s.getAddress();
            bw.write(str);
            bw.newLine();

        }
        bw.close();

    }

}

d.txt

it01,001,21,aaa
it02,002,23,bbb
it03,003,25,ccc
it04,004,19,ddd

T5 将对象从文件中读到集合

将d.txt文件中内容读取出来,每行解析成一个学生对象,最后放入到ArrayList里面

/***
 * 将d.txt文件中内容读取出来,每行解析成一个学生对象,最后放入到ArrayList里面
 */
public class TestWork4 {

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

        BufferedReader br = new BufferedReader(new FileReader("E:\\d.txt"));
        List<Student> list = new ArrayList<>();

        String line;
        while ((line = br.readLine()) != null) {

            String[] str = line.split(",");
            list.add(new Student(str[0], str[1], Integer.parseInt(str[2]), str[3]));
            //System.out.println(str);

        }
        for (Student s : list) {
            System.out.println(s);
        }

        br.close();
    }

}

结果:
    Student{id=‘it01‘, name=‘001‘, age=21, address=‘aaa‘}
    Student{id=‘it02‘, name=‘002‘, age=23, address=‘bbb‘}
    Student{id=‘it03‘, name=‘003‘, age=25, address=‘ccc‘}
    Student{id=‘it04‘, name=‘004‘, age=19, address=‘ddd‘}

T6 字符串出现次数

获取文件内 字符串 出现的次数

e.txt

aaahellobbbcdcdcdhello aaahellobbbcdcdcdhello aaahellobbbcdcdcdhello
aaahellobbbcdcdcdhello aaahellobbbcdcdcdhello aaahellobbbcdcdcdhello
aaahellobbbcdcdcdhello aaahellobbbcdcdcdhello aaahellobbbcdcdcdhello
/**
 * 获取文件内 字符串 出现的次数
 */
public class TestWork6 {

    public static void main(String[] args) throws Exception {

        BufferedReader br = new BufferedReader(new FileReader("E:\\e.txt"));

        String line;
        int flag = 0;
        //从后向前
        while ((line = br.readLine()) != null) {
            int index = line.length();
            while (true) {
                //subString(int beginIndex, int endIndex)方法 左闭右开 [beginIndex, endIndex)
                if ("hello".equals(line.substring(index-5, index))) {
                    //System.out.println(line.substring(index - 5, index));
                    //System.out.println("index: " + index);
                    index -= 5;
                    flag++;
                }else
                    index -= 1;

                if (index <= 5) {
                    break;
                }
            }
        }

        //从前向后遍历
        /*while ((line = br.readLine()) != null) {
            int index = 0;
            while (true) {

                if ("hello".equals(line.substring(index, index+5))) {
                    index += 5;
                    flag++;
                }else
                    index += 1;
                if (index >= line.length() - 4) {
                    break;
                }
            }
        }*/
        System.out.println("index: " + flag);

        //System.out.println("hello".substring(0, 5));
        br.close();
        //System.out.println("ahello".substring(1, 5));
    }

}

结果:
	index: 18

T7 复制文件夹

复制任意文件夹下面所有文件和子文件夹内容到e:/demojava。

提示:涉及单个文件复制、目录的创建、递归的使用

/***
 * 复制任意文件夹下面所有文件和子文件夹内容到e:/demojava。
 * 提示:涉及单个文件复制、目录的创建、递归的使用
 */
public class TestWork5 {

    public static void main(String[] args) throws IOException {
        copyDirectory("E:\\Idea\\Hadoop_HD123", "D:\\test");
    }

    //复制目录的方法
    public static void copyDirectory( String sourceDirectory, String targetDirectory) throws IOException {
        //创建文件对象
        File sourceFile = new File(sourceDirectory);
        File targetFile = new File(targetDirectory);
        //判断源目录
        if(!sourceFile.exists() || !sourceFile.isDirectory()) {
            System.out.println("源目录不存在!!");
            return ;
        }

        //判断目标目录,若不存在,则手动创建
        if(!targetFile.exists()) {
            targetFile.mkdirs();
        }

        //获取源目录的所有文件和目录
        File[] sourceList = sourceFile.listFiles();
        System.out.println(sourceList.length);
        //遍历源目录
        for (File f : sourceList) {
            if(f.isFile()) {
                //源文件
                File sourceF = f;
                //目标文件 父路径 + 子路径
                File targetF = new File(targetFile,f.getName());
                //调用复制文件的方法
                copyFiles(sourceF, targetF);
            }
            //若为目录
            if(f.isDirectory()) {
                //源目录
                String sourceD = sourceDirectory + File.separator + f.getName();
                System.out.println("sourceD: " + sourceD);
                //目标目录
                String targetD = targetDirectory + File.separator + f.getName();
                System.out.println("targetD: " + targetD);
                //递归调用复制目录的方法
                copyDirectory(sourceD, targetD);
            }
        }
    }

    //复制文件的方法
    public static void copyFiles(File sourceFile, File targetFile) throws IOException {

        BufferedReader br = new BufferedReader(new FileReader(sourceFile));
        BufferedWriter bw = new BufferedWriter(new FileWriter(targetFile));

        //用字节流可能会出现数组越界的情况
        String line;
        while ((line = br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
        }
        bw.flush();
        br.close();
        bw.close();
    }

}



以上是关于IO流的主要内容,如果未能解决你的问题,请参考以下文章

在 .net 中处理 pcm wav 流

此应用小部件片段中所有意图 (PendingIntents) 的逻辑流

JavaSE 一些技巧 04——IO流各种流常用代码整理

猜数小游戏升级版(IO流实现,对IO流进行加强理解运用)

利用IO(文件字符流)打印代码本身

JAVA IO流相关代码(Serializable接口,管道流PipedInputStream类,RandomAccessFile类相关代码)