IO篇

一、IO概述

IO是InputOutput的缩写,IO流即输入输出流。
1、IO流:
    1) 用来处理设备之间的数据传输。
    2) Java 对数据的操作是通过IO流操作的。
    3) Java用于操作流的对象都在IO包中。
    4) 流只能操作数据。
    5) 流按操作数据分两种:字节流、字符流;流按流向分为:输入流、输出流。
Note:字符流融合了编码表,用于处理文字。字节流用于处理图片、文字,通用的是字节流。程序从输入流中读取数据,向输出流中写入数据。在数据类型上,read()方法在作提升,write()方法在做强转,以保证原数据的不变化。
2、IO 流常用类:
    字节流的抽象基类:InputStream、OutputStream
    字符流的抽象基类:Reader、Writer
    字符流:FileWriter、FileReader               (字符流缓冲区:BufferedWriter、BufferedReader)
    字节流:FileOutputStream、FileInputStream    (字节流缓冲区:BufferedOutputStream、BufferedInputStream)
Note:由这四类派生出来的子类名称都是以其父类名作为子类名的后缀。
    eg:InputStream 的子类 FileInputStream        Reader 的子类 FileReader

二、字符流
字符流是字节流根据指定编码表编码而得到的,所以字符流是在字节流之后出现的。
字符流有以下特点:
    1、字符流中的对象融合了编码表。默认的编码,即当前系统的编码。
    2、字符流只用于处理文字数据,而字节流可以处理媒体数据。
Writer:写入字符流的抽象类。
子类必须实现的方法仅有 writer()、flush()、close(),多数子类将重写方法,以提供更高的效率和其他功能。IO流是用于操作数据的,而数据的最常见体现形式是文件。查看API,找到一个专门用于操作文件的Writer子类:FileWriter。
1、字符流的读写
  a、字符流写入
步骤:

    1) 创建文件。创建一个 FileWriter 对象,该对象一初始化就必须要明确被操作的文件。而且该文件会被创建到指定的目录下。如果该目录下已有同名文件,已有文件将被覆盖。明确数据要存放的目的地(文件)。
      eg:FileWriter fw=new FileWriter(“demo.txt”);
    2) 调用 writer 方法,将字符串写入(内存)流中。
      eg:fw.writer(“abcfgggg”);
    3) 刷新流对象中的缓冲区数据。将数据刷新到目的地(文件) 。
      eg:fw .flush();
    4) 关闭流资源(必须有的步骤)。但关闭之前会刷新一次内部缓冲区中的数据,将数据刷新到目的地(文件)。
      eg:fw.close();
flush() 与 close() 的区别:
    flush() 刷新后,流一直存在,可以继续写入数据。
    close() 刷新后会将流关闭,不能再继续写入数据。关闭资源需要单独做 try 处理。
Note:
    1) 其实java自身不能写入数据,而是调用系统内部方式完成数据的书写,使用系统资源后,一定要关闭资源。
    2) 文件的数据的续写是通过构造函数 FileWriter(Strings,boolean append),在创建对象时,传递一个true参数,代表不覆盖已有的文件。并在已有文件的末尾处进行数据续写。(windows系统中的文件内换行用\r\n两个转义字符表示,在linux系统中只用\n表示换行)。
    3) 由于在创建对象时,需要指定创建文件位置,如果指定的位置不存在,就会发生IOException异常,所以在整个步骤中,需要对IO异常进行try处理。
示例如下:

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class FileWriterDemo {

	public static void main(String[] args) {
		// FileWriter(File file) 创建一个file关联的字符流对象,file对象随之创建
		Writer file = null;
		try {
			file = new FileWriter("fileWriter.txt");
			// 调用write方法,将字符串写入字符流中
			file.write("fileDemo");
			// 刷新字符流中的缓冲,将数据刷新到目的地文件中
			file.flush();
			// flush刷新后流资源还可以使用,close关闭之后流资源不能访问
		} catch (IOException e) {
			throw new RuntimeException("文件写入失败");
		} finally {
			// 关闭之前判空操作提高效率
			if (file != null) {
				try {
					file.close();
				} catch (IOException e) {
					throw new RuntimeException("文件关闭失败");
				}
			}
		}
	}
}
b、字符流读取
步骤:

    1) 创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件已经存在,若不存在,将会发生异常FileNotFoundException,记住要处理异常。
    2) 调用读取流对象的read()方法,一次读一个字符,并且会继续往下读。
      (第一种方式:读取单个字符。第二种方式:通过字符数组进行读取。)
    3) 读取后要调用close方法将流资源关闭。
示例如下:

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

public class FileReaderDemo {

	public static void main(String[] args) {
		readerMethod_1();
		readerMethod_2();
	}

