黑马程序员——【学习笔记】IO技术——IO流

来源:互联网 发布:vb msg 编辑:程序博客网 时间:2024/05/22 00:53

-------android培训java培训、期待与您交流!----------


1 IO流是用来处理设备之间的数据传输。

这里的设备是指:鼠标,键盘,硬盘(就是文件),显示器,控制台,内存...

Java对数据的操作是通过流的方式处理。Java用与操作流的对象都在包中。


1.1 IO流的分类


①IO流按流方向来说,分为输入流和输出流

输入流和输出流是指用于输入/输出数据的流,这个输入/输出是对于内存来说的。

比如,在键盘打字到显示器上就是键盘输入到内存再输出到显示器上。后续学习到的流很多,不要混淆。


②IO流按操作的数据来说,分为字节流和字符流。

字节流:早期IO包里的都是字节流,因为数据无论是内存中的还是硬盘中的都是字节,最终是以二进制的形式存储。特别是图片、视频等媒体文件。

字符流:后来操作文本数据相当常见,于是创建了字符流。字符流也就是存储abcd等英文及符号的存在,而存入存储的时候就需要编码来对abcd进行编译成二进制输入输出,这就是编码表。中国的编码表是GKB,Unicode是国际通用的码表。字符流可以在内部融合编码表,即可以由调用者指定流内操作的是什么编码表。

PS:Unicode里无论什么字符都用两个字节表示。

其中:

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

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

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


2 字符流

Reader Writer


2.1 FileWriter

示例:在硬盘创建一个文件并写入文字数据。

首先,创建一个可以往文件中写入字符数据的字符输出流对象FileWriter(是Writer的子类)

在创建对象时,在参数中计入文件存放路径和文件名,如果本来已有文件,则会覆盖,否则新建一个文件。

注意,因为IO操作总是有可能造成IO错误IOException,记得抛出。

用append()或者write()(通常用write)计入数据内容。(跟StringBuffer一样都是用append(),而集合用add()或insert();)

class Day20{public static void main(String[] args) throws IOException{FileWriter fw = new FileWriter("D:\\Demo.txt");fw.append("输出到文件Demo.txt");fw.flush();fw.append("shuchu again");fw.flush();fw.write("haha");fw.flush();}}


FileWriter一旦被创建就必须要有被操作的对象(文件),所以FileWriter没有无参数的构造函数


2.1.1 FileWriter中的flush()和close();

为什么要有flush或close()的操作?

这是因为当我们writer()数据执行完之后实质上只是把数据写入到流里,即内存内,而并没有写到文件中。如果不用这语句,结果是新文件被创造出来并覆盖了旧文件,但是写入的内容并没有被写入。

只有flush()或close()后,被写入到流的数据才会被输出到文件中。

flush()和close()的区别是:flush只刷新,即把流里的数据输出到文件中,但不关闭流。

而close会把数据输出到文件之后关闭流,之后不能再操作此流了。

