简述:
IO(Input Output)
- IO流用于处理设备之间的数据传输。(比如硬盘上的文件,内存中驻留的数据)
- IO流是用于操作数据的,数据的最常见体现形式是:文件。
- Java对数据的数据的操作是通过流的方式。
- Java用于操作流的对象都在IO包中。
- 流按操作数据分为两种:字节流和字符流。
- 流按流向分为:输入流和输出流。
注意:流的操作只有两种:读和写。
IO流常用基类
- 字节流的抽象基类:
- InputStream,OutputStream。
- 字符流的抽象基类:
- Reader,Writer。
- 注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
- 如:InputStream的子类FileInputStream。
- 如:Reader的子类FileReader。
编码表:
计算机智能识别二进制数据,早期的由来是电信号。:为了方便应用计算机,让他可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表,这就是编码表。
- 常见编码表:
- ASCII:美国标准信息交换码。用一个字节的7位可以表示。
- ISO8859-1:拉丁码表。欧洲码表。用一个字节的8位来表示。
- GB2312:中国的中文编码表。
- GBK:中国的中文编码表升级,融合了更多的中文文字符号。
- Unicode:国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语音使用的就是Unicode
- UTF-8:最多用3个字节来表示一个字节。
UTF-8编码格式:
一个字节: 0开头
两个字节:字节一 ---> 110 位数: 10 ~ 6
字节二 ---> 10 位数: 5 ~ 0
三个字节:字节一 ---> 110 位数: 15 ~ 12
字节二 ---> 10 位数: 11 ~ 6
字节三 ---> 10 位数: 5 ~ 0
第一讲 字符流
一、简述
- 字节流:
- 处理字节数据的流对象。设备上的数据无论是图片或者dvd,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。
- 为什么单独分离字符流:
- 因为字符每个国家都不一样,所以涉及到了字符编码问题,那么GBK编码的中文用unicode编码解析是有问题的,所以需要获取中文字节数据的同时+ 指定的编码表才可以解析正确数据。为了方便于文字的解析,所以将字节流和编码表封装成对象,这个对象就是字符流。只要操作字符数据,优先考虑使用。
- 字符流出现的好处:
- 可以再内部融合编码表。读取的字节数据到底查哪个码表,可以由自己来制定。这样在处理文字的时候会变的很方便。
- 简单的说:字符流的对象当中融合了编码表。
- 字符流体系:
- Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。
- BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
- LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。
- InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
- FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。
- CharArrayReader:
- StringReader:
- BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
- Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。
- BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
- OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
- FileWriter:用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要自己指定这些值,可以先在 FileOutputStream 上构造一个 OutputStreamWriter。
- PrintWriter:
- CharArrayWriter:
- StringWriter:
二、FlieWriter和FileReader
- FileWriter,专门用于操作文件的Writer子类对象。后缀名是父类名,前缀名是该对象的功能。
- 作用:用于字符流的写入。
- FileWriter 没有空参数构造函数
- 因为:想要操作文件并向文件中写入数据,必须要先有文件。该流对象一被初始化,就应该有被操作的文件存在。这是初始化动作。
- 创建对象:
- FileWriter fw = new FileWriter("demo.txt");
- 该对象一建立,就必须明确数据存储位置,是一个文件。
- 对象产生后,会在堆内存中有一个实体,同时也调用了系统底层资源,在指定的位置创建了一个存储数据的文件。
- 如果指定位置,出现了同名文件,文件会被覆盖。
- 常用方法:
- void write(); 写入流数据。write方法写入字符串。字符串并未直接写入到目的地中,而是写入到了流中,(其实是写入到内存缓冲区中)。
- void flush(); 刷新缓冲区,将缓冲区中的数据刷到目的地文件中。
- void close(); 关闭流资源。其实关闭的就是java调用的系统底层资源。在关闭前,会先刷新该流。
- close()和flush()的区别:
- flush():将缓冲区的数据刷到目的地中后,流可以使用。
- close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动作一定做。
- FileReader,专门用于操作文件的Reader子类对象。后缀名是父类名,前缀名是该对象的功能。
- 作用:用于字符流的读取。
- 创建对象:
- FileReader fr = new FileReader("demo.txt");
- 创建可以读取文本文件的流对象,FileReader让创建好的流对象和指定的文件相关联。
- FileReader 同样没有空参数构造函数。
- 常用方法:
- int read(); 读取的字符数,一个字符一个字符读取。如果已到达流的末尾,则返回 -1 。
- void close(); 关闭流资源。
三、IO程序的书写
- 导入IO包中的类
- 进行IO异常处理
- 在finally中对流进行关闭
- 写入字符流字符流步骤
- 创建FileWriter对象,该对象一被初始化就必须要明确被操作的文件。
- FileWriter fw = new FileWriter("demo.txt");
- 调用write方法,将字符串写入到流中。
- fw.write("abcde");
- 刷新流对象中的缓冲中的数据,将数据刷到目的地当中。
- fw.flush();
- 关闭流资源,关闭之前会刷新一次内部的缓冲中的数据。将数据刷到目的地中。
- fw.close();
- 创建FileWriter对象,该对象一被初始化就必须要明确被操作的文件。
示例:
- import java.io.*;
- class FileWriterDemo
- {
- public static void main(String[] args) throws IOException { //读、写都会发生IO异常
- /*
- 1:创建一个字符输出流对象,用于操作文件。该对象一建立,就必须明确数据存储位置,是一个文件。
- 2:对象产生后,会在堆内存中有一个实体,同时也调用了系统底层资源,在指定的位置创建了一个存储数据的文件。
- 3:如果指定位置,出现了同名文件,文件会被覆盖。
- */
- FileWriter fw = new FileWriter("demo.txt"); // FileNotFoundException
- /*
- 调用Writer类中的write方法写入字符串。字符串并未直接写入到目的地中,而是写入到了流中,(其实是写入到内存缓冲区中)。怎么把数据弄到文件中?
- */
- fw.write("abcde");
- fw.flush(); // 刷新缓冲区,将缓冲区中的数据刷到目的地文件中。
- fw.close(); // 关闭流,其实关闭的就是java调用的系统底层资源。在关闭前,会先刷新该流。
- }
- }
- FileWriter写入数据的细节:
- window中的换行符:\r\n两个符号组成。 linux:\n。
- 续写数据,只要在构造函数中传入新的参数true。
- 目录分割符:window \\
- import java.io.*;
- class FileWriterDemo
- {
- public static void main(String[] args){
- FileWriter fw = null;
- try {
- fw = new FileWriter("demo.txt",true);
- fw.write("abcde");
- }
- catch (IOException e ){
- System.out.println(e.toString()+"....");
- }
- finally{
- if(fw!=null)
- try{
- fw.close();
- }
- catch (IOException e){
- System.out.println("close:"+e.toString());
- }
- }
- }
- }
- 读取字符流步骤
- 创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件已经存在,若不存在,将会发生异常FileNotFoundException。
- 调用读取流对象的read()方法。read():一次读一个字符,且会继续往下读。第一种方式:读取单个字符。第二种方式:通过字符数组进行读取。
- 读取后要调用close方法将流资源关闭。
- FileReader:使用Reader体系,读取一个文本文件中的数据。返回 -1 ,标志读到结尾。
- import java.io.*;
- class FileReaderDemo {
- public static void main(String[] args) throws IOException {
- /*
- 创建可以读取文本文件的流对象,FileReader让创建好的流对象和指定的文件相关联。
- */
- FileReader fr = new FileReader("demo.txt");
- int ch = 0;
- while((ch = fr.read())!= -1) { //条件是没有读到结尾
- System.out.println((char)ch); //调用读取流的read方法,读取一个字符。
- }
- fr.close();
- }
- }
- 读取数据的第二种方式:第二种方式较为高效,自定义缓冲区。
- import java.io.*;
- class FileReaderDemo2 {
- public static void main(String[] args) throws IOException {
- FileReader fr = new FileReader("demo.txt"); //创建读取流对象和指定文件关联。
- //因为要使用read(char[])方法,将读取到字符存入数组。所以要创建一个字符数组,一般数组的长度都是1024的整数倍。
- char[] buf = new char[1024];
- int len = 0;
- while(( len=fr.read(buf)) != -1) {
- System.out.println(new String(buf,0,len));
- }
- fr.close();
- }
- }
练习: 将C盘一个文本文件复制到D盘。
- import java.io.*;
- class CopyTest
- {
- /*
- 复制原理:
- 其实就是将C盘下的文件数据存储到D盘的一个文件中。
- 步骤:
- 1,在D盘创建一个文件,用于存储C盘文件中的数据。
- 2,定义读取流和C盘的文件关联。
- 3,通过不断的读写完成数据存储。
- 4,关闭资源。
- */
- public static void main(String[] args) throws IOException
- {
- copy_1();
- copy_2();
- }
- public static void copy_2()
- {
- FileWriter fw = null;
- FileReader fr = null;
- try
- {
- fw = new FileWriter("c:\\info_copy2.txt");
- fr = new FileReader("d:\\info.txt");
- char[] buf = new char[1024];
- int len = 0;
- while ((len = fr.read(buf))!=-1)
- {
- fw.write(buf,0,len);
- }
- }
- catch (IOException e )
- {
- throw new RuntimeException("读取失败");
- }
- finally
- {
- if(fw!= null)
- try
- {
- fw.close();
- }
- catch (IOException e)
- {
- }
- if(fr!= null)
- try
- {
- fr.close();
- }
- catch (IOException e)
- {
- }
- }
- }
- //从C盘读一个字符,就往D盘写一个字符。
- public static void copy_1()
- {
- FileWriter fw = null;
- FileReader fr = null;
- try
- {
- //创建目的地。
- fw = new FileWriter("c:\\info_copy1.txt");
- //与已有文件关联。
- fr = new FileReader("d:\\info.txt");
- int ch = 0;
- while ((ch = fr.read())!=-1)
- {
- fw.write(ch);
- }
- }
- catch (IOException e )
- {
- throw new RuntimeException("读取失败");
- }
- finally
- {
- if(fw!= null)
- try
- {
- fw.close();
- }
- catch (IOException e)
- {
- }
- if(fr!= null)
- try
- {
- fr.close();
- }
- catch (IOException e)
- {
- }
- }
- }
- }
四、字符流的缓冲区
- 字符流缓冲区:缓冲区的出现提高了对数据的读写效率。
- BufferedWriter: 是给字符输出流提高效率用的,那就意味着,缓冲区对象建立时,必须要先有流对象。
- BufferedReader:是给字符输入流提高效率用的,那就意味着,缓冲区对象建立时,必须要先有流对象。
- 缓冲区要结合流才可以使用。在创建缓冲区之前,必须要先有流对象。 在流的基础上对流的功能进行了增强。
- readLine()方法:
- 该方法是BufferedReader缓冲区提供了一个一次读取一行的方法。方便于对文本数据的读取。当返回值为null时,表示读到文本末尾。
- readLine方法返回的时候只是返回回车符之前的数据内容,并不返回回车符。
- readLine方法原理:
- 无论是读一行,还是读取多个字符。其实都是在硬盘上一个一个读取。
- 所以使用的还是read方法一次读取一个的方法。
- 字符输出流缓冲区基本步骤:
- 创建一个字符写入流对象和文件相关联。
- FileWriter fw = new FileWriter("buf.txt");
- 加入缓冲区技术。将需要被提高效率的流对象作为参数传递给缓冲区的构造函数.
- BufferedWriter bufw = new BufferedWriter(fw);
- 写入数据
- bufw.write("abcd");
- 将缓冲区的数据刷新到目的地。
- bufw.flush();
- 该缓冲区提供了一个跨平台的换行符newLine();
- 关闭缓冲区,就是在关闭缓冲区中的流对象。
- bufw.close();
- 创建一个字符写入流对象和文件相关联。
- 字符输入流缓冲区基本步骤:
- 创建一个字符读取流对象和文件相关联。
- FileReader fr = new FileReader("buf.txt");
- 加入缓冲区技术。将需要被提高效率的流对象作为参数传递给缓冲区的构造函数.
- BufferedReader bufr = new BufferedReader(fr);
- 定义一个字符串变量接收读取到的数据,用循环完成数据读取.
String line = null;
while((line = bufr.readLine())!= null)
{
System.out.println(line);
} - 关闭缓冲区,就是在关闭缓冲区中的流对象。
- bufr.close();
- 创建一个字符读取流对象和文件相关联。
- 装饰设计模式:IO中的使用到了一个设计模式。
- 装饰设计模式解决:对一组类进行功能的增强。
- 包装:写一个类(包装类)对被包装对象进行包装;
- 包装类和被包装对象要实现同样的接口;
- 包装类要持有一个被包装对象;
- 包装类在实现接口时,大部分方法是靠调用被包装对象来实现的,对于需要修改的方法我们自己实现;
- 继承和装饰设计模式的区别如下图:
练习:
- /*
- 明白了BufferedReader类中特有方法readLine的原理后。
- 可以自定义一个雷中包含一个功能和readLine一致的方法。
- 来模拟一下BufferedReader
- */
- import java.io.*;
- class MyBufferedReader extends Reader
- {
- private /*FileReader*/Reader r;
- MyBufferedReader(/*FileReader*/Reader r)
- {
- this.r = r;
- }
- //可以一次读一行数据的方法。
- public String myReadLine()throws IOException
- {
- //定义一个临时容器。原BufferReader封装的是字符数组。
- //为了演示方便。定义一个StringBuilder容器。因为最终还是要将数据变成字符串。
- StringBuilder sb = new StringBuilder();
- int ch = 0;
- while ((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 void close()throws IOException
- {
- r.close();
- }
- public int read(char[] cbuf,int off,int len)throws IOException
- {
- return r.read(cbuf,off,len);
- }
- public void myClose()throws IOException
- {
- r.close();
- }
- }
- class MyBufferedReaderDemo
- {
- public static void main(String[] args) throws IOException
- {
- FileReader fr = new FileReader("buf.txt");
- MyBufferedReader myBuf = new MyBufferedReader(fr);
- String line = null;
- while ((line =myBuf.myReadLine())!=null)
- {
- System.out.println(line);
- }
- myBuf.myClose();
- }
- }
练习:模拟LineNumberReader类
- import java.io.*;
- 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;
- }
- }
- class MyLineNumberReaderDemo
- {
- public static void main(String[] args) throws IOException
- {
- FileReader fr = new FileReader("FileReaderTest.java");
- MyLineNumberReader mylnr = new MyLineNumberReader(fr);
- String line = null;
- mylnr.setLineNumber(100);
- while ((line = mylnr.myReadLine())!=null)
- {
- System.out.println(mylnr.getLineNumber()+"::"+line);
- }
- mylnr.myClose();
- }
- }
第二讲 字节流
一、简述
字节流和字符流的基本操作是相同的,但字节流还可以操作其他媒体文件。由于媒体文件数据中都是以字节存储的,所以,字节流对象可直接对媒体文件的数据写入到文件中,而可以不用再进行刷流动作。因为字节流操作的是字节,即数据的最小单位,不需要像字符流一样要进行转换为字节。所以可直接将字节数据写入到指定文件中。
- 字节流体系:
- InputStream:是表示字节输入流(读取字节流)的所有类的超类。
- 特有方法: int available();//返回文件中的字节个数。
- FileInputStream:从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。
- FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。
- BufferedInputStream:该类实现缓冲的输入流。
- Stream:
- ObjectInputStream:
- PipedInputStream:
- OutputStream:此抽象类是表示输出字节流(写入字节流)的所有类的超类。
- FileOutputStream:文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。
- FilterOutputStream:此类是过滤输出流的所有类的超类。
- BufferedOutputStream:该类实现缓冲的输出流。
- PrintStream:
- DataOutputStream:
- ObjectOutputStream:
- PipedOutputStream:
- /*
- 复制一个图片
- 思路:
- 1,用字节读取流对象和图片关联。
- 2,用自己写入流对象创建一个图片文件,用于存储获取到的图片数据。
- 3,通过循环读写,完成数据的存储。
- 4,关闭资源。
- */
- import java.io.*;
- class CopyPic
- {
- public static void main(String[] args)
- {
- FileOutputStream fos = null;
- FileInputStream fis =null;
- try
- {
- //关联要复制的文件
- fis = new FileInputStream("e:\\1.bmp");
- //指定复制路径
- fos = new FileOutputStream("e:\\2.bmp");
- //利用available方法指定数组长度
- byte[] buf = new byte[fis.available()];
- fis.read(buf);//读取流中数据
- fos.write(buf);//将流数据写到目的地
- /*
- byte[] buf = new byte[1024];
- int len = 0;
- while ((len = fis.read(buf))!=-1)
- {
- fos.write(buf,0,len);
- }
- */
- }
- catch (IOException e)
- {
- throw new RuntimeException("复制文件失败");
- }
- finally
- {
- //关闭流资源
- try
- {
- if(fis!=null)
- fis.close();
- }
- catch (IOException e)
- {
- <span style="white-space:pre"> </span>throw new RuntimeException("读取关闭失败");
- }
- try
- {
- if(fos!=null)
- fos.close();
- }
- catch (IOException e)
- {
- throw new RuntimeException("写入关闭失败");
- }
- }
- }
- }
二、字节流缓冲区
- 字节流缓冲区:同样提高了对数据的读写效率。
- BufferedInputStream: 是给字符输入流提高效率用的。
- read()方法特点: 会将字节byte型值提升为int型值。
- BufferedOutputStream:是给字符输出流提高效率用的。
- write()方法特点: 会将int型强转为byte型,即保留二进制数的最后八位。
- 缓冲区原理:将数据拷贝一部分,读取一部分,循环,直到数据全部读取完毕。
- 在内存中创建一个字节数组作为缓冲区 用字节读取流的read()方法把硬盘中一部分数据存储到缓冲区里。
- 因为缓冲区是字节数组,字节数组有指针,用BufferedInputStream对象的read方法根据指针从缓冲区中一个一字节读取。
- 当读取完最后一个指针所存取的字节后,再通过字节读取流对象的read()方法把硬盘中一部分数据存储到缓冲区里。
- 如此反复。当读到结尾时字节读取流对象的read()方法返回-1.不再从硬盘中读取数据到缓冲区。
- 注意:
- 字节流的读一个字节的read方法为什么返回值类型不是byte,而是int。
- 因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1.那么就会数据还没有读完,就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。
- 所以,为了避免这种情况将读到的字节进行int类型的提升。并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。而在写入数据时,只写该int类型数据的最低8位。
- byte类型的-1提升为int类型时还是-1。原因:因为在bit8个1前面补的全是1导致的。如果在bit8个1前面补0,即可以保留原字节数据不变,又可以避免-1的出现。这时将byte型数据&0xff即255即可。
- 字节流的读一个字节的read方法为什么返回值类型不是byte,而是int。
- /*
- 涉及的数据:
- 1,定义数组
- 2,定义指针
- 3,定义计数器
- 思路:
- 1、定义一个固定长度的数组。
- 2、定义一个指针和计数器用于读取数组长度,和计数数组元素是否取完为0。
- 3、每次将字节数据存入元素要先将数组中的元素取完。
- */
- import java.io.*;
- class MyBufferedInputStream
- {
- private InputStream in;
- private byte[] buf = new byte[1024];
- //pos代表字节数组指针,count记录从硬盘存储到字节数组的字节个数。
- private int pos = 0,count = 0;
- MyBufferedInputStream(InputStream in)
- {
- this.in = in;
- }
- //一次读取一个字节,从缓冲区(字节数组)读取。
- public int myRead() throws IOException
- {
- //通过in对象读取硬盘上的数据,并存储在buf当中。
- if(count==0)//count为0时从硬盘中抓取数据到缓冲区
- {
- count = in.read(buf);
- //读取到末尾时返回-1,程序结束。
- if(count<0)
- return -1;
- //从缓冲区读取数据
- pos = 0;//初始化指针
- byte b = buf[pos];
- count--;//每被读一个字节,表示数组中的字节数少一个
- pos++;//指针加1
- return b&255;
- //返回的byte类型提升为int类型,字节数增加,且高24位被补1,原字节数据改变。
- //通过与上255,主动将byte类型提升为int类型,将高24位补0,原字节数据不变。
- //而在输出字节流写入数据时,只写该int类型数据的最低8位。
- }
- //count>0时,说明数组中数据没读取完,继续读取数据
- else if(count>0)
- {
- byte b = buf[pos];
- count--;
- pos++;
- return b&0xff;
- }
- return -1;
- }
- public void myClose() throws IOException
- {
- in.close();
- }
- }
- class CopyMp3
- {
- public static void main(String[] args)
- {
- MyBufferedInputStream mybin = null;
- BufferedOutputStream bufos =null;
- try
- {
- //关联复制文件输入流对象到缓冲区
- mybin = new MyBufferedInputStream(new FileInputStream("e:\\1.mp3"));
- //指定复制文件目的地的输出流对象到缓冲区
- out = new BufferedOutputStream(new FileOutputStream("e:\\3.mp3"));
- int by = 0;
- while ((by=bufis.myRead())!=-1)
- {
- bufos.write(by);//将缓冲区中的数据写入指定文件中
- }
- }
- catch(IOException e)
- {
- throw new RuntimeException("读写失败");
- }
- finally
- {
- //关闭流资源
- try
- {
- if(bufos!= null)
- bufos.close();
- }
- catch(IOException ex)
- {
- throw new RuntimeException("写入关闭失败");
- }
- try
- {
- if(bufis!= null)
- bufis.myClose();
- }
- catch(IOException ex)
- {
- throw new RuntimeException("读取关闭失败");
- }
- }
- }
- }
第三讲 转换流
简述
- 转换流特有功能:
- 转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。
- 转换流的最强功能就是基于 字节流 + 编码表 。没有转换,没有字符流。
- 转换流InputStreamReader,OutputStreamWriter各有一个子类就是操作文件的字符流对象FileReader,FileWrier:想要操作文本文件,必须要进行编码转换,而编码转换动作转换流都完成了。所以操作文件的流对象只要继承自转换流就可以读取一个字符了。但是子类有一个局限性,就是子类中使用的编码是固定的,是本机默认的编码表,对于简体中文版的系统默认码表是GBK。
- FileReader fr = new FileReader("a.txt");
- InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");
- 以上两句代码功能一致,
- 如果仅仅使用平台默认码表,就使用FileReader fr = new FileReader("a.txt"); //因为简化。
- 如果需要制定码表,必须用转换流。
- 转换流 = 字节流+编码表。
- 转换流的子类File = 字节流 + 默认编码表。
- 凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。
- System.out(InputStream):对应的是标准输出设备。控制台
- System.in(PrintStream): 对应的标准录入设备:键盘。
- /*
- 需求:
- 通过键盘录入数据。
- 当录入一行数据后,就将该行数据进行打印。
- 如果录入的数据时over,那么停止录入。
- */
- import java.io.*;
- class ReadIn
- {
- public static void main(String[] args) throws IOException
- {
- //获取键盘录入
- InputStream in = System.in;
- //定义不确定长度的缓冲区接收键盘录入
- StringBuilder sb = new StringBuilder();
- while (true)//从缓冲区读取数据
- {
- //定义变量接收字节
- int ch = in.read();
- //判断是否按下回车符
- if (ch =='\r')
- continue;
- if (ch =='\n')
- {
- // 输出数据
- String s = sb.toString();
- //以over为结束标记
- if("over".equals(s))
- break;
- //将字符转换大写
- System.out.println(s.toUpperCase());
- //清空缓冲区
- sb.delete(0,sb.length());
- }
- else
- //将接收的字节添加到缓冲区
- sb.append((char)ch);
- }
- }
- }
当使用输入流进行键盘录入时,只能一个字节一个字节进行录入。为了提高效率,可以自定义一个数组将一行字节存储。当一行录入完毕,再将一行数据显示。这种整行录入的方式,和字符流读一行数据的原理是一样的。也就是readLine方法。readLine方法是字符流BufferedReader类中方法。而键盘录入的read方法是字节流InputStream的方法。那么能不能将字节流转成字符流再使用字符流缓冲区的readLine方法呢?这就需要用到转换流了。
二、转换流基本使用步骤
- 输入转换流使用步骤:
- 获取字节输入流对象。
- InputStream in = System.in;
- 将字节流对象转成字符流对象,使用转换流:InputStreamReader。
- InputStreamReader isr = new InputStreamReader(in);
- 为了提高效率,将字符流进行缓冲区技术高效操作,使用BufferedReader。
- BufferedReader bufr = new BufferedReader(isr);
- 定义String型变量,循环接收数据 String line = null;
while((line = bufr.readLine())!=null)
{
if("over".equals(line))//自定义结束标记over
break;
System.out.println(line);//输出数据
} - 关闭流资源,该步骤可以省略。
- bufr.close();
- 注:1,2,3步合并为键盘录入最常见写法。
- BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
- 获取字节输入流对象。
- 输出转换流使用步骤
- 获取输出流对象。
- OutputStream out = System.out;
- 将字节流对象转成字符流对象,使用转换流:OutputStreamWriter。
- OutputStreamWriter osw = new OutputStreamWriter(out);
- 为了提高效率,将字符流进行缓冲区技术高效操作,使用BufferedWriter。
- BufferedWriter bufw = new BufferedWriter(osw);
- 写入数据
- bufw.write("abc");
- 刷新数据到目的地
- bufw.flush();
- 关闭流资源
-
- bufw.close();
- 注:1,2,3步可以合并为:BufferedWriter bufw =new BufferedWriter(new OutputStreamWriter(System.out));
- 获取输出流对象。
- 注:System类可以重新分配流字节流。
- static void setIn(InputStream in) 重新分配“标准”输入流。
- static void setOut(PrintStream out) 重新分配“标准”输出流。
练习:使用转换流,接收键盘录入打印在控制台
- import java.io.*;
- class TransStreamDemo
- {
- public static void main(String[] args) throws IOException
- {
- BufferedReader bufr =null;
- BufferedWriter bufw = null;
- try {
- //将键盘录入转换成字符流
- bufr =new BufferedReader(new InputStreamReader(System.in));
- //将输出字节流转换为字符流
- bufw = new BufferedWriter(new OutputStreamWriter(System.out));
- String line = null;
- while((line = bufr.readLine())!=null)
- {
- if("over".equals(line))//自定义结束标记over
- break;
- //将字符转换成大写
- bufw.write(line.toUpperCase());
- //换行
- bufw.newLine();
- bufw.flush();
- }
- } catch (IOException e) {
- throw new RuntimeException("读写失败");
- }
- finally
- {
- try {
- if(bufr!=null)
- bufr.close();
- } catch (IOException e) {
- throw new RuntimeException("读取关闭失败");
- }
- try {
- if(bufw!=null)
- bufw.close();
- } catch (IOException e) {
- throw new RuntimeException("写入关闭失败");
- }
- }
- }
- }
三、字符编码
- 字符编码通过转换流来完成。
- 在两个对象进行构造的时候,可以加入字符集(即编码表),可传入编码表的有:
- 转换流:InuputStreamReader和OutputStreamWriter。
- 打印流:PrintStream和PrintWriter,只有输出流。
- 转换流的编码应用
- 可以将字符以指定编码格式存储。
- 可以对文本数据指定编码格式来解读。
- 指定编码表的动作由构造函数完成。
- 编码和解码
- 编码:字符串变成字节数组
- 默认字符集:
- String ---> byte[] :str.getBytes()
- 指定字符集:
- String ---> byte[] :str.getBytes(charsetName)
- 默认字符集:
- 解码:字节数组变成字符串
- 默认字符集:
- byte[] ---> String :new String(byte[])
- 指定字符集:
- byte[] ---> String :newString(byte[],charsetName)
- 默认字符集:
- 编码:字符串变成字节数组
- 对于编码和解码的字符集转换注意事项
- 如果编码失败,解码就没意义了。
- 如果编码成功,解码出现的是乱码,需要对乱码通过再次编码(用解错码的编码表),然后再通过正确的编码表解码。针对于IOS8859-1是通用的。
- 如果用的是GBK编码,UTF-8解码,此时通过再次编码后解码的方式,就不能成功了,因为UTF-8也支持中文,在UTF-8解的时候,会将对应的字节数改变,所以不会成功。
- 特别注意:对于中文的”联通“,这两个字比较特别,它的二进制位正好是和在UTF-8中两个字节打头的相同,所以在文本文件中,如果单独写“联通”或者和满足UTF-8编码格式的字符一起保存时,记事本就会用UTF-8来进行解码动作,这样显示的就会是乱码。
- /*
- 有五个学生,每个学生有3门课的成绩,
- 从键盘输入以上数据(包括姓名,三门课成绩),
- 输入的格式:如:zhagnsan,30,40,60计算出总成绩,
- 并把学生的信息和计算出的总分数高低顺序存放在磁盘文件"stud.txt"中。
- 1,描述学生对象。
- 2,定义一个可操作学生对象的工具类。
- 思想:
- 1,通过获取键盘录入一行数据,并将该行中的信息取出封装成学生对象。
- 2,因为学生有很多,那么就需要存储,使用到集合。因为要对学生的总分排序。所以可以使用TreeSet。
- 3,将集合的信息写入到一个文件中。
- */
- import java.io.*;
- import java.util.*;
- //学生类
- class Student implements Comparable<Student>
- {
- private String name;
- private int ma,cn,en;
- private int sum;
- Student(String name,int ma,int cn,int en)
- {
- this.name = name;
- this.ma = ma;
- this.cn = cn;
- this.en = en;
- sum = ma + cn + en;
- }
- //复写compareTo方法,按分数排序
- public int compareTo(Student s)
- {
- int num = new Integer(this.sum).compareTo(new Integer(s.sum));
- if(num==0)
- return this.name.compareTo(s.name);
- return num;
- }
- public String getName()
- {
- return name;
- }
- public int getSum()
- {
- return sum;
- }
- //复写hashCode
- public int hashCode()
- {
- return name.hashCode()+sum*78;
- }
- //复写equals,判断姓名和总成绩是否相同
- public boolean equals(Object obj)
- {
- if(!(obj instanceof Student))
- throw new ClassCastException("类型不匹配");
- Student s = (Student)obj;
- return this.name.equals(s.name) && this.sum==s.sum;
- }
- //复写toString方法,返回学生信息。
- public String toString()
- {
- return "student["+name+", "+ma+", "+cn+", "+en+"]";
- }
- }
- //学生工具类
- class StudentInfoTool
- {
- //从键盘读取学生信息方法,以学生自身排序方式存入集合中
- public static Set<Student> getStudents()throws IOException
- {
- return getStudents(null);
- }
- //从键盘读取学生信息方法,以比较器排序方式存入集合中
- public static Set<Student> getStudents(Comparator<Student> cmp)throws IOException
- {
- //获取键盘录入
- BufferedReader bufr =
- new BufferedReader(new InputStreamReader(System.in));
- String line = null;
- //定义一个集合,用来存储学生对象
- Set<Student> stus = null;
- if(cmp==null)
- stus = new TreeSet<Student>();
- else
- stus = new TreeSet<Student>(cmp);
- //读取数据
- while((line=bufr.readLine())!=null)
- {
- if("over".equals(line))
- break;
- String[] info = line.split(",");
- //将学生对象存入集合中
- Student stu = new Student(info[0],Integer.parseInt(info[1]),
- Integer.parseInt(info[2]),
- Integer.parseInt(info[3]));
- stus.add(stu);
- }
- //关闭流
- bufr.close();
- return stus;
- }
- //把学生信息按照指定属性写入指定文件中
- public static void write2File(Set<Student> stus)throws IOException
- {
- //关联文件
- BufferedWriter bufw = new BufferedWriter(new FileWriter("stuinfo.txt"));
- //遍历集合,将学生信息写入到指定文件
- for(Student stu : stus)
- {
- bufw.write(stu.toString()+"\t");
- bufw.write(stu.getSum()+"");
- bufw.newLine();
- bufw.flush();
- }
- bufw.close();
- }
- }
- class StudentInfoTest
- {
- public static void main(String[] args) throws IOException
- {
- //定义一个反转自身比较性的比较器
- Comparator<Student> cmp = Collections.reverseOrder();
- //调用学生工具类的获取信息方法,将信息存入集合中
- Set<Student> stus = StudentInfoTool.getStudents(cmp);
- //将学生信息写入指定文件中
- StudentInfoTool.write2File(stus);
- }
- }
第四讲 流操作的基本规律
如何选择流的是使用,通过三个明确来完成
- 明确源和目的。
- 源:输入流。InputStream Reader。
- 目的:输出流。OutputStream Writer。
- 明确:操作的数据是否是纯文本。
- 是:用字符流。
- 不是:用字节流。
- 当体系明确后,再明确要使用哪个具体的对象。通过设备来进行区分。
- 源设备:内存,硬盘,键盘。
- 目的设备:内存,硬盘,控制台。
练习:
- 需求:将一个文本文件中的数据存储到另一个文件中,复制文件。
- 源:因为是文件 ,所以使用读取流。InputStream Reader。
- 是不是操作文本文件。是:这时就可以选择Reader
- 这样体系就明确了。
-
- 接下来要使用该体系中的哪个对象。
- 明确设备:硬盘。一个文件。
- Reader体系中可以操作文件的对象是FileReader。
-
- 是否需要提高效率:是:加入Reader体系中的缓冲区 BufferedReader.
- FileReader fr = new FileReader("a.txt");
- BufferedReader bufr = new BufferedReader(fr);
-
- 目的:OutputStream Writer。
- 是否是纯文本。是:Writer。
- 设备:硬盘。一个文件。
- Writer体系中可以操作文件的对象是FileWriter。
- 是否需要提高效率:是:加入Writer体系中的缓冲区 BufferedWriter.
- FileWriter fw = new FileWriter("b.txt");
- BufferedWriter bufw = new BufferedWriter(fw);
- 需求:将键盘录入的数据保存到一个文件中。
- 这个需求中有源和目的都存在。
- 源:InputStream Reader。
- 是不是纯文本?是:Reader。
-
- 设备:键盘。对应的对象是System.in。
- 不是选择Reader吗?System.in对应的不是字节流吗?为了操作键盘的文本数据方便。转成字符流按照字符串操作是最方便的。
- 所以既然明确了Reader,就将System.in转换成Reader。用到了Reader体系中转换流,InputStreamReader。
-
- InputStreamReader isr = new InputStreamReader(System.in);
- 需要提高效率吗?需要:BufferedReader。
- BufferedReader bufr = new BufferedReader(isr);
-
- 目的:OutputStream Writer。
- 是否是纯文本?是:Writer。
- 设备:硬盘,一个文件。使用FileWriter。
- FileWriter fw = new FileWriter("c.txt");
- 需要提高效率吗?需要。
- BufferedWriter bufw = new BufferedWriter(fw);
- 扩展:想要把录入的数据按照指定的编码表(utf-8),将数据存到文件中。
- 目的:OutputStream Writer。
- 是否是纯文本?是:Writer。
- 设备:硬盘,一个文件。使用FileWriter。
- 但是FileWriter是使用的默认编码表 GBK。但是存储时,需要加入指定的编码表(utf-8)。而指定的编码表只有转换流可以指定。
- 所以要使用的对象是OutputStreamWriter。而该转换流对象要接收一个字节输出流,而且还可以操作文件的字节输出流FileOutputStream。
- OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8");
- 需要提高效率吗?需要。
- BufferedWriter bufw = new BufferedWriter(osw);
- 所以:转换流什么时候使用?字符和字节之间的桥梁,通常涉及到字符编码转换时,需要用到转换流。