	public static void readerMethod_2() {
		FileReader fr = null;
		try {
			fr = new FileReader("FileWriterDemo.java");
			// 定义一个字符数组用于存储读到的字符
			// read(char[] ch)返货的是读到的字符个数
			char[] buf = new char[1024];
			int num = 0;
			// int read(char[] buf) 返回本次读取到的字符个数
			while ((num = fr.read(buf)) != -1) {
				System.out.print(new String(buf, 0, num));
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (fr != null)
					fr.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public static void readerMethod_1() {
		FileReader fr = null;
		try {
			// 创建一个文件读取流对象,和指定文件名相关联
			// 要保证该文件是存在的,否则会报FileNoFoundException
			fr = new FileReader("FileWriterDemo.java");
			int ch = 0;
			// read()一次读取一个字符
			while ((ch = fr.read()) != -1) {
				System.out.print((char) ch);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (fr != null)
					fr.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
     当我们读取文件的时候,上述示例中的第二种方法是先创建了一个字符数组用于存储读取到的字符,然后利用read(buf)方法一次读取多个字符并返回字符个数,这样一来就比之前的read()方法,每次只读取一个字符来得要快,其中的字符数组buf就可以称为字符缓冲区。而Java中也提供了字符缓冲区对象。
2、字符流缓冲区
字符流的缓冲区提高了对数据的读写效率。
缓冲区对应类:BufferedWriter、BufferedReader。缓冲区要结合流才可以使用,在流的基础上对流的功能进行增强。缓冲区的出现是为了提高流的操作效率。因此,在创建缓冲区前,必须要先有流对象。
原理:将数组封装,变成对象再使用。
BufferedWriter的使用:
步骤:
    1) 创建一个字符写入流对象。
      eg:FileWriter   w = new FileWriter("buf.txt");
    2) 为了提高字符写入流效率,加入缓冲区技术。只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。
      eg:BufferedWriter   bfw = new BufferedWriter(w);
    3) 写入数据
      eg:bfw.write(“abc\r\nde”);
    4) 只要用到缓冲区,必须刷新。
      eg:bfw.flush();
    5) 关闭资源。缓冲区的关闭其实就是在关闭提高效率(缓冲区)的流对象。
      eg:bfw.close();
BufferedReader的使用:
    该缓冲区提供了一个一次读一行的方法readLine,方便于堆文本数据的获取,当返回null时表示读到文件末尾。readLine方法返回的时候,只返回回车符之前的数据内容。并不返回回车符。使用步骤:
    1) 创建一个读取流对象和文件相关联。
      eg: FileReader bfr=new FileReader(“buf.txt”);
    2) 为了提高效率,加入缓冲技术。将字符读取流对象作为参数传递给缓冲对象函数的构造函数。
      eg: BufferedReader bfr=new BufferedReader(bfr);
    3) 读取缓冲区
      eg: String   line=null;   //记录读取的数据
          while((line=bfr.readLine())!=null)   //按行读取文本内容
          {
               System.out.println(line);
          }
    4) 关闭缓冲区资源。
      eg:  bfr.close();
示例如下:

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

public class CopyTextByBuf {

    public static void main(String[] args) {
        // 定义文件读取流、输出流缓冲区
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            // 将缓冲区关联文件
            br = new BufferedReader(new FileReader("buf.txt"));
            bw = new BufferedWriter(new FileWriter("copyofbuf.txt"));

            // buf记录缓冲区读取到的字符数据
            String buf = null;
            while ((buf = br.readLine()) != null) {
                bw.write(buf);// 向缓冲区中写入数据
                bw.newLine();// 换行
                bw.flush();// 刷新缓冲区中的数据到目的文件中去
            }
        } catch (IOException e) {
            throw new RuntimeException("文件操作失败");
        } finally {// 最后关闭资源
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
LineNumberReader
LineNumberReader是 BufferedReader 的子类,跟踪行号缓冲字符输入流。
Note:默认情况下,行编号从 0 开始。该行号随数据读取在每个行结束符处递增,并可以通过调用 setLineNumber(int)更改行号,但并不会实际更改流中的当前位置;它只更改将由 getLineNumber()返回的值。
方法:
setLineNumber(int);    //设置当前行号,可以设置行号的起点
getLineNumber();    //获取当前行号
示例如下:

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

public class LineNumberReaderDemo {

    public static void main(String[] args) {

        // 定义字符读取流
        FileReader fr = null;
        LineNumberReader lnb = null;
        try {
            fr = new FileReader("FileWriterDemo.java");
            // 让LineNumberReader关联一个存在的字符读取流
            lnb = new LineNumberReader(fr);
            String line = null;
            // 设置读取初始行号为100
            lnb.setLineNumber(100);
            while ((line = lnb.readLine()) != null) {
                System.out.println(lnb.getLineNumber() + "   " + line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {// 关闭资源
            if (lnb != null) {
                try {
                    lnb.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
三、字节流
1、简述:
    1) 字节流和字符流的基本操作是相同的,但字节流还可以操作其他媒体文件。
     2) 由于媒体文件数据中都是以字节存储,所以字节流对象可直接对媒体文件的数据写入到文件中,可以不用再进行刷流动作。
     3) 读写字节流:InputStream  输入流(读);OutputStream  输出流(写)。
     4) 为何不用进行刷流动作:因为字节流操作的是字节,即数据的最小单位,不需要像字符流一样要进行转换为字节。所以可直接将字节数据写入到指定文件中。
     5) InputStream特有方法:int available();//返回文件中的字节个数
Note: 可以利用此方法来指定读取方式中传入数组的长度,从而省去循环判断。但是如果文件较大,而虚拟机启动分配的默认内存一般为64M。当文件过大时,此数组长度所占内存空间就会溢出。所以,此方法慎用,当文件不大时,可以使用。

示例如下:

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

public class OutputStreamDemo {

    public static void main(String[] args) {
        // fileOutputStream();
        // fileInputStream_1();
        // fileInputStream_2();
        fileInputStream_3();
    }

    // 逐个字节读取
    public static void fileInputStream_1() {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("fos.txt");
            int ch = 0;
            while ((ch = fis.read()) != -1) {
                System.out.print((char) ch);
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }
        }
    }

    // 借助缓冲数组buf,一次读取多个字节的形式完成文件读取操作
    public static void fileInputStream_2() {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("fos.txt");
            byte[] buf = new byte[1024];
            int len = 0;
            while ((len = fis.read(buf)) != -1) {
                System.out.println(new String(buf, 0, len));
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }
        }
    }

    // 借助特有方法avaliable()返回的文件大小建立一个字节数组,在完成读取操作
    public static void fileInputStream_3() {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("fos.txt");
            // 获取待读取文件的大小
            int numOfChar = fis.available();
            // 创建一个刚刚好的缓冲区
            byte[] buf = new byte[numOfChar];
            int len = 0;
            while ((len = fis.read(buf)) != -1) {
                System.out.println(new String(buf, 0, len));
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }
        }
    }
    // 字节写入操作演示
    public static void fileOutputStream() {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("fos.txt");
            fos.write("abcdef".getBytes());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }
        }
    }
}
2、字节流缓冲区
读写特点

    read():会将字节byte型值提升为int型值
    write():会将int型强转为byte型,即保留二进制数的最后八位。
