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

-------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 block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{ try {
				fr.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.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)方法只能 写入最低八位,变成a
		ps.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; //自定义UID
	private 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 block
			e.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 block
			e.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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值