class Day20{public static void main(String[] args) throws IOException{FileWriter fw = new FileWriter("D:\\Demo.txt");fw.append("输出到文件Demo.txt");//fw.flush();fw.append("\r\nshuchu again");//fw.flush();fw.write("\r\nhaha");//fw.flush();}}


2.1.2 如果在构造函数中加入true,可以实现对文件进行续写。即创建文件时遇到文件存在的话,不会覆盖,只会在数据后续写。

class Day20{public static void main(String[] args) throws IOException{FileWriter fw = new FileWriter("D:\\Demo.txt",true );fw.append("\r\n这是Java输出到文件Demo.txt的数据");//fw.flush();fw.append("\r\nshuchu again");//fw.flush();fw.write("\r\nhaha");fw.flush();}}


2.2 FileReader

示例:读取一个文件,将读取到的字符打印到控制台

FileReader fr = new FileReader(文件);

fr.read();读取流一个字节一个字节地读取,一次读取下一个。如果读到没有字节了就返回-1。返回-1这个反馈给了我们循环的条件。

fr.read(char[] ch);将字符读入数组,返回读取的字符数。然后存一次把字符组打出一次。每次打出的字符个数为fr.read(int[] ch);反回的读入,直到返回数为-1(即没有数据)为止。这样输入流不是一个字节一个字节地读取,而是一次读完数组的长度,再输出。直到没有数。

用StringBuffer()装入read()读取的字符,然后toString输出字符缓冲区。字符一次性读取全部,存入StringBuffer里。

class Day20{public static void main(String[] args){FileReader fr = null;try {fr = new FileReader("D:\\Demo.txt");char[] buf = new char[1024];int num=0;while((num=fr.read(buf))!=-1)System.out.print(new String(buf,0,num));//int ch = 0;//while((ch=fr.read())!=-1)//System.out.print((char)ch);//int ch1 =0;//StringBuffer sb = new StringBuffer();//while((ch1=fr.read())!=-1)//sb.append((char)ch1);//System.out.println(sb.toString());} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}finally{ try {fr.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}

PS:

注意凡是有IO操作的语句肯定会有安全问题,比如读取文件会有FileNotFoundException,IO流操作会有IOException。

数据流操作try catch后记得要finally fr.close();,否则程序一旦catch到了错误,流对象不会关闭。

而fr.close();也是IO操作,也要try catch


示例:将D盘的一个文本文件复制到D盘的另一个不存在的目录。

public class Day21 {public static void main(String[] args){FileReader fr = null;FileWriter fw = null;try{fr = new FileReader("D:\\Demo.txt");fw = new FileWriter("D:\\1\\Demo.txt",true);//第一种方法:数组读取//int num = 0;//char[] ch = new char[1024];////while((num=fr.read(ch))!=-1){//fw.write(ch,0,num);//}//第二种方法:频繁读取int ch = 0;while((ch=fr.read())!=-1)fw.write(ch);}catch(FileNotFoundException e){e.printStackTrace();}catch(IOException e){e.printStackTrace();}finally{try{fr.close();fw.close();}catch(IOException e){e.printStackTrace();}}}}


3 字符流缓冲区

BufferedWriter

BufferedReader

字符缓冲区是为了增强IO流的运行效率。

字符缓冲区的出现是为了提高流的操作效率而出现的,所以在创建缓冲区之前,必须要先有流对象。

字符缓冲区的最终原理就是数组。

public class Day21{public static void main(String[] args) throws IOException{FileWriter fw = null;fw = new FileWriter("D:\\buf.txt");//创建缓冲区,把需要被提高效率的流作为参数输入BufferedWriter bufw = new BufferedWriter(fw);for(int x=0;x<5;x++){bufw.write("bacde"+x);bufw.newLine();}//只要用到缓冲区,就记得刷新。//但是缓冲区只是为了提高效率,真正调用资源的是FileWriter//所以关闭缓冲区,就是关闭缓冲区中的流对象。bufw.flush();bufw.close();}}

PS:

缓冲区只是为了提高效率,

正如上面说的,缓冲区技术最终原理就是用数组存储FileWriter的数据,

所以程序中实际操作数据的是FileWriter,而缓冲区是跟它关联起来了。

所以关闭缓冲区实际上就是关闭流本身。

BufferedReader:

BufferReader的read()可以读取字符流。

而它的readLine();方法更高效,可以直接读取一行,再处理,并返回String(因为一行一行读取的结果就是字符串)。

当readLine();读取到没有数据时,就会返回null。可以用于循环的时候判断。

public class Day21{public static void main(String[] args) throws IOException{FileReader fr = new FileReader("D:\\Demo.txt");BufferedReader bufr = new BufferedReader(fr);String s = new String();while((s=bufr.readLine())!=null)System.out.println(s);bufr.close();}}

LineNumberReader:

LineNumberReader虽然名字没有Buffered,但它是BufferedReader的子类。

它可以获取的数据带行数,而且可以设置行数和获取行数。

跟踪行号的缓冲字符输入流。此类定义了方法setLineNumber(int i)和getLineNumber(),他们可以分别用于设置和获取当前行号。

setLineNumber();设置起始行数(这个数不包含到数据类,是初始化的数,比如设置0,第一行就是1)

getLineNumber();获取行数

public class Day21{public static void main(String[] args) throws IOException{FileReader fr = new FileReader("D:\\Demo.txt");//BufferedReader bufr = new BufferedReader(fr);LineNumberReader bufr = new LineNumberReader(fr);String s = new String();bufr.setLineNumber(0);while((s=bufr.readLine())!=null)System.out.println(bufr.getLineNumber()+":"+s);bufr.close();}}
1:这是文件里的原有数据
2:这是Java输出到文件Demo.txt的数据null
3:shuchu \r\nagain
4:haha

4 装饰类

实际上,BufferedReader和BufferedWriter就是装饰类。

它不是Reader或Writer的子类,但它是为了增强Reader和Writer的功能。

public class Day21{public static void main(String[] args){Animal a = new Animal();NewAnimal na = new NewAnimal(a);na.eat(2);}}class Animal{void eat(){System.out.println("animal eat");}}class NewAnimal{private Animal a;NewAnimal(Animal a){this.a=a;}void eat(int i){System.out.println("newanimal eat better"+i);}}
装饰类的特点

装饰类的方法与本类的方法并不是继承关系,装饰类跟本类本身也没有特别的关系(本类的方法eat()不能被调用)。方法间的调用都是认为的。

装饰类必须与本类同继承一个父类或同实现一个接口。


装饰类与父子类关系的区别

按照面向对象的思想,增强程序的运行效率的一个方法是使用缓冲区。

那么,如果用父子类来增强本类,需要:

Writer

——TextWriter操作文本的字符输出流

——BufferedTextWriter运用了缓冲技术的操作文本的字符输出流

——MediaWriter操作媒体的字符输出流

——BufferedMediaWriter运用了缓冲技术的操作媒体的字符输出流

……

这样在以后就会使Writer体系越来越臃肿。


如果使用装饰类,把装饰类设定好之后,则以后使用只需要把需要被增强效率的类传入即可。

即:

Writer

——TextWriter操作文本的字符输出流

——MediaWriter操作媒体的字符输出流

——FileWriter操作文件的字符输出流

……

——BufferedWriter运用了缓冲技术的,默认传入参数是Writer对象的输出流,是一个装饰类,谁需要被装饰就往参数传入谁,以后Writer增加了新的子类,都可以用。


5 字节流

InputStream和OutputStream

基本操作与字符流类相同。但它不近可以操作字符流,还可以操作其它媒体文件。

它操作字符流时需要先把字符转化为字节。getByte();

字节流不需要flush即可以把内容传入文本里。但是最后要close关闭资源

fis.available();返回下一次对此输入流调用的方法可以不受阻塞地(读到回车不算阻塞)从此输入流读取的估计剩余字节数

public class Day21{public static void main(String[] args) throws IOException{FileInputStream fis = new FileInputStream("D:\\Demo.txt");//用笃定的字节数组来读//int len =0;//byte[] b = new byte[1024];//while((len=fis.read(b))!=-1)//System.out.println(new String(b,0,len));//一个字节一个字节地读,当文件里有中文的时候需要用到转换里将字节装换成字符//InputStreamReader isr = new InputStreamReader(fis);//int i = 0;//while((i=isr.read())!=-1)//System.out.print((char)i);//估计文本的大小,然后用一个限定长度的数组一次装完。注意如果文件太大,可能会发生内存溢出错误byte[] b = new byte[fis.available()];int i = fis.available();System.out.println(i);fis.read(b);System.out.println(new String(b));fis.close();}}


5.1 设备读写

读取一个键盘录入的数据,并打印在控制台上,

键盘本身就是一个标准的输入设备,对于java而言,对于这种输入设备都有对应的对象

5.1.1 读取键盘录入

System.out:对应的是标准输出设备:控制台

System.in:对应的是标准输入设备:键盘。所有键盘输入的都是字节流。

public class Day22{public static void main(String[] args) throws IOException{InputStream in = System.in;int ch  = in.read();System.out.println((char)ch);}}
sdfafaf
s

注意:以上程序只读取一个字节,所以就算输入了很多个字节,也只读取一个。


通过System.setIn(InputStream is);和System.setOut(PrintStream sp);可以对默认设备进行设定。

因为InputStream和OutputStream操作的是字节流,所以System.setIn()和System.setOut()的参数都是字节流对象,其中,setIn要求是输入字节流,setOut要求是打印字节流

public class Day22{public static void main(String[] args) throws IOException{System.setIn(new FileInputStream("D:\\Demo.txt"));System.setOut(new PrintStream("D:\\Demo3.txt"));InputStream  is = System.in;OutputStream os = System.out;int ch = 0;while((ch=is.read())!=-1)os.write(ch);is.close();os.close();}}

此程序相当于复制本件。因为输入流和输出流都改为文件。



6 转换流

我们直到,读取设备和输出到设备的流都是字节流,但是我们常用的输入是字符,而字符流能更好的运用于各种操作。

所以java给了转换流,作为字节与字符间的桥梁。

6.1 读取转换流

InputStreamReader:它是一个装饰类,可以把输入到内存的字节流转换成字符流。然后用字符流的方法来操作数据。

OutputStreamWriter:它是一个装饰类,可以把内存中的字符流转换成字节流输出到设备中。

就是说:内存内用字符流操作更显高效。但内存外设备只接收字节流。所以转换流就是字节流和字符流间的桥梁。

public class Day22{public static void main(String[] args) throws IOException{BufferedReader br = new BufferedReader(new InputStreamReader(System.in));//使用转换流的同时对转换流使用缓冲技术BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));//<pre name="code" class="java">使用转换流的同时对转换流使用缓冲技术
String line = null;while((line=br.readLine())!=null){if("over".equals(line)){break;}bw.write(line);bw.newLine();bw.flush();}}}

输入转换流输入的过程,是一个对字节进行解码的过程;

输出转换流输出的过程,是一个对 字符进行编码的过程。

以为一个中文字符由两个字节组成,所以使用字节流读取一个中文字符需要读取两次,容易出乱码,而使用字符流只需要读取一次。

7 流操作的规律

因为学习的流对象太多,开发时不知道用哪个对象合适。可以通过以下四个步骤明确:

①明确数据源和目的

数据源:InputStream Reader

目的:OutputStream Writer

②明确数据是否纯文本数据

是:Reader

否:InputStream

③明确具体设备

源设备:

键盘:File

键盘:System.in(除非System.setIn()成别的设备)

内存:数组

网络:Socket流

目的设备

硬盘:File

控制台:System.out

内存:数组

网络:Socket流

④是否需要提高效率:

需要:设置缓冲区。BufferedReader BufferedWriter BufferedInputStream BufferedOutputStream

8 编码问题

任何Java识别的字符数据使用的都是“Unicode”码表(国际通用),

但是FileWriter写入本地文件使用的是本地编码(中国是“GBK”)

而OutputStreamWriter可以使用指定的编码将要写入流中的字符编码成字节。

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\Demo.txt"),"GBK");

8 File类

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

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

PS:流只能操作数据,不能操作文件。

File f = new File("d:\\demo\\b.txt")

</pre><pre name="code" class="java">class Day22{public static void main(String[] args){//方法一File f = new File("D:\\Day22.txt");//方法二File f1 = new File("D:\\","Day22.txt");//方法三File f2 = new File("D:\\");File f3 = new File(f2,"Day22.txt");
<span style="white-space:pre"></span>System.out.println(f);<span style="white-space:pre"></span><span style="white-space:pre"></span>System.out.println(f1);<span style="white-space:pre"></span>System.out.println(f2);<span style="white-space:pre"></span>System.out.println(f3);}}
D:\Day22.txt
D:\Day22.txt
D:\
D:\Day22.txt

PS:如果想要Windows和linux都成功识别路径分隔符”\“的话,就要用File.separator来代替

public class Day22{public static void main(String[] args){File f = new File("D:\\Day22.txt");File f1 = new File("D:"+File.separator+"Day22.txt");System.out.println(f);System.out.println(f1);}}
D:\Day22.txt
D:\Day22.txt

8.1 File类的常用方法

8.1.1 获取

getName();获取文件名

getAbsolutePath();获取绝对路径

getPath();获取路径。实质是File对象引用的路径,当时输入什么,现在就输出什么。

length();获取大小,返回long

lastModified();获取上次修改日期,返回long

getParent();获取父目录。只有引入的时候有写父目录才有,如果当时引入的时候只有文件名,则返回null

getAbsoluteFIle();获取绝对路径,但返回文件类对象的形式。与getAbsolute()可以以toString和new File()相互转换

class Day22{public static void main(String[] args){File f = new File("D:"+File.separator+"Demo.txt");File f1 = new File("Demo.txt");String name = f.getName();String name1 = f1.getName();String abPath = f.getAbsolutePath();String abPath1 = f1.getAbsolutePath();String path = f.getPath();String path1 = f1.getPath();long len = f.length();long len1 = f1.length();long modi = f.lastModified();Date d = new Date(modi);long modi1 = f1.lastModified();String parent = f.getParent();String parent1 = f1.getParent();System.out.println(name);System.out.println(name1);System.out.println(abPath);System.out.println(abPath1);System.out.println(path);System.out.println(path1);System.out.println(len);System.out.println(len1);System.out.println(modi+"..."+d);System.out.println(modi1);System.out.println(parent);System.out.println(parent1);}}
Demo.txt
Demo.txt
D:\Demo.txt
E:\Java Workshop\demo\Demo.txt
D:\Demo.txt
Demo.txt
82
0
1444759011857...Wed Oct 14 01:56:51 CST 2015
0
D:\
null


8.1.2 创建和删除

boolean createNewFile(); 创建一个文件,并返回创建结果true或false,如果文件不存在,则创建,如果文件已存在,则不出案件,并返回false创建失败。跟输出流不一样,输出流是覆盖或续写文件。如果目录不存在,报错IOException。

boolean deleteFile(); 删除文件或文件夹,并返回删除结果。当删除文件夹时,如果文件夹中有文件,则删除失败,并返回false
void deleteOnExit();在程序退出时删除。当文件处理的途中报错了,一般程序卡死后deleteFile就没有被执行,文件变成垃圾文件占用空间。用了deleteOnExit就可以在程序关闭时删除指定文件。这个方法没有返回值。

boolean mkdir();创建目录,不可以创建多级目录

boolean mkdirs();创建目录,可以创建多记目录

class Day22{public static void main(String[] args) throws IOException{File f = new File("D:\\2\\3\\4\\5");File f3 = new File("D:\\2\\3\\4\\5\\demo.txt");boolean f2 = f.mkdirs();boolean f1 = f3.createNewFile();System.out.println(f2);System.out.println(f1);}}

8.1.3 判断

boolean exists();判断文件是否存在。可以用于流操作前判断文件是否存在,避免IOException

boolean isFile();判断是否文件

boolean isDirectory();判断是否文件夹

boolean isHidden();判断是否隐藏

boolean isAbsolute();判断是否

PS:记住,因为如果引用的对象本来就不存在,那么判断isFile和isDirectory都会返回false。

所以判断是文件或这是文件夹之前,最好先判断文件是否存在exists();

class Day22{public static void main(String[] args){File f = new File("D:\\haha.txt");boolean b1 = f.exists();boolean b2 = f.isFile();boolean b3 = f.isDirectory();System.out.println(b1);System.out.println(b2);System.out.println(b3);}}
false
false
false


8.1.4 重命名

boolean renameTo("新名字");重命名并返回是否命名成功。重命名还有移动文件的功能。

class Day22{public static void main(String[] args){File f = new File("D:\\3.txt");File f1 = new File("D:\\1\\3.txt");f.renameTo(f1);}}


8.1.5 获取目录

String[] list();获取目录下的文件以及文件夹的名称,包含隐藏文件。

调用list方法的File对象中封装的必须是目录,否则会产生NullPointerException

如果访问的是系统级目录也会发生空指针异常

如果目录存在但是没有内容,会返回一个数组,但是长度为0;

File[] listRoot();获取根目录盘符。返回一个File[]列表

File[] listFiles();获取目录,返回File()列表。注意与list区分,一个是获取目录的路径,并返回String。一个是获取目录的对象,返回File[]。


8.2 FilenameFilter

boolean FilenameFilte是一个接口,用于过滤文件名,下面只有一个方法accept(File dir, String name),返回真假表示是否符合要求。

FilenameFilter作为list()的参数加入,在list()的过程中过滤。

class Day22{public static void main(String[] args){File f = new File("E:\\");String[] arr = f.list(new FilenameFilter(){public boolean accept(File f,String name){return name.endsWith(".exe");}});for(String s :arr)System.out.println(s);}}
这个FilenameFilter以匿名内部类的形式存在。

示例:获取C盘下的隐藏文件

class Day22{public static void main(String[] args){File f = new File("c:\\");String[] arr = f.list(new FilenameFilter(){public boolean accept(File f,String name){return f.isHidden();}});for(String s :arr)System.out.println(s);}}

8.3 递归

函数自身直接或者间接调用到自身。

一个功能在被重复调用,并每次使用时,参与运算的结果和上一次调用有关,这时可以用递归来解决问题。

PS:

①递归一定要明确结束条件,否则会溢出。

②注意一下递归的次数。


示例:计算1到10的迪递加

public class Day22{public static void main(String[] args){int getSum =getSum(10);System.out.println(getSum);}public static int getSum(int x){if(x==1){return 1;}return x+getSum(x-1);}}


8.4 删除目录

在windows中,删除目录是从里往外删的,

既然是从里往外删除,就需要用到递归


9 Properties集合

该集合是属于Map——HashTable下的一个子类,

具备Map的特点,存储的都是键值对,而且键值都是String类型

因为配置信息一般都是存储在硬盘类,而它是以Map集合(键值对)的形式存在的,

所以properties集合是集合和IO技术相结合的集合容器。

可以用于存放键值对形式的配置文件。


9.1 Properties的常用方法

存储和取出:

setProperty(String key,Strng value);存入键值对,如果已有该key,value会覆盖

getProperty(key);根据键返回值。

stringPropertyNames();返回一个存储键的Set集合。

list(PrintStream out);把属性列表输出到指定的输出流。

load(InputStream in);从字节输入流读取属性列表。

load(Reader in);从字符输入流读取属性列表。

store(OutputStream out,String comment);

store(Writer out,String commet);将属性列表写入输出流。

ublic class Day22{public static void main(String[] args) throws IOException{Properties prop = new Properties();prop.setProperty("abc","1");prop.setProperty("def","2");prop.setProperty("ghi","2");//prop.list(System.out);FileOutputStream fos = new FileOutputStream("D:\\java prop.txt");prop.store(fos,"haha");fos.close();}}

9.2 Properties存储配置文件

因为Properties里资源更多的时候不是从程序里直接取得(不需要IO流),而是从配置文件里提取,所以Properties必须包含操作文件的方法。

* * 想将文件中的键值数据存到集合中进行操作 */public class Day24{public static void main(String[] args) throws IOException{//1,用一个流和文件关联,//读取一行数据,将该行数据用“=”进行切割//等号左边作为键,右边作为值,存入到Properties集合中即可BufferedReader br = new BufferedReader(new FileReader("E:\\Demotest\\peizhi.txt"));Properties pro = new Properties();String line = null;while((line=br.readLine())!=null){String[] temp=line.split("=");pro.setProperty(temp[0],temp[1]);}br.close();System.out.println(pro);Set<String> s = pro.stringPropertyNames();Iterator<String> it = s.iterator();while(it.hasNext()){String temp = it.next();System.out.println(temp+"=="+pro.getProperty(temp));}}}
{赵六=60, 王五=40, 刘七=70, 张三=30, 李四=100}
赵六==60
王五==40
张三==30
刘七==70
李四==100


但是载入Properties不能保证跟源文件顺序一致(因为Properties是HashTable的子类)

实际上这就是Properties的load(InputStream is)或load(Reader r)



10 IO包中的其它类


10.1 打印流

printWriter与PrintStream:可以直接操作输入流和文件。是一个装饰类。

PrintStream为其它输出流添加了功能,除了常规的write(),还有print()方法,能够方便地打印各种数据值表示形式。

与其它输出流不同,PrintSteam永远不会抛出IOException。

PrintStream打印的所有字符都是用平台默认字符编码转换为字节。

在需要

PrintStream

可以对基本数据类型进行直接操作。能保证数据原样性将数据打印出去。(而Write只能输出最低八位)

构造函数可以接收的参数类型:

①File对象。File

②字符串路径。String

③字节输出流。OutputStream


PrintWriter

构造函数可以接收的参数类型:

①File对象。File

②字符串路径。String

③字节输出流。OutputStream

④字符输出流。Writer

public class Day23{public static void main(String[] args) throws IOException{PrintStream ps = new PrintStream("D:\\ps.txt");//write(int b)方法只能 写入最低八位,变成aps.write(97);//print()方法可以将97先变成字符串保持原样将数据打印到目的地ps.print(97);ps.print("示例文件");ps.close();}}


PrintWriter是非常有用的方法,它可以直接println数据,并且把true作为参数传入可以自动刷新。

(但是File类不可以,因为autoFlush只能用与流,File类不是流,但是用FileWriter(“file”)代替,就可以刷新了)

public class Day23{public static void main(String[] args) throws IOException{BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));PrintWriter out = new PrintWriter(new FileWriter("D:\\haha2。txt"),true);String line = null;while((line=bufr.readLine())!=null){if("over".equals(line))break;out.println(line.toUpperCase());}}}


10.2 序列流

SequenceInputStream:对多个流进行串联合并。它没有对应的OutputStream。

即讲多个流对象拼成一个流对象。

class Day23{public static void main(String[] args) throws IOException{Vector<FileInputStream> v = new Vector<FileInputStream>();//创建集合,与三个需要被合并的文件相关联v.add(new FileInputStream("D:\\1.txt"));v.add(new FileInputStream("D:\\2.txt"));v.add(new FileInputStream("D:\\4.txt"));Enumeration<FileInputStream> e = v.elements(); //迭代器迭代 SequenceInputStream sis = new SequenceInputStream(e);//用序列流将迭代器导入  PrintStream ps = new PrintStream("D:\\5.txt");  byte[] b= new byte[1024]; int len =0; while((len=sis.read(b))!=-1) ps.write(b,0,len);  sis.close(); ps.close();}}



注意a97那里,SequenceInputStream是不自动添加换行的,会紧随上个文件的末尾进行续写。


11 文件切割

原理是用固定大小的数组对读取的文件进行“分组”存放到硬盘。

public class Day24 {public static void main(String[] args) throws IOException{splitFile();}public static void splitFile() throws IOException{FileInputStream fis  = new FileInputStream("D:\\5.txt");FileOutputStream fos = null;<span style="white-space:pre"></span>//创建输出流用于“分组”输出到文件byte[] buf = new byte[60];int len = 0;int count = 1;while((len=fis.read(buf))!=-1){fos = new FileOutputStream("D:\\"+(count++)+".part"); //输出流运用到循坏内fos.write(buf,0,len);fos.close();}}}



12 文件合并

前面的程序用Vector方法来合并,效率较低。

用ArrayList来存放更高效,

但是序列流SequenceInputStream只能用Enumeration来做参数,所以中间要变换一下。

public class Day24{public static void main(String[] args) throws IOException{//创建更高效的ArrayList来收纳需要被合并的文件ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();for(int x=1;x<=3;x++)al.add(new FileInputStream("D:\\"+x+".part"));Iterator<FileInputStream> it = al.iterator();//创建Enumeration对象Enumeration<FileInputStream> en = new Enumeration<FileInputStream>(){public boolean hasMoreElements(){return it.hasNext();}public FileInputStream nextElement(){return it.next();}};//创建序列流,但是序列流要输入Enumeration对象做参数SequenceInputStream sis = new SequenceInputStream(en);//创建字符输出流,输出合并后的文件FileOutputStream fos = new FileOutputStream("D:\\3.txt");//用数组操作输出byte[] b = new byte[1024*500];int len = 0;while((len=sis.read(b))!=-1)fos.write(b,0,len);fos.close();}}


13 操作对象

ObjectInputStream和ObjectOutputStream,是可以直接操作对象的流

意义:我们知道流是可以操作数据的,现在对象被封装成一个对象。

对象本身存在于堆内存当中,程序一旦关闭,堆内存就被释放。

这个流就可以把堆内存的对象存放在硬盘上。——这个行为叫做对象的持久化存储,或对象的序列化。Serializable


可以直接操作基本数据类型


13.1 构造方法:

ObjectOutputStream();为完全重新实现ObjectOutputStream的子类提供一种方法,让它不必分配仅由ObjectOutputStream的实现使用的私有数据。

ObjectOutputStream(OutputStream out);创建写入指定OutputStream的dObjectOutputStream。


13.2 常用方法:

writeObject(Object obj);写入一个类。

public class Day24{public static void main(String[] args) throws IOException {writeObj();}public static void writeObj() throws IOException{File f = new File("E:\\Demotest\\oos.txt");ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));oos.writeObject(new Person("zhangsan",16));oos.close();}}class Person{private String name;private int age;Person(String name, int age){this.name=name;this.age=age;}}
然而报错了,

Exception in thread "main" java.io.NotSerializableException: demo.Person


13.2 对象序列化

类通过实现Java.io.Serializable接口可以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化(即可把信息保存到硬盘上)。可序列化类的所有子类本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

就是说Serializable仅仅是一个标识性接口,可简单理解为一个“邮戳”,戳了就视作被“邮局”认可为“信件”了。

class Person implements Serializable{ //序列化private String name;private int age;Person(String name, int age){this.name=name;this.age=age;}}

PS:如果把类序列化之后,类的语句有变化的话,通过系列化后的文件来活得类的方法就会失败。因为该类已经变了。

除非:

①把类语句修正过来。

②给该类设定一个自定义的uid

class Person implements Serializable{private static final long serialVersionUID = 525L; //自定义UIDprivate String name;private int age;Person(String name, int age){this.name=name;this.age=age;}public void print(){System.out.println(name+"..."+age);}}

14 管道流——建议用于多线程
PipedInputStream和PipedOutputStream

输入输出可以直接进行连接。

一般来说读取流和写出流没有直接联系,要想把读取的数据输出到内存外的设备,中间需要字符串或byte数组来承接寄来。

管道流的引入就是为了直接把输入流和输出流连接上,通过结合线城使用。


不建议管道流用于单线程因为这样可能引起死锁:输入流没有数据让输出流输出,输出流等待,因为输出流和输入流是同一线程(单线程),所以输入流无法输入数据。


14.1 PipedInputStream常用方法

connect(PipedOutputStream pos);连接相应的输出管道流。

public class Day24{public static void main(String[] args) throws IOException {PipedInputStream pis = new PipedInputStream();PipedOutputStream pos = new PipedOutputStream();pis.connect(pos);Read r = new Read(pis);Write w = new Write(pos);Thread t1= new Thread(r);Thread t2 = new Thread(w);t1.start();t2.start();}}class Read implements Runnable{ //设置读取流private PipedInputStream pis =null;Read(PipedInputStream pis){this.pis =pis;}public void run(){ //读取线程的工作byte[] buf = new byte[1024];try {int len = pis.read(buf);String s = new String(buf,0,len);System.out.println(s);pis.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}class Write implements Runnable{ //设置写入线程private PipedOutputStream pos =null;Write(PipedOutputStream pos){this.pos=pos;}public void run(){ //运行写入线程try {pos.write("piped lai la".getBytes());pos.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
这个过程有点像生产者和消费者。其中PipedOutputStream是生产者,PipedInputStream是消费者。


15 此类的实例支持对随机访问文件的读取和写入。

RandomAccessFile


16 操作基本数据类型的类

DataInputStream和DataoutputStream

public class Day24{public static void main(String[] args) throws IOException {DataOutputStream dos = new DataOutputStream(new FileOutputStream("E:\\Demotest\\Demo.txt"));dos.writeBoolean(true);//1个字节dos.writeInt(65);//4个字节dos.writeChar('a') ; //2个字节dos.writeChars("ok"); //4个字节dos.close();}}

17 操作字节数组的类

ByteArrayInputStream和ByteArrayOutputStream

可以直接操作数组中类

特点:作为缓冲区的数组会随着数据不断写入而自动增长(其它流需要设定比如1024字节),可以使用toString()和toString()获取数据

关闭ByteArrayInputStream无效,因为实际上它并没有和其它设备打交道,所以此类中的方法在关闭此流后仍可被调用,而不会产生任何IOException。

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

ByteArrayOutputStream:在构造的时候,不用定义数据目的,因为该对象中已经内部分装了一个可变长度的字节数组。

这就是数据目的地。

public class Day24{public static void main(String[] args) {ByteArrayInputStream bais = new ByteArrayInputStream("abcdefg".getBytes());ByteArrayOutputStream baos = new ByteArrayOutputStream();int by =0; //不用在创建数组,因为数组已经封装在baos内。while((by=bais.read())!=-1)baos.write(by);System.out.println(baos.size());<span style="white-space:pre">System.out.println(baos.toString());</span>}}
7
abcdefg

PS:设备总结:

源设备:

键盘:System.in硬盘:File Stream内存:Array Stream

目的设备:

控制台:System.out硬盘:File Stream内存:Array Stream



0 0