原理:将数据拷贝一部分,读取一部分,循环,直到数据全部读取完毕。
    1)先从数据中抓取固定数组长度的字节,存入定义的数组中,再通过然后再通过read()方法读取数组中的元素,存入缓冲区。
    2)循环这个动作,直到最后取出一组数据存入数组,可能数组并未填满,同样也取出包含的元素。
    3)每次取出的时候,都有一个指针在移动,取到数组结尾就自动回到数组头部,这样指针在自增。
    4)取出的时候,数组中的元素在减少,取出一个,就减少一个,直到减到0即元素取完。
    5)当文件中的全部数据都被读取出时,read()方法就返回-1。
自定义读取字节流缓冲区
Note:

    1) 字节流的读一个字节的read方法为什么返回值类型不是byte,而是int。因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1.那么就会数据还没有读完,就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。所以,为了避免这种情况将读到的字节进行int类型的提升。并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。而在写入数据时,只写该int类型数据的最低8位。
    2) byte类型的-1提升为int类型时还是-1。原因:因为在bit8个1前面补的全是1导致的。如果在bit8个1前面补0,即可以保留原字节数据不变,又可以避免-1的出现。这时将byte型数据&0xff即255即可。
代码示例:

/*
 自定义字节流读取缓冲区
 思路:
 1、定义一个固定长度的数组
 2、定义一个指针和计数器用于读取数组长度,和计数数组元素是否取完为0
 3、每次将字节数据存入元素要先将数组中的元素取完
 */
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

class MyBufferedInputStream {
    private InputStream in;
    private byte[] by = new byte[1024];
    private int count = 0, pos = 0;

    MyBufferedInputStream(InputStream in) {
        this.in = in;
    }

    // 自定义读方法,一次读一个字节
    public int myRead() throws IOException {
        // 通过in对象读取硬盘上数据,并存储by中。
        // 存储在数组中的数据被读取完,再通过in对象从硬盘上读取数据
        if (count == 0) {
            count = in.read(by);
            if (count < 0)// 文件数据全部被读取出来了
                return -1;

            pos = 0;// 初始化指针
            byte b = by[pos];

            count--;// 每被读一个字节,表示数组中的字节数少一个
            pos++;// 指针加1
            return b & 255;// 返回的byte类型提升为int类型,字节数增加,且高24位被补1,原字节数据改变。
                            // 通过与上255,主动将byte类型提升为int类型,将高24位补0,原字节数据不变。
                            // 而在输出字节流写入数据时,只写该int类型数据的最低8位。
        } else if (count > 0)// 如果数组中的数据没被读取完,则继续读取
        {
            byte b = by[pos];

            count--;
            pos++;
            return b & 0xff;
        }
        return -1;
    }

    // 自定义关闭资源方法
    public void close() throws IOException {
        in.close();
    }
}

// 测试自定义输入字节流缓冲区
public class MyBufferedCopyMp3 {
    public static void main(String[] args) {
        // 利用字节流的缓冲区进行复制
        copy();
    }

    // 使用字节流的缓冲区进行复制
    public static void copy() {
        BufferedOutputStream bout = null;
        MyBufferedInputStream bin = null;
        try {
            // 关联复制文件输入流对象到缓冲区
            bin = new MyBufferedInputStream(new FileInputStream(
                    "E:\\BaiduMusic\\Songs\\My Love - Westlife.mp3"));
            // 指定文件粘贴位置的输出流对象到缓冲区
            bout = new BufferedOutputStream(new FileOutputStream(
                    "My Love - Westlife.mp3"));
            int by = 0;

            while ((by = bin.myRead()) != -1) {
                bout.write(by);// 将缓冲区中的数据写入指定文件中
            }
        } catch (IOException e) {
            throw new RuntimeException("MP3复制失败");
        } finally {
            try {
                if (bin != null)
                    bin.close();// 关闭输入字节流
            } catch (IOException e) {
                throw new RuntimeException("读取字节流关闭失败");
            }
            try {
                if (bout != null)
                    bout.close();// 关闭输出字节流
            } catch (IOException e) {
                throw new RuntimeException("写入字节流关闭失败");
            }
        }
    }
}
3、转换流
1) 由来:

    a、字符流与字节流之间的桥梁
    b、方便了字符流与字节流之间的操作
2) 应用:
字节流中的数据都是字符时,转成字符流操作更高效。
3)InputStreamReader:字节流通向字符流
    a、获取键盘录入对象。
      InputStream in=System.in;
    b、将字节流对象转成字符流对象,使用转换流。
      InputStreamReader isr=new InputStreamReader(in);
    c、为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
      BufferedReader br=new BufferedReader(isr);
      //键盘录入最常见写法
      BufferedReaderin=new BufferedReader(new InputStreamReader(System.in));
4) OutputStreamWriter:字符流通向字节流
    录入的是字符,存到硬盘上的是字节,步骤和InputStreamReader转换流基本一致。
4、键盘录入
System.out : 字节流,对应的是标准输出设备,控制台。
System.in  : 字节流,对应的是标准输入设备,键盘。
Note: 在 ASCII 码表中字符‘\r’对应数字 13 ,‘\n’对应数字10,停止键盘录入:Ctrl+C或者自定义结束标记,一般系统平台默认的编码表是 GBK。
System 类中的方法:
    static void setIn(InputStream in)      //重新分配“标准”输入流
     static void setOut(PrintStream out)    //重新分配“标准”输出流

示例如下:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;

public class ReaderInAndWriterOut {

	public static void main(String[] args) throws IOException {
		InputStreamMethod();
		changeMethod();
	}

	public static void changeMethod() throws IOException {

		// 改变标准输入输出流
		System.setIn(new FileInputStream("FileWriterDemo.java"));
		System.setOut(new PrintStream("char.txt"));

		// 获取键盘录入对象
		InputStream in = System.in;
		// 将字节流转换成字符流对象使用转换流InputStreamReader
		InputStreamReader isr = new InputStreamReader(in);
		// 为提高效率,将字符串进行缓冲区技术的操作使用BufferedReader
		BufferedReader br = new BufferedReader(isr);

		// 简化书写如下:
		BufferedReader bufferReader = new BufferedReader(new InputStreamReader(
				System.in));
		BufferedWriter bufferWriter = new BufferedWriter(
				new OutputStreamWriter(System.out));

		// 获取控制台输出 对象
		OutputStream out = System.out;
		OutputStreamWriter osw = new OutputStreamWriter(out);
		BufferedWriter bw = new BufferedWriter(osw);

		String line = null;
		while ((line = br.readLine()) != null) {
			if ("over".equals(line)) {
				break;
			}
			// System.out.println(line.toUpperCase());
			bw.write(line.toUpperCase());
			bw.newLine();
			bw.flush();
		}
		br.close();

	}

	// 自定义键盘录入方法
	public static void InputStreamMethod() throws IOException {
		InputStream in = System.in;
		StringBuilder sb = new StringBuilder();
		int ch = 0;
		while (true) {
			ch = in.read();
			if (ch == '\r') {
				continue;
			}
			if (ch == '\n') {
				String s = sb.toString();
				if (s.equals("over")) {
					break;
				}
				System.out.println(s.toUpperCase());
				// 将缓冲区清空
				sb.delete(0, sb.length());
			} else {
				sb.append((char) ch);
			}
		}
	}
}
5、流操作规律
IO流使用的时候最痛苦的就是流对象有很多,不知道该用哪一个,通过三个明确来完成。
1) 明确源和目的。
    源:输入流。InputStream  Reader
    目的:输出流。OutputStream  Writer。
2) 操作的数据是否是纯文本。
    是:字符流。
    不是:字节流。
3) 当体系明确后,在明确要使用哪个具体的对象。
    通过设备来进行区分:
    源设备:内存,硬盘。键盘
    目的设备:内存,硬盘,控制台。
例如:将一个文本文件中数据存储到另一个文件中。复制文件。
思路分析:
    
    1) 源:因为是源,所以使用读取流。InputStream Reader 
    是不是操作文本文件。 是!这时就可以选择Reader,这样体系就明确了。
    2) 接下来明确要使用该体系中的哪个对象。
    明确设备:硬盘。上一个文件。 Reader体系中可以操作文件的对象是 FileReader
    3) 是否需要提高效率:是!。加入Reader体系中缓冲区 BufferedReader.
    FileReader fr = new FileReader("a.txt");
    BufferedReader bufr = new BufferedReader(fr);
    4) 目的:OutputStream Writer,
    是否是纯文本。是!Writer。
    5) 设备:硬盘,一个文件。
    Writer体系中可以操作文件的对象FileWriter。
    6) 是否需要提高效率:是!。加入Writer体系中缓冲区 BufferedWriter
    FileWriter fw = new FileWriter("b.txt");
    BufferedWriter bufw = new BufferedWriter(fw);
示例如下:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyPic {

	public static void main(String[] args) {
		FileInputStream fis = null;
		FileOutputStream fos = null;
		try {
			fis = new FileInputStream(
					"C:\\Users\\caven\\Desktop\\HeiMa\\IMG_ME.jpg");
			fos = new FileOutputStream("ME1.jpg");
			byte[] buf = new byte[1024];
			int len = 0;
			while ((len = fis.read(buf)) != -1) {
				fos.write(buf, 0, len);
			}
		} catch (IOException e) {
			System.out.println(e.getMessage());
		} finally {
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e) {
					System.out.println(e.getMessage());
				}
			}
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					System.out.println(e.getMessage());
				}
			}
		}
	}
}
四、File类
1、File:
文件或者目录路径的抽象表示形式。用来将文件或者文件夹封装成对象,方便文件与文件夹的属性信息进行操作,File对象可以作为参数传递给流的构造函数。
成员变量:static String separator    //系统目录分隔符。与系统有关的默认名称分隔符,被表示为一个字符串。
构造函数:
    File(String pathname)    //参数是一个文件。 通过给定路径名  字符串转换为抽象路径名来创建一个新的 File 实例。
    File(String patent,Stirng child)   //参数左边是文件父目录,右边是子文件。根据父路径名字符串和子路径名字符串创建一个新 File 实例。
File对象创建:
    1)将 a.txt 封装成对象
      File f1=new File("F:\\abc\\a.txt");
    2)左边指父目录,右边指子文件.与 f1 作用一样。它们的区别是,右边的子文件是可以变化的,可以传参进去一个变量。
      File f2=new File("F:\\abc","a.txt");
    3)与上面 2 的作用一样。将文件的父目录封装成一个对象,然后作为参数传递
      File d=new File("F:\\abc");
      File f3=new File(d,"a.txt");
    4)实现跨平台:由于系统分隔符是分平台的,为了体现 java 的跨平台性,使用File.separator 作为系统的目录分隔符
      File f 4=new File("F:"+File.separator+"abc"+File.separator+"a.txt");
常用方法:
1) 创建
    boolean createNewFile()  //在指定位置创建文件。若已存在该文件,则返回 false。
                             //与输出流的区别:输出流对象一建立就创建文件,若已存在该文件,则会覆盖。
    createTempFile(String prefix,String suffix); 
    //创建临时文件,程序使用完会自动删除或者留存变成垃圾文件。一般临时文件时以.temp 为后缀的文件 
    createTempFile(String prefix,String suffix,File directory);  //指定文件路径,创建临时文件。
    boolean mkdir()    //创建指定目录(文件夹) ,只能创建一级目录
    boolean mkdirs()  //创建指定目录(文件夹) ,创建多级目录
2) 删除
    boolean delete()    //删除文件或者目录(文件夹) ,删除失败返回 false
    void deleteOnExit()  //在程序退出时删除文件或者目录(文件夹)
3) 判断
    boolean canExecute() //判断文件是否可执行
    boolean canRead()  //判断文件是否可读取
    boolean canWrite()  //判断文件是否可写入、修改
    int compareTo(File pathname)    //按字母顺序比较两个文件路径名
    boolean exists()    //判断文件或者目录(文件夹)是否存在
    boolean isDirectory()  //判断是否是目录
    boolean isFile()    //判断是否是文件
Note:判断文件对象是否是文件或者目录文件夹时,必须先进行判断该文件对象封装的内容是否存在。通过 exists 判断。
    boolean    isHidden()    //判断是否为隐藏文件
    boolean    isAbsolute()  //判断是否为绝对路径,文件不存在也可以进行判断
4) 获取
    String   getName()    //返回文件或者文件夹的名称
    String    getParent() 
    //获取父目录,没有指定父目录或者获取的是相对路径,则返回 null若相对路径中有上层目录,那么该目录就是返回结果。
    getPath()        //获取路径
    String  getAbsolutePath()    //获取绝对路径
    File    getAbsoluteFile()    //获取绝对路径,但是是将绝对路径封装成对象。
    long  lastModified()      //获取文件最后一次被修改的时间
    long  length()        //获取文件的大小
    renameTo()        //重命名
5) 其他方法
    static File listRoot()   //列出系统中有效盘符。操作共享数据,不操作具体的特有数据。无须创建对象
    String[] list()  
    //返回当前文件夹下的文件以及文件夹名称。以数组形式列出指定路径下的所有文件和文件夹(包含隐藏文件)
Note:
    
调用list方法的file对象必须是封装了一个目录,该目录还必须存在。当list所属对象是一个文件会发生空指针异常。
    String[] list(Filename filter)  //列出指定路径下的所有指定后缀的文件名
    //list 依据 FilenameFilter 中对象的 accept 方法的返回值来判定是否需要过滤文件。
    //若返回值为真,就是所有文件都未过滤,若返回值为假,则所有文件都被过滤掉。
    FilenameFilter 是一个接口,实现此接口的实例可用于过滤文件名。其中只有一个方法:
    boolean   accept(file dir ,String name )   //dir 表示被过滤的路径,那么表示被过滤掉的所有文件名
    File [] listFiles()    //返回当前文件的名称以及文件夹的对象
Note:可以通过方法获取文件名称、大小、路径等,开发中常用。
示例如下:

import java.io.File;
import java.io.IOException;

/*
 * File类:将文件或者文件夹封装成对象
 * File常见方法演示:
 */
public class FileDetials {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		// fileConstructMethod();
		// method_1();
		// method_2();
		// method_3();
		method_4();
	}

	public static void method_4() throws IOException {
		File f = new File("abc\\file.txt");
		System.out.println("createNewFile::" + f.createNewFile());
		System.out.println("path::" + f.getPath());
		System.out.println("AbsolutePath::" + f.getAbsolutePath());
		System.out.println("Parent::" + f.getParent());
		System.out.println("Modified::" + f.lastModified());
		System.out.println("length::" + f.length());
	}

	public static void method_3() throws IOException {
		File f = new File("file.txt");
		// 判断文件对象是否是文件或者文件目录之前,必须先判断文件对象是否存在exists()
		// f.createNewFile();
		// f.delete();
		// f.mkdir();
		System.out.println("isAbsolute::" + f.isAbsolute());
		System.out.println("isHidden::" + f.isHidden());
		System.out.println("file::" + f.isFile());
		System.out.println("dir::" + f.isDirectory());
	}

	public static void method_2() throws IOException {
		File f = new File("FileWriterDemo.java");
		// System.out.println("canExecute::" + f.canExecute());
		System.out.println("exsits::" + f.exists());

	}

	public static void method_1() throws IOException {
		File f = new File("file.txt");
		// 虚拟机退出时删除文件
		f.deleteOnExit();
		boolean flag1 = f.delete();
		System.out.println("delete::" + flag1);
		boolean flag = f.createNewFile();
		System.out.println("createNewFile::" + flag);

		File dir = new File("abc\\edf");
		System.out.println("mkdir::" + dir.mkdirs());
	}

	// 创建File对象
	public static void fileConstructMethod() {
		// 通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
		File f = new File("file.txt");
		// 创建指定父目录下的文件对象
		// File.separator跨平台的文件分割符
		File fileUnderParent = new File("D:" + File.separator + "JDoc",
				"file.txt");

		System.out.println(f);
		System.out.println(fileUnderParent);
	}

}
五、Properties类
Properties: 
是 Hashtable 的子类。具备了 map 集合的特点,所存储的键值对都是字符串。不需要泛型,是集合中与 IO 技术相结合的集合容器。
特点:可以用于键值对形式的配置文件。在加载数据时,需要数据有固定格式:键=值。
Note:  配置文件可以实现应用程序数据的共享。配置文件只有两种:properties 或者 xml。在配置属性文件中,所有前面带#的信息,都是注释信息,不会被 properties 加载。
方法:
    load();  //从流中加载键值对。注:字符流是从 JDK1.6 版本开始的。早期都是字节流
    list();  //将属性列表输出到指定的流中。列出集合目录
    stringPropertyNames()   //遍历集合,返回一个 set 集合。 (JDK1.6 版本)
    store()   //设置键值对,存到一个流中,并存到文件上。
              //参数:一个是流,另一个是注释信息(可不加注释。注释无法解析中文,只能写英文)
    setProperty()    //设置集合键值对,改变的只是内存中的结果
    getProperty(key)    //通过键获取值
示例如下:

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;

public class PropertiesDemo {

	public static void main(String[] args) throws IOException {
		// setAndGet();
		// info2Map();
		loadDemo();
	}
        // 加载设置文件中的信息并修改打印在控制台
	public static void loadDemo() throws IOException {
		Properties prop = new Properties();
		FileInputStream fis = new FileInputStream("info.txt");
		prop.load(fis);
		// System.out.println(prop);
		FileOutputStream fos = new FileOutputStream("info.txt");
		prop.store(fos, "ok");

		prop.setProperty("lisi", "38");
		prop.list(System.out);
	}

	/*
	 * 需求:将info.txt中的键值对数据存入到集合中 1、用一个流对象与文件info.txt关联 2、读取一行数据,用“=”进行切割
	 * 3、等号左边为键,右边为值,存入到Properties中
	 */
	public static void info2Map() throws IOException {
		BufferedReader br = new BufferedReader(new FileReader("info.txt"));
		String line = null;
		Properties prop = new Properties();
		while ((line = br.readLine()) != null) {
			String[] datas = line.split("=");
			// prop.put(datas[0],datas[1]);
			prop.setProperty(datas[0], datas[1]);
			// System.out.println(line);
		}
		br.close();
		System.out.println(prop);
	}

	// 设置和获取元素
	public static void setAndGet() {
		Properties prop = new Properties();
		prop.put("zhangsan", "12");
		prop.put("nihao", "33");
		System.out.println(prop);
		String age = prop.getProperty("nihao");
		System.out.println("age::" + age);

		Set<String> names = prop.stringPropertyNames();
		System.out.println(names);
		for (String name : names) {
			System.out.println(name + ".." + prop.getProperty(name));
		}
	}

}
五、其他IO类
打印流
:PrintWriter、PrintStream。可以直接操作输入流和文件。
序列流:SequenceInputStream。对多个流进行合并。
管道流:PipedInputStream、PipedOutputStream。管道输入流应该连接到管道输出流
操作对象:ObjectInputStream、ObjectOutputStream。被操作的对象需要实现 Serializable(标记接口);
1、字符打印流
PrintWriter:
是 OutputStream 的子类。经常使用!为其他输出流添加功能。使其能够方便打印各种数据值表示形式。永远不会抛出 IOException,异常情况仅设置可通过checkError 方法测试的内部标志。
    println()    //打印。可以对基本数据类型进行直接操作。保证数据原样性。
    write(int n)    //将指定的字节写入此流。打印的是 int 类型的最低八位。 
特点:该流提供了打印方法,可以将各种数据类型的数据都原样打印。
构造方法:可以接收的参数类型:file 对象、字符串路径、字节输出流(OutputStream) 、字符输出流(Writer)
2、字节打印流
PrintStream:
为其他输出流添加功能,使它们能够方便地打印数据值表示形式。
Note:
    与其他输出流不同,永远不会抛出 IOException ;而是异常情况仅设置可通过checkError。为了自动刷新,可以创建一个 PrintStream,这意味着可在写入byte数组之后自动调用flush()方法,可调用其中一个 println()方法,或写入一个换行符或字节('\n')。PrintStream  打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用PrintWriter  类。
构造方法:可以接收的参数类型:file 对象、字符串路径、字节输出流(OutputStream)
示例如下:

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

/*
 * 打印流:
 */
public class OtherClass {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter pr = new PrintWriter(new BufferedWriter(new FileWriter(
				"a.txt")), true);

		String line = null;
		while ((line = br.readLine()) != null) {
			if ("over".equals(line)) {
				break;
			}
			pr.println(line);
			// pr.flush();
		}
		pr.close();
		br.close();
	}

}
3、序列流
SequenceInputStream: 
 对多个流进行合并。表示其他输入流的逻辑串联。从输入流有序集合开始,并从第一个输入流开始读取,直到到达文件末尾;接着从第二个输入流读取,以此类推,直到到达最后一个输入流的文件末尾为止。
构造函数:
    SequenceInputStream(s1,s2)    //参数 s1,s2 为不同的输入流,按顺序读取。
    SequenceInputStream(Enumeration<? extends InputStream> e)
    //通过记住参数来初始化新创建的  SequenceInputStream,该参数必须是生成运行时类型为InputStream对象的Enumeration型参数(使用此构造要结合集合中枚举Vector)。
示例如下:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.Vector;

/*
 * 文件合并
 * SequenceInputStream
 * 1、SequenceInputStream(Enumeration en)
 * 2、SequenceInputStream(FileInputStream file1,FileInputStream file2)
 * 
 */
public class SequenceDemo {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		Vector<FileInputStream> v = new Vector<FileInputStream>();
		v.add(new FileInputStream("1.txt"));
		v.add(new FileInputStream("2.txt"));
		v.add(new FileInputStream("3.txt"));

		Enumeration<FileInputStream> en = v.elements();
		SequenceInputStream sis = new SequenceInputStream(en);

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

		byte[] buf = new byte[1024];
		int len = 0;
		while ((len = sis.read(buf)) != -1) {
			fos.write(buf, 0, len);
		}
		sis.close();
		fos.close();
	}

}
4、管道流
    管道输入流应该连接管道
输出流,管道输入流提供要写入管道输出流的所有数据字节。
通常数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的PipedOutputStream。不建议对这两个对象使用单个线程,可能会出现死锁问题。管道输入流包含一个
缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。若向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。
构造方法:PipedInputStream()   //创建尚未连接的  PipedInputStream。
方法:connect(PipedOutputStream src)    //使此管道输入流连接到管道输出流  src。
5、对象序列化流
将堆内存中的对象存入硬盘,保留对象中的数据,称之为对象的持久化(或序列化),ObjectOutputStream将java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。若流是网络套接字流,则可以再另一台主机上或另一个进程中重构对象。只能将支持 java.io.Serializable 接口的对象写入流中。每个 Serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包。构造函数可以接收字节流,方法中可以接收对象。
序列化步骤:
写入流对象:
    创建对象写入流,与文件关联,即传入目的
    通过写入writeObject(Object obj)方法,将对象作为参数传入,即可写入文件
读取流对象
    创建对象读取流,与文件关联,即传入源
    通过readObject()方法,读取文件中的对象,并返回这个对象
Note:
    静态成员不能被序列化
    非静态成员要不被序列化,可以用关键字transient修饰,保证非静态成员保存在堆内存中,不能存入文件中。
示例如下:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
//创建Peason类,实现Serializable接口
class Person implements Serializable {
    private static final long serialVersionUID = -4461523780114351798L;
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

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

    public Person() {
        super();
    }
}

public class ObjectStreamDemo {

    public static void main(String[] args) throws IOException,
            ClassNotFoundException {
        writeObject();
        readObject();
    }

    // 读取指定文件中的对象,也称反序列化
    public static void readObject() throws FileNotFoundException, IOException,
            ClassNotFoundException {
        File file = new File("object.txt");
        if (!file.exists()) {
            return;
        }
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        Person p = (Person) ois.readObject();
        System.out.println(p);
        ois.close();
    }

    // 将指定对象序列化到指定文件中
    public static void writeObject() throws IOException, FileNotFoundException {
        File file = new File("object.txt");
        if (!file.exists()) {
            file.createNewFile();
        }
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
                file));
        oos.writeObject(new Person("zhangsan", 313));
        oos.close();
    }
}

6、字节数组的流:
ByteArrayInputStream和ByteArrayOutputStream

这个对象并没有调用底层资源,所以不用关闭流资源,即使关闭后,仍可调用。 内部包含缓冲区,相当于以内存作为流操作源和目的,不会产生任何IO异常。对象中封装了数组,其实就是用流的思想操作数组。
构造函数:
        ByteArrayInputStream:在构造函数的时候,需要接受数据源,而且数据源是一个字节数据。
        ByteArrayOutputStream:在构造函数的时候,不用定义数据目的,因为该对象中已经在内部封装了可变长度的字节数组,这就是数据的目的地。
特有方法:
ByteArrayOutputStream中:
        writeTo(OutputStream out);   //将此 byte数组输出流的全部内容写入到指定的输出流参数中,这与使用out.write(buf, 0, count)调用该输出流的 write方法效果一样。因为这个方法用到了字节输出流,需要抛IO异常,也是字节数组流中唯一需要抛异常的方法。
        int size();   //当前缓冲区的大小
        String toString();   //使用平台默认的字符集,通过解码字节将缓冲区内容转换为字符串。
示例如下:

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class ByteArrayStreamDemo {

    @SuppressWarnings({ "unused", "resource" })
    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
        // 定义一个字节读取流---数据源
        ByteArrayInputStream bais = new ByteArrayInputStream(
                "ABCDEF".getBytes());
        BufferedInputStream bufIn = new BufferedInputStream(
                new FileInputStream("FileWriterDemo.java"));
        // 字节写入流---目的
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int len = 0;
        while ((len = bufIn.read()) != -1) {
            baos.write(len);
        }
        System.out.println(baos.size());
        System.out.println(baos.toString());
    }
}
7、字符编码
简述

1) 字符流的出现为了方便操作字符。
2) 更重要的是加入了编码的转换,即转换流。
3) 通过子类转换流来完成。在两个对象进行构造的时候,可以加入字符集(即编码表)。
可传入编码表的有:转换流 InuputStreamReader和OutputStreamWriter。
                  打印流:PrintStream和PrintWriter(只有输出流)
4) 编码表的由来
    计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。这就是编码表。
5) 常见的编码表:
    ASCII:美国标准信息交换码表。用一个字节的7位表示
    IOS8859-1:拉丁码表;欧洲码表。用一个字节的8位表示
    GB2312:中国的中文编码表(早期)
    GBK:中国的中文编码表升级,融合了更多的中文文字字符。打头的是两个高位为1的两个字节编码。为负数
    Unicode:国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode。
    UTF-8:最多用三个字节表示一个字符的编码表,根据字符所占内存空间不同,分别用一个、两个、三个字节来编码。

UTF-8编码格式:
    一个字节:0开头
    两个字节:字节一  ---> 110    位数:10 ~ 6
              字节二  ---> 10     位数:5 ~ 0
    三个字节:字节一  ---> 110    位数:15 ~ 12
              字节二  ---> 10     位数:11 ~ 6
              字节三 ---> 10      位数:5 ~ 0

转换流的编码应用:
    可以将字符以指定编码格式存储。可以对文本数据指定编码格式来解读。指定编码表的动作由构造函数完成。
编码和解码

1) 编码:字符串变成字节数组
    默认字符集: String  --->  byte[]  :srt.getBytes()
    指定字符集:String  --->  byte[]  :srt.getBytes(charsetName)
2) 解码:字节数组变成字符串
    默认字符集: byte[]  --->  String :new String(byte[])
    指定字符集: byte[]   --->  String :newString(byte[],charsetName)
对于编码和解码的字符集转换注意事项
    1) 如果编码失败,解码就没意义了。
    2) 如果编码成功,解码出来的是乱码,,则需对乱码通过再次编码(用解错码的编码表),然后再通过正确的编码表解码。针对于IOS8859-1是通用的。
    3) 如果用的是GBK编码,UTF-8解码,此时通过再次编码后解码的方式,就不能成功了,因为UTF-8也支持中文,在UTF-8解的时候,会将对应的字节数改变,所以不会成功。
    如使用了错误的解码表:

IO篇_第1张图片

    4) 特别注意:对于中文的”联通“,这两个字比较特别,它的二进制位正好是和在UTF-8中两个字节打头的相同,所以在文本文件中,如果单独写“联通”或者和满足UTF-8编码格式的字符一起保存时,记事本就会用UTF-8来进行解码动作,这样显示的就会是乱码。

    以上所述仅代表个人看法,如有出入请谅解。


你可能感兴趣的:(java,IO)