java必须精通第五课

java IO

java IO操作主要是指使用java进行输入、输出操作,java中的所有操作类都存在java.io包中。
在整个java包中最重要的就是5个类和1个接口,5个类是指FIle、InputStream、OutputStream、Reader、Writer;一个接口指的是Serializable。

操作文件的类–File

在整个io包中,唯一与文件本身有关的类就是File类,使用File类可以创建或者删除文件等常用操作,要使用File类则首先观察File类的构造方法:
public File(String pathname) 实例化File类必须设置好路径
可以发现,如果要使用一个File类,则必须向File类的构造方法中传递一个文件路径。
例如:D盘下的test.txt文件。路径必须写成"d:\test.txt",其中\表示一个
windows使用反斜杠表示目录的分隔符“\”
Linux使用正斜杠表示目录的分隔符 “/”
File类中的主要方法

实例操作;创建一个新文件
File类对象实例化完成之后,就可以使用creatNewFile创建一个新文件,但是此方法使用了throws关键字,
所以在使用时必须使用try....catch进行异常处理(在D盘创建一个test.txt文件)
public class Test3{
	
public static void main(String[] args) {
	File file=new File("d:\\test.txt");
	try {
		file.createNewFile();
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	  }
   }
}
结果是在D盘已经创建好了文件。

那么java程序本身具有可移植性的特点,则在编写路径时,最好可以根据程序所在的操作系统自动使用符合
本地操作系统要求的分隔符,这样才能达到可移植性的目的。
要使用本地分隔符就要用到FIle类的两个常量:pathSeparator,separator。
public class Test3{
	
public static void main(String[] args) {
	System.out.println(File.pathSeparator);
	System.out.println(File.separator);
 }
}
结果:
;
\

最好的做法是使用常量来表示路径:
public class Test3{
	
public static void main(String[] args) {
	String path="d:"+File.separator+"test.txt";
	File file=new File(path);
	try {
		file.createNewFile();
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	System.out.println(File.pathSeparator);
	System.out.println(File.separator);
 }
}
在开发的时候一定要使用FIle.separator分隔符,这样Windows开发,Linux部署才不会出现问题。


实例二,删除一个指定的文件
public class Test3{
	
public static void main(String[] args) {
	String path="d:"+File.separator+"test.txt";
	File file=new File(path);
		file.delete();
 }
}
文件被删除了,但是这种做法存在一个问题,就是应该先判断文件存在不存在,在进行删除操作。
public class Test3{
	
public static void main(String[] args) {
	String path="d:"+File.separator+"test.txt";
	File file=new File(path);
	boolean b=file.exists();
	if(b) {
		file.delete();
		System.out.println("文件存在,并已经被删除。");
	}else {
		System.out.println("无法找到您要删除的文件。");
	}
		
 }
}
结果:
文件存在,并已经被删除。

实例:创建一个文件夹
可以使用File类指定一个文件夹,直接使用mkdir()方法就可以完成。
public class Test3{
	
public static void main(String[] args) {
	String path="d:"+File.separator+"testDir";
	File file=new File(path);
	file.mkdir();
 	}
}
结果:文件夹已创建

实例:列出指定目录的全部文件
如果现在给出一个目录,可以直接列出目录中的内容,有两个方法:
public String[] list()  列出全部名称,返回一个字符串数组
public File[] listFiles()  列出一个完整路径,返回File对象数组。

public class Test3{
public static void main(String[] args) {
	String path="d:"+File.separator+"testDir";
	File file=new File(path);
	String arr[]=file.list();
	File f[]=file.listFiles();
	System.out.println(Arrays.toString(arr));
	for(int i=0;i<f.length;i++) {
		System.out.println(f[i]);
	}
  }
}
结果:
[cui.txt, dir, sheng.txt, xian.txt]
d:\testDir\cui.txt
d:\testDir\dir
d:\testDir\sheng.txt
d:\testDir\xian.txt

实例:判断一个给定的路径是否是目录
public class Test3{
public static void main(String[] args) {
	String path="d:"+File.separator+"testDir";
	File file=new File(path);
	System.out.println(file.isDirectory());
  }
}
结果:
true

实例:列出指定目录和子目录的全部文件
public class Test3{
public static void main(String[] args) {
	String path="d:"+File.separator+"testDir";
	File file=new File(path);
	print(file);
  }
public static void print(File file) {
	if(file != null) {
		if(file.isDirectory()) {
			File f[]=file.listFiles();
			if(f != null) {
				for(int i=0;i<f.length;i++) {
					print(f[i]);
				}
			}
		}else{
			System.out.println(file);
		}
	}
  }
}
结果:
d:\testDir\cui.txt
d:\testDir\dir\cui1.txt
d:\testDir\sheng.txt
d:\testDir\xian.txt
RandomAccessFile类

File类只是针对文件本身进行操作,而如果要对文件内容进行操作,则可以使用RandomAccessFile类,此类属于随机读取类,可以随机的读取一个文件中指定位置的数据。
假如有数据:
zhangsan,30。
lisi,31。
王五,32。
那如果要使用RandomAccessFile类读取信息时,就可以将zhangsan的信息跳过,相当于在文件设置了一个指针,根据指针的位置读取,但是如果想实现这样的功能,则每个数据的长度应该保持一致。所以在设置姓名时应该设置8位,设置年龄时应该设置4位。
设置字符串
Random常用方法
需要注意的是:如果使用rw方式声明RandomAccessFile对象时,要写入的文件不存在系统将自动创建。

使用RandomAccessFile类写入文件
为了保证进行随机读取,所以写入的名字都是8个字节,写入的数字是固定的4个字节
public class Test3{
public static void main(String[] args) throws Exception {
	String path="d:"+File.separator+"testDir"+File.separator+"xian.txt";
	File file=new File(path);
	RandomAccessFile raf=null; //声明一个RandomAccessFile对象
	raf=new RandomAccessFile(file, "rw");  //以读写的方式打开文件,自动创建新文件
	String name=null;
	int age=0;
	
	name="zhangsan";
	age=30;
	raf.writeBytes(name);
	raf.writeInt(age);
	
	name="lisi    ";
	age=31;
	raf.writeBytes(name);
	raf.writeInt(age);
	
	name="wangwu  ";
	age=33;
	raf.writeBytes(name);
	raf.writeInt(age);
	raf.close();	
  }
}

public class Test2{
	public static void main(String[] args) throws Exception{
		String path="d:"+File.separator+"testDir"+File.separator+"xian.txt";
		File file=new File(path);
		RandomAccessFile raf=null;
		raf=new RandomAccessFile(file, "r");
		
		String name=null;
		int age=0;
		
		byte[] b2=new byte[8]; //准备空间读取姓名
		raf.skipBytes(12);
		for(int i=0;i<b2.length;i++) {//循环读取前八个内容
			b2[i]=raf.readByte();
		}
		name=new String(b2);
		age=raf.readInt();
		System.out.println("第二个人信息"+name+"---"+age);
		raf.seek(0); //指针回到文件头
		
		byte[] b1=new byte[8]; //准备空间读取姓名
		for(int j=0;j<b1.length;j++) {//循环读取前八个内容
			b1[j]=raf.readByte();
		}
		name=new String(b1);
		age=raf.readInt();
		System.out.println("第1个人信息"+name+"---"+age);
		raf.skipBytes(12);
		
		byte[] b3=new byte[8]; //准备空间读取姓名
		for(int z=0;z<b3.length;z++) {//循环读取前八个内容
			b3[z]=raf.readByte();
		}
		name=new String(b3);
		age=raf.readInt();
		System.out.println("第3个人信息"+name+"---"+age);
		raf.close();
	}
}
结果:
第二个人信息lisi    ---31
第1个人信息zhangsan---30
第3个人信息wangwu  ---33

可以发现,程序中随机跳过12位读取信息,也可以回到开始点冲洗读取,随机读写流可以实现对文件内容的操作。
但一般情况下,操作文件内容往往会使用字节流或字符流。
字节流与字符流基本操作

在程序中,所有的数据都是以流的方式进行传输或者保存的,程序需要数据时要使用输入流读取数据,而当程序要将一些数据保存起来时,就要使用输出流。
在java.io包中流的操作主要有字节流、字符流两大类,两类都有输入和输出操作。在字节流输出数据主要使用OutputStream类完成,输入使用的是InputStream类。字符流依靠Writer输出,reader类输入。

1.字节流
字节流的主要操作byte类型数据,以byte数组为准。
字节输出流:OutputStream
OutputStream是整个io包中字节输出流最大的父类,定义如下:
public abstract class OuputStream extends Object implements Closeable,Flushable
从以上定义可以发现,OutputStream类是一个抽象类,如果要使用此类,则首先必须通过子类实例化对象。
如果现在要操作的是一个文件,则可以使用FileOutputStream类,通过向上转型后,可以为OutputStream
实例化。

OutputStream类常用方法

此时使用FileOutputStream子类来进行实例化OutputStream
public FileOutputStream(FIle file) throws FileNotFoundException
操作时必须接收File类的实例,指明要输出的文件路径

例子:向文件中写入字符串
public class Test3{
public static void main(String[] args) throws Exception{
	//第一步  使用File类找到一个文件
	String pathname="d:"+File.separator+"testDir"+File.separator+"cui.txt";
	File file=new File(pathname);
	//第二步  通过子类实例化父类
	OutputStream out=null;
	out=new FileOutputStream(file);
	//第三步  进行写操作
	//准备一个字符串
	String s="Hello China!!!";
	//只能输出byte数组,所以将字符串变成byte数组
	byte b[]=s.getBytes();
	out.write(b);
	out.close();
	}
}
结果:
文件中存在字符串 Hello China!!!

***追加新内容
如果重新执行上面的程序,肯定会覆盖文件中已有的内容,那么可以使用FileOutputStream向文件中
追加内容,FileOutputStream另一个构造方法如下:
public FileOutputStream(File file,boolean append) throws FileNotFoundException
在构造方法中,如果将append的值设为true,则表示在文件的末尾追加内容。
public class Test3{
public static void main(String[] args) throws Exception{
	//第一步  使用File类找到一个文件
	String pathname="d:"+File.separator+"testDir"+File.separator+"cui.txt";
	File file=new File(pathname);
	//第二步  通过子类实例化父类
	OutputStream out=null;
	out=new FileOutputStream(file,true);
	//第三步  进行写操作
	//准备一个字符串
	String s="Hello China!!!";
	//只能输出byte数组,所以将字符串变成byte数组
	byte b[]=s.getBytes();
	out.write(b);
	out.close();
 }
}
结果:
文件中出现两遍 Hello China!!!Hello China!!! 代表追加成功

如何增加换行呢,只需要在想要换行的地方加上"\r\n",就可以实现换行的功能了

关于Closeable和Flushable两个接口说明:
在OutputStream类中实现了这两个接口
Closebale接口定义:
public interface Closeable{
void close() throws IOException
}
Flushable接口定义:
public interface Closeable{
void flush() throws IOException
}
这两个接口的作用从其定义的方法中可以发现,Closeable表示可关闭,Flushable表示可刷新。而且在OutputStream中已经有两个这样方法的实现。而用户一般不关心这两个接口,直接调用OutputStream类就可以。

2.字节输入流InputStream
既然程序可以向文件中写内容,则可以通过InputStream把文件中的内容读取出来。InputStream定义如下:
public abstract class InputStream extends Object implements Closeable
与OutputStream类一样,InputStream类也是一个抽象类,如果要从文件中读取内容,实现的子类肯定是FileInputStream。

InputStream常用方法

例子:从文件中读取内容
public class Test3{
public static void main(String[] args) throws Exception{
	//第一步  使用File类找到一个文件
	String pathname="d:"+File.separator+"testDir"+File.separator+"cui.txt";
	File file=new File(pathname);
	//第二步  通过子类实例化父类
	InputStream in=null;
	in=new FileInputStream(file);
	byte b[]=new byte[1024];
	in.read(b);
	in.close();
	System.out.println(new String(b));
 	}
}
结果:
Hello China!!!Hello China!!!(后面带了很多空格)  和上面我们写入文件的结果一样

内容已经被读出来了,但是发现后面有许多空格,这是因为开辟的byte数组大小为1024,而实际内容并没有那么多。
也就是存在许多空白的空间,在将byte数组变为字符串时,也将这剩余的空间转为字符串,这样的操作肯定是
不合理的,要解决就要看read方法,这个read方法有一个返回值,此返回值表示数组中写入了多少数据
public class Test3{
public static void main(String[] args) throws Exception{
	//第一步  使用File类找到一个文件
	String pathname="d:"+File.separator+"testDir"+File.separator+"cui.txt";
	File file=new File(pathname);
	//第二步  通过子类实例化父类
	InputStream in=null;
	in=new FileInputStream(file);
	byte b[]=new byte[1024];
	int len =in.read(b);
	in.close();
	System.out.println(new String(b,0,len));
 }
}
这样结果就没有多余的空格了。

以上程序虽然最后输出没有多余的空格了,但是内容完全没有占用足够的byte数组,但是程序依然开辟了很多
空间,但是这样肯定造成了资源浪费,要根据文件的数量开辟内存空间,就要使用File类中length方法,
这个方法可以取得文件的大小。
public class Test3{
public static void main(String[] args) throws Exception{
	//第一步  使用File类找到一个文件
	String pathname="d:"+File.separator+"testDir"+File.separator+"cui.txt";
	File file=new File(pathname);
	//第二步  通过子类实例化父类
	InputStream in=null;
	int l=(int) file.length();
	in=new FileInputStream(file);
	byte b[]=new byte[l];
	in.read(b);
	in.close();
	System.out.println(new String(b));
 	}
}
这样结果就没有多余的空格占位了。
(也可以使用for循环进行循环数组一个一个读取)

另一种读取方式:临时变量读取:
public class Test3{
public static void main(String[] args) throws Exception{
	//第一步  使用File类找到一个文件
	String pathname="d:"+File.separator+"testDir"+File.separator+"cui.txt";
	File file=new File(pathname);
	//第二步  通过子类实例化父类
	InputStream in=null;
	int l=(int) file.length();
	in=new FileInputStream(file);
	byte b[]=new byte[l];
	int temp=0;
	int len=0;
	while((temp=in.read())!=-1) {
		b[len]=(byte) temp;
		len++;
	}
	in.close();
	System.out.println(new String(b,0,len));
  }
}
结果和之前一样,成功读取Hello China!!!Hello China!!!
这里注意read()方法,一个一个读了,不是一次性读取到数组中了读的。
以上程序要判断temp接收到内容是否是-1,正常情况下,不会返回-1,只有当输入流的内容已经读到底部
末尾,才会返回这个数字,通过此数字可以判断输入流中是否还有其他内容。
3.字符输出流Writer
在程序中一个字符等于两个字节,java提供了两个专门操作字符的类--Writer和Reader
	Writer字符输出流定义:
	public abstract class Wrtirer extends Object implements Appendable,
	Closeable,Flushable
	此类本身也是一个抽象类,如果要使用此类,则肯定要使用其子类进行实例化,如果是向文件中写入内容
	应该使用FileWriter的子类。
Appendable接口表示内容可以被追加,	接收的参数是CharSequence,实际上String类就实现了此接口,
所以可以直接通过此接口的方法向输出流中追加内容。

Writer类的常用方法

FileWriter类构造方法
public FileWriter(File file) throws IOEception

范例:向文件中写入数据
public class Test3{
public static void main(String[] args) throws Exception{
	//第一步  使用File类找到一个文件
	String pathname="d:"+File.separator+"testDir"+File.separator+"sheng.txt";
	File file=new File(pathname);
	//第二步  通过子类实例化父类
	Writer writer=null;
	writer=new FileWriter(file);
	String str="This is book!";
	writer.write(str);
	writer.close();
  }
}
结果:
This is book!

整个程序与OutputStream的操作流没有什么太大的差异,唯一的好处就是可以直接输出字符串,而不用将
字符串变成byte数组再输出。

范例:追加文件
public class Test3{
public static void main(String[] args) throws Exception{
	//第一步  使用File类找到一个文件
	String pathname="d:"+File.separator+"testDir"+File.separator+"sheng.txt";
	File file=new File(pathname);
	//第二步  通过子类实例化父类
	Writer writer=null;
	writer=new FileWriter(file,true);
	String str="\r\n do you want to by it? ";
	writer.write(str);
	writer.close();
  }
}
结果:
This is book!
 do you want to by it? 

4.字符输入流Reader
Reader是使用字符的方式从文件中取出数据
public abstract class Reader extends Object implements Readable,Closeable
Reader本身也是抽象类,如果现在要从文件中读取内容,则可以直接使用FileReader子类。

Reader常用类

FileReader的构造方法如下:
public FileReader(File file) throws FileNotFoundException

实例:从文件中读取内容
public class Test3{
public static void main(String[] args) throws Exception{
	//第一步  使用File类找到一个文件
	String pathname="d:"+File.separator+"testDir"+File.separator+"sheng.txt";
	File file=new File(pathname);
	//第二步  通过子类实例化父类
	Reader read=null;
	read=new FileReader(file);
	char c[]=new char[(int) file.length()];
	int len=0;
	int temp=0;
	while((temp=read.read())!=-1) {
		c[len]= (char) temp;
		len++;
	}
	read.close();
	System.out.println(new String(c));
	
  }
}
结果:
This is book!
 do you want to by it? 

字节流与字符流的区别
字节流和字符流使用上非常相似,两者除了操作代码的不同之外,另一个重要的不同就是:
字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流在操作时,使用了缓冲区
那么主要体现是哪里呢?
就是当我们使用输出流时不关闭它(OutputStream和Writer),这时候会发现,字节流的直接内容输出到文件中,而字符流则没有被输出到文件中。
证明字节流是操作文件本身
字符流使用了缓冲区,而在关闭字符流时,会强制性的将缓冲区的内容进行输出,但如果程序没有关闭,则缓冲区的内容无法输出。
如果在不关闭也想将字符流的内容全部输出,可以使用flush()方法,清理缓冲区。
缓冲区
缓冲区是什么?缓冲区可以简单的理解是一段内存区域
某些情况下,如果一个程序频繁的操作一个资源(文件或者数据库),则性能会很低,此时为了提升性能,就可以将一部分数据暂时读到内存的一块区域中,以后直接从此区域读取数据即可,这样可以提高性能。
使用字节流好还是使用字符流好?
首先所有的文件在硬盘或者在传输时,都是以字节的方式进行的。,包括图片都是按照字节的方式存储的,而字符只有在内存中才会形成,所以在开发中字节流使用广泛。

实例:文件边读边写(文件复制功能)
public class Test3{
public static void main(String[] args) throws Exception{
	//第一步  使用File类找到一个文件
	String pathname1="d:"+File.separator+"testDir"+File.separator+"sheng.txt";
	String pathname2="d:"+File.separator+"testDir"+File.separator+"cui.txt";
	File file1=new File(pathname1);
	File file2=new File(pathname2);
	
	if(!file1.exists()) {
		System.out.println("源文件不存在");
	}
	InputStream in=null;
	OutputStream out=null;
	
	in=new FileInputStream(file1);
	out=new FileOutputStream(file2);
	
	int temp=0;
	if(in!=null && out!=null) {
		while((temp=in.read())!=-1) {
				out.write(temp);
		}
	}
	in.close();
	out.close();
  }
}
结果:
成功将文件内容复制到另一个文件中。
转换流
转换流OutputStreamWriter类与InputStreamReader类
整个IO包实际上分为字节流和字符流,但是除了这两个流之外,还存在一组字节流-字符流的转换类。
OutputStreamWriter:是一个Writer的子类,将输出的字符流变为字节流,即将一个字符流的输出对象变为
字节流输出对象。
InputStreamReader:是Reader的子类,将输入的字节流变为字符流,即将一个字节流的输入对象变为
字符流的输入对象。

转换过程

从上图可以清楚的发现,不管如何操作,最终全部是以字节的形式保存在文件中。
OutputStreamWriter的构造方法如下:
public OutputStreamWriter(OutputStream out)

正常我们往文件中写入内容,通过OutputStream写入对吧,但是这种形式只能通过byte字节的形式,
现在我们要通过字符的形式写入,这是就可以通过OutputStreamWriter类了。
public class Test3{
public static void main(String[] args) throws Exception{
	//第一步  使用File类找到一个文件
	String pathname1="d:"+File.separator+"testDir"+File.separator+"cui.txt";
	File file1=new File(pathname1);
	
	if(!file1.exists()) {
		System.out.println("源文件不存在");
	}
	Writer out=null;
	
	out=new OutputStreamWriter(new FileOutputStream(file1));
	out.write("who are you?");
	out.close();
  }
}
结果:文件成功存储了who are you?这句话。

同时,我们也可以把这句话读出来,之前我们都是通过InputStream把文件中的数据读到byte字节流数组中,
但是现在我们可以通过InputStreamWriter把字节流转换成字符流读取到字符流char的数组中。
public class Test3{
public static void main(String[] args) throws Exception{
	//第一步  使用File类找到一个文件
	String pathname1="d:"+File.separator+"testDir"+File.separator+"cui.txt";
	File file1=new File(pathname1);
	
	if(!file1.exists()) {
		System.out.println("源文件不存在");
	}
	Reader in=null;
	
	in=new InputStreamReader(new FileInputStream(file1));
	char c[]=new char[(int) file1.length()];
	in.read(c);
	in.close();
	System.out.println(new String(c));
  }
}
结果:
who are you?  已经成功读取出来了。

特殊说明:FileWriter和FileReader分别是InputStreamReader和OutputStreamWriter的直接子类。
内存操作流

前面讲解的程序中都是输出和输入都是从文件中来的,当然也可以将输出的位置设置在内存上,
此时就要使用ByteArrayInputStream、ByteArrayOutputStream来完成输入和输出的功能。
ByteArrayInputStream是将数据读取到内存中
ByteArrayOutputStream是把内存中的数据读取出来
ByteArrayInputStream
ByteArrayOutputSream

实例:字母大小写转换
public class Test3{
public static void main(String[] args) throws Exception{
	String str="ABCDEFG";
	ByteArrayInputStream bis=null;
	bis=new ByteArrayInputStream(str.getBytes());
	ByteArrayOutputStream bos=null;
	bos=new ByteArrayOutputStream();
	int temp=0;
	while((temp=bis.read())!=-1) {
		char c=(char) temp;
		bos.write(Character.toLowerCase(c));
		}
	String newStr=bos.toString();
	bis.close();
	bos.close();
	System.out.println(newStr);
	}
}
结果:abcdefg

内存操作流一般在生成一些临时的信息才会使用,而这些临时的信息如果要保存在文件中,则代码执行完毕肯定
还要删除这些临时文件,那么此时使用内存操作流是最合适的。
管道流

管道流主要的作用是可以进行两个线程之间,分为管道输出流(PipedOutputStream)和管道输入流(PipedInputStram)。如果要进行管道输出,则必须把输出流连到输入流上。
在PipedOutputStream类上有如下方法进行连接:
public void connect(PipedInputStream snk) throws IOException
管道流操作

线程之间管道流通信
输出流管道
class Outer implements Runnable{
	
	private PipedOutputStream pos=null;
	public Outer() {
		this.pos=new PipedOutputStream();
	}
	
	@Override
	public void run() {
		String str="I am String";
		try {
			this.pos.write(str.getBytes());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			this.pos.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public PipedOutputStream getPos() {
		return pos;
	}
}
输入流管道
class Inter implements Runnable{
	
	private PipedInputStream pis=null;
	
	public Inter() {
		pis=new PipedInputStream();
	}
	
	@Override
	public void run() {
		byte b[]=new byte[1024];
		int len=0;
		try {
			len=this.pis.read(b);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			this.pis.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(new String(b,0,len));
	}
	public PipedInputStream getPis() {
		return pis;
	}
}
执行线程
public class MyTask{
	public static void main(String[] args){
		Outer out=new Outer();
		Inter in=new Inter();
		try {
			out.getPos().connect(in.getPis());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		new Thread(out).start();
		new Thread(in).start();
	}
}
结果:
I am String  成功打印

关于输出流
你构建输出流对象时是文件,就会内容输出到指定文件
File file2=new File(pathname2);
out=new FileOutputStream(file2);
while((temp=in.read())!=-1) {
out.write(temp);
}
如果你没构建具体的文件输出对象,而是其他输出对象,那就存储在其他输出对象中
String str=“ABCDEFG”;
ByteArrayInputStream bis=null;
bis=new ByteArrayInputStream(str.getBytes());
ByteArrayOutputStream bos=null;
bos=new ByteArrayOutputStream();
int temp=0;
while((temp=bis.read())!=-1) {
char c=(char) temp;
bos.write(Character.toLowerCase©);
}
String newStr=bos.toString();
bis.close();
bos.close();
System.out.println(newStr);
这时内容就在bos这个对象中(即ByteArrayOutputStream中)

打印流

在整个IO包中,打印流是输入信息最方便的类,主要包含字节打印流(PrintStream)和字符打印流(PrintWriter)。打印流提供了非常方便的打印功能,可以打印任何数据类型,如小数、整数、字符串等。PrintStream是OutputStream的子类。
PrintStream类常用方法
在PrintStream类中定义的构造方法可以清楚的发现,有一个构造方法可以直接接受OutputStream类的实例。这是因为与OutputStream类相比,PrintStream类更加方便的输出数据,这就好像将OutputStream类重新包装一下,使之输出更加方便。
打印流把输出流重新装饰一下,就像送给别人礼物,需要把礼物包装一下,才会更加好看,所以这样的设计称为装饰设计模式。

实例:使用PrintStream打印流输出
public class Test3{
public static void main(String[] args) throws FileNotFoundException{
	String path="d:"+File.separator+"testDir"+File.separator+"cui.txt";
	File file=new File(path);
	PrintStream ps=null;
	ps=new PrintStream(new FileOutputStream(file));
	ps.print("I ");
	ps.print("am ");
	ps.print("PrintStream");
	ps.close();
	}
}
结果:
I am PrintStream  文件中已成功打印出这个字符串

使用打印流进行格式化输出
格式化输出

格式化打印
public class Test3{
public static void main(String[] args) throws FileNotFoundException{
	String path="d:"+File.separator+"testDir"+File.separator+"cui.txt";
	File file=new File(path);
	PrintStream ps=null;
	ps=new PrintStream(new FileOutputStream(file));
	String name="CuiXianSheng";
	int age=10;
	float score=99.99f;
	char sex='M';
	ps.printf("姓名:%s; 年龄:%d; 成绩:%f; 性别:%c",name,age,score,sex);
	ps.close();
	}
}
结果:
姓名:CuiXianSheng; 年龄:10; 成绩:99.989998; 性别:M
成功打印到文件中(注意使用printf方法格式化打印,还有%在左边。)

System类对IO的支持
System类有三个主要的方法
System.out
System.err
System.in
System.out和System.err都是PrintStream的实例化对象,两者都是输出错误信息,而System.out是将信息显示给用户看,是正常的显示信息。而System.err的信息恰好相反,是不希望用户看到的,会直接在后台打印,是专门显示错误的。
System.in实际上是一个键盘输入流,可以从键盘上读取数据。
System为这三个提供了重定向的方法
重定向方法
就是可以改变输出的位置或者改变输入的位置
具体使用如下:
实例
注意的是不要随便修改System.err重定向的位置,因为它表示输出的数据不希望用户看到。

BufferedReader类

BufferedReader类用于从缓冲区中读取内容,所有的输入字节数据都将放到缓冲区中
BufferedReader类的常用方法
从以上定义可以看出来,构造方法中只能接收字符输入流的实例,所以必须使用字符输入流Reader和字节输入流的转换类InputStreamReader,将字节输入流System.in变为字符流。
转化实例

public class Test3{
public static void main(String[] args){
	BufferedReader br=null;
	br=new BufferedReader(new InputStreamReader(System.in));
	String str=null;
	System.out.println("请输入你想输入的内容:");
	try {
		str=br.readLine();
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	System.out.println("您输入的内容是:"+str);
  }
}
结果:
请输入你想输入的内容:
随便输入的 哈哈哈
您输入的内容是:随便输入的 哈哈哈

可以发现程序没有长度限制,也可以正常接收中文。
数据操作流

在IO包中,提供了两个与平台无关的数据操作流,分别是数据输出流DataOutputStream和数据输入流DataInputStream,通常数据输出流会按照一定的格式将数据输出,在通过数据输入流按照一定的格式将数据读入,这样可以方便对数据进行处理。
DataOutputStream是OutputStream的子类
public class DataOutputStream extends FilterOutputStream implements DataOutput
DataOutput是数据的输出接口,其中定义了各种数据的输出操作方法。例如在DataOutputStream类中的
各种WriterXxx()方法就是此接口的定义,但在数据输出时,一般都会直接使用DataOutputStream,只有在
对象序列化时才有可能直接操作DataOutput接口。
DataOutputStream类常用方法
订单数据

使用DataOutputStream输出这个订单到指定文件
public class Test3{
public static void main(String[] args) throws Exception{
	String path="d:"+File.separator+"testDir"+File.separator+"cui.txt";
	File file=new File(path);
	DataOutputStream dos=null;
	dos=new DataOutputStream(new FileOutputStream(file));
	String names[]= {"衬衣","手套","围巾"};
	float price[]= {98.3f,30.3f,50.5f};
	int nums[]= {3,2,1};
	for(int i=0;i<names.length;i++) {
		dos.writeChars(names[i]);
		dos.writeChar('\t');
		dos.writeFloat(price[i]);
		dos.writeChar('\t');
		dos.writeInt(nums[i]);
		dos.writeChar('\n');
		}
		dos.close();
	}
}
DataOutputStream提供了将Java各种类型数据的输出方法,但是其将各种数据类型以二进制形式输出,
用户无法方便的进行查看。使用PrintWriter类的print()和PrintLn()方法可以轻松地实现将
Java的各种数据类型转换为字符串类型输出。
合并流SequenceInputStream

合并流的主要功能是将两个文件的内容合并成一个文件
合并流
常用方法

public class Test3{
public static void main(String[] args) throws Exception{
	String path1="d:"+File.separator+"testDir"+File.separator+"cui.txt";
	String path2="d:"+File.separator+"testDir"+File.separator+"xian.txt";
	String path3="d:"+File.separator+"testDir"+File.separator+"sheng.txt";
	File file1=new File(path1);
	File file2=new File(path2);
	File file3=new File(path3);
	InputStream in1=null;
	InputStream in2=null;
	SequenceInputStream in3=null;
	OutputStream out=null;
	in1=new FileInputStream(file1);
	in2=new FileInputStream(file2);
	in3=new SequenceInputStream(in1,in2);
	out=new FileOutputStream(file3);
	int temp=0;
	while((temp=in3.read())!=-1) {
		out.write(temp);
		}
	in3.close();
	in1.close();
	in2.close();
	out.close();
	}
}
结果:
I am firstI am second  成功cui.txt和xian.txt的内容写入到sheng.txt中

压缩流

在javaIO中,不仅可以实现ZIP压缩格式的输入、输出,也可以实现JAR以及GZIP文件的格式压缩
JAR的压缩支持类保存在java.util.jar包中,常用类有如下几个:
JAR压缩输出流:JarOutputStream
JAR压缩输入流:JarInputStream
JAR文件:JARFile
JAR实体:JAREntry
ZIP的压缩支持类保存在java.util.zip包中,常用类有如下几个:
ZIP压缩输出流:ZipOutputStream
ZIP压缩输入流:ZipInputStream
ZIP文件:ZIPFile
ZIP实体:ZIPEntry
GZIP是用于UNIX系统的文件压缩,在Linux经常会使用到*.gz文件,就是GZIP文件的格式。
GZIP压缩输出流:GZIPOutputStream
GZIP压缩输入流:GZIPInputStream
在每个压缩文件中都会存在多个子文件,那么每个子文件在java中就使用ZIPEntry表示。
ZipEntry类常用方法
要完成一个文件或者文件夹的压缩,则要使用ZipOutputStream。ZipOutputStream是OutputStream的子类。
ZipOutputStream常用方法

把cui.txt压缩成cui.zip
public class Test3{
public static void main(String[] args) throws Exception{
	String path1="d:"+File.separator+"testDir"+File.separator+"cui.txt";
	String path2="d:"+File.separator+"testDir"+File.separator+"cui.zip";
	File file=new File(path1);
	File fileZip=new File(path2);
	InputStream in=null;
	in=new FileInputStream(file);
	ZipOutputStream zos=null;
	//实例化压缩输出流对象,并指定压缩文件输出路径
	zos=new ZipOutputStream(new FileOutputStream(fileZip));
	//每一个被压缩的文件都用ZipEntry表示,需要为每一个压缩后的文件设置名称
	zos.putNextEntry(new ZipEntry(file.getName()));
	zos.setComment("I am Comment!");
	int temp=0;
	while((temp=in.read())!=-1) {
		zos.write(temp);
		}
	zos.close();
	in.close();
	}
}
结果:已成功压缩到指定目录,并压缩成cui.zip文件

压缩一个文件夹:
应该列出文件夹的全部文件,把每一个文件内容设置成ZipEntry对象,保存在压缩文件中。

执行流程

压缩一个文件夹
public class Test3{
public static void main(String[] args) throws Exception{
	String path1="d:"+File.separator+"testDir"+File.separator+"dir";
	String path2="d:"+File.separator+"testDir"+File.separator+"dir.zip";
	File file=new File(path1);
	File fileZip=new File(path2);
	InputStream in=null;
	ZipOutputStream zos=null;
	//实例化压缩输出流对象,并指定压缩文件输出路径
	zos=new ZipOutputStream(new FileOutputStream(fileZip));
	
	if(file.isDirectory()) {
		File files[]=file.listFiles();
		for(int i=0;i<files.length;i++) {
			in=new FileInputStream(files[i]);
			zos.putNextEntry(new ZipEntry(files[i].getName()));
			int temp=0;
			while((temp=in.read())!=-1) {
				zos.write(temp);
			}
			in.close();
		}
	}
	zos.close();
  }
}
ZipFile

在java中每一个压缩文件都可以用ZipFile表示,还可以使用ZipFile根据压缩后的文件名称找到每一个压缩文件中的ZipEntry,并将其进行解压缩的操作。
ZipFile类常用方法

基本使用:
public class Test3{
public static void main(String[] args) throws Exception{
	String path1="d:"+File.separator+"testDir"+File.separator+"cui.zip";
	File file=new File(path1);
	ZipFile zf=new ZipFile(file);
	System.out.print("压缩文件的名称为:"+zf.getName());
  }
}
结果:	
压缩文件的名称为:d:\testDir\cui.zip

单文件解压缩:
public class Test3{
public static void main(String[] args) throws Exception{
	String path1="d:"+File.separator+"testDir"+File.separator+"cui.zip";
	String path2="d:"+File.separator+"testDir"+File.separator+"cuiUNzip.txt";
	File file=new File(path1);
	//定义解压缩文件名称
	File fileUnzip=new File(path2);
	
	ZipFile zf=new ZipFile(file);
	ZipEntry ze=zf.getEntry("cui.txt");
	InputStream in=zf.getInputStream(ze);
	OutputStream out=new FileOutputStream(fileUnzip);
	int temp=0;
	while((temp=in.read())!=-1) {
		out.write(temp);
	}
	in.close();
	out.close();
  }
}
结果:文件已成功解压到cuiUNzip.txt文本文件中。

多文件解压缩:
通过ZipInputStream类中的getNextEntry()方法可以依次取得每一个ZipEntry,那么将此类与ZipFile
结合就可以对压缩文件夹进行解压缩操作。应该先根据ZIP对应的文件名对应的文件夹在硬盘上创建好,
然后才能把文件解压缩进去。而且在操作时对于每一个解压缩的文件都必须先创建(File类的createNewFile
方法可以创建新文件)后再将内容输出。
public class Test3{
public static void main(String[] args) throws Exception{
	String path1="d:"+File.separator+"testDir"+File.separator+"dir.zip";
	File file=new File(path1);
	//定义输出文件对象
	File fileUnzip=null;
	ZipFile zf=new ZipFile(file);//实例化ZupFile对象
	//实例化Zip输入流
	ZipInputStream zis=new ZipInputStream(new FileInputStream(file));
	
	ZipEntry ze=null;//定义ZipEntry对象,用来接收压缩文件中的每一个实体
	InputStream in=null;//定义输入流,用于读取每一个ZipEntry
	OutputStream out=null;//定义输出流,用于输出每一个实体内容。
	
	//得到每一个ZipEntry
	while((ze=zis.getNextEntry())!=null) {
		System.out.println("解压缩"+ze.getName()+"文件");
		fileUnzip=new File("d:"+File.separator+"testDir"+File.separator+"dir"
				+File.separator+ze.getName());
		if(!fileUnzip.getParentFile().exists()) {
			fileUnzip.getParentFile().mkdir();
		}
		if(!fileUnzip.exists()) {
			fileUnzip.createNewFile();
		}
		in=zf.getInputStream(ze);
		out=new FileOutputStream(fileUnzip);
		int temp=0;
		while((temp=in.read())!=-1) {
			out.write(temp);
		}
		in.close();
		out.close();
	}
  }
}
结果:
解压缩cui1.txt文件
解压缩sheng1.txt文件
解压缩xian1.txt文件
都已经成功解压到指定目录下。
回退流

在java的IO中所有的数据都是采用顺序的读取方式,即对于一个输入流来说,都是采用从头到尾的顺序读取,如果在输入流中的某个不需要的内容被读取进来,则只能通过程序将这些不需要的内容处理掉。为了解决这样的读取问题,在java中提供了一种回退输入流(PushbackInputStream,PushbackReader),可以把读取进来的某些数据重新退回到输入流的缓冲区中。
原理图
对于不需要的数据可以使用unread()方法,将内容重新发送到输入的缓冲区中。
PushbackInputStream
回退流

示例:
public class Test3{
public static void main(String[] args) throws Exception{
	String str="www.baidu.com!!!";
	PushbackInputStream pis=null;
	ByteArrayInputStream bis=null;
	bis=new ByteArrayInputStream(str.getBytes());
	pis=new PushbackInputStream(bis);
	System.out.println("开始读取数据");
	
	int temp=0;
	while((temp=pis.read())!=-1) {
		if(temp=='.') {
			pis.unread(temp);
			temp=pis.read();
			System.out.print("*");
		}else {
			System.out.print((char)temp);
		}
	}
  }
}
结果:
开始读取数据
www*baidu*com!!!

System.out.print((char)temp);这里不加char转换成字符会以ASCII显示字符
结果会是119119119*9897105100117*99111109333333
而119对应的ascii表就是W

字符编码

java常见编码简介:
在计算机世界里,任何的文字都是以指定文字编码的方式存在的,在java程序开发中最常见的是:
ISO8859-1,GBK/GB2312,unicode,UTF编码
ISO8859-1:属于单字节编码,最多只能表示0~255字符的范围,主要在英文上使用
GBK/GB2312:中文的国标编码,专门用来表示汉字,是双字节编码,如果在此编码出现中文,则使用GBK编码或者GB2312。GBK可以表示简体中文和繁体中文,GB2312只能表示简体中文。GBK兼容GB2312
unicode:java中使用此编码方式,是最标准的一种编码,使用十六进制表示编码。但此编码不兼容ISO8859-1编码
UTF:由于unicode不支持ISO8859-1编码,而且容易占用更多空间,而且对于英文字母也需要使用两个字节编码,这样使用unicode不便于传输和存储。因此产生了UTF编码。UTF编码兼容了ISO8859-1编码,同时也可以用来表示所有的语音字符,不过UTF编码是不定长编码,每一个字符长度为1~6个字符不等,一般在中文网页使用此编码,可以节省空间。

得到本机的编码显示:
public class Test3{
public static void main(String[] args) throws Exception{
		System.out.print(System.getProperty("file.encoding"));
	}
}
结果:UTF-8

对象序列化

对象序列化就是把一个对象变为二进制数据流的一种方法,通过对象序列化可以方便的实现对象的传输和存储。
如果一个类的对象想被序列化,则对象所在类必须java.io.Serializable接口。
public interface Serializable{}
可以发现此接口没有定义任何方法,所以此接口是一个标识接口,标识一个类具备了被序列化的能力。

public class Person {
	private String name;
	private int age;
	
	public Person(String name,int age) {
		this.name=name;
		this.age=age;
	}
	public String toString() {
		return "姓名:"+this.name+";"+"年龄:"+this.age;
	}
}
以上Person类已经实现了序列化接口,所以此类的对象是可以经过二进制数据流	进行传输的。而如果要完成对
象的输入或者输出,还要依靠对象的输出流(ObjectOuputStream)和对象的输入流(ObjectInputStream)
使用ObjectOutputStream对象输出流称为序列化,使用ObjectInputStream对象输入流称为反序列化

对象的序列化和反序列化兼容问题:
在对象进行序列化和反序列化操作时,要考虑JDK版本问题,如果序列化的JDK版本和反序列化的JDK版本不统一则就有可能造成异常,所以在序列化操作中引入一个serialVersionUID的常量,可以通过此常量来验证版本的一致性。
在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应的实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。当实现java.io.Serializable接口的实体类没有显式的定义一个名为serialVersionUID、类型为long的变量时,java序列化机制在编译时会自动生成一个此版本的serialVersionUID。当然,如果不希望通过编译生成,也可以直接显式的定义一个名为serialVersionUID类型为long的变量,只要不修改这个变量值的序列化实体,都可以互相进行串行化和反串行化。
public static final long serialVersionUID=1L;

*****ObjectOutputStream***** 对象输出流
实例:将对象数据保存到文件
public class Test3{
public static void main(String[] args) throws Exception{
		String path="d:"+File.separator+"testDir"+File.separator+"cui.txt";
		File file=new File(path);
		ObjectOutputStream oos=null;
		
		OutputStream out=new FileOutputStream(file);
		oos=new ObjectOutputStream(out);
		oos.writeObject(new Person("cui", 18));
		oos.close();
	}
}
结果:
 sr 
myTest.Person        I ageL namet Ljava/lang/String;xp   t cui

文件里面是乱的。因为这里保存的都是二进制数据,所以我们不能随意修改会破坏保存格式。

序列化都那些进行了序列化?
在面向对象上,每个对象都具备相同的方法,但是每个对象的属性不一定相同,也就是说对象保存的只有属性信息,那么在序列化操作时,也同样是这个道理,只有属性被序列化。

*****ObjectInputStream***** 对象输入流
实例:读出保存在文件中的对象信息。
public class Test3{
public static void main(String[] args) throws Exception{
		String path="d:"+File.separator+"testDir"+File.separator+"cui.txt";
		File file=new File(path);
		
		ObjectInputStream ois=null;
		InputStream in=null;
		in=new FileInputStream(file);
		ois=new ObjectInputStream(in);
		Object objec=ois.readObject();
		ois.close();
		System.out.print(objec);
	}
}
结果:
姓名:cui;年龄:18

另一种实例化对象方法,是部分实例化,Serializable是全部实例化。
Externalizable是部分实例化。
实现过程和Serializable一样,只不过多了两个覆写的方法(前面也是实体类实现Externalizable接口,之后在实体类中指定实例化的具体参数)

public class Person implements Externalizable{
	public static final long serialVersionUID=1L;
	private String name;
	private int age;
	
	public Person(String name,int age) {
		this.name=name;
		this.age=age;
	}

	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeObject(this.name);//保存姓名属性
		out.writeObject(this.age);//保存年龄属性
		
	}
	@Override
	public void readExternal(ObjectInput in) throws IOException, 
	ClassNotFoundException {
		this.name=(String) in.readObject();//读取姓名属性
		this.age=in.readInt();//读取年龄属性
	}
	public String toString() {
		return "姓名:"+this.name+";"+"年龄:"+this.age;
		
	}
}
使用使用依然使用对象的输入输出流(ObjectInputStream和ObjectOutputStream),使用方法不变。
只是实体类有变化,变化如上所示。

两个接口对比

从实际情况来看,因为Serializable接口使用方便,所以出现较多。
transient关键字
Serializable接口实际上是将对象中的全部属性进行序列化。但是使用transient关键字就可以指定某个属性不被序列化。
private transient String name;
这样name这个属性就不会被序列化。放入文件中,取值会是空值。因为根本没有实例化到文件里面。
本章要点
在这里插入图片描述
在这里插入图片描述
2.在java中使用File类表示文件本身,可以直接使用此类完成文件的各种操作,如创建删除等。
3.RandomAccessFile类可以从指定的位置开始读取信息,但是要求文件中的各个数据的保存长度必须固定。
4.输入输出流主要分为字节流和字符流两种,但在传输过程中字节流操作较多,字符流在操作时使用了缓冲区,而字节流没有使用缓冲区。
5.字节流和字符流都是以抽象类的形式定义的,根据其使用的子类不同输入或输出的位置也不同。
6.在IO包中可以使用OutputStreamWriter和InputStreamReader完成字符流与字节流之间的转换操作。
7.使用ByteArrayInputStream和ByteOutputStream类可以对内存进行输入输出操作。
8.在线程之间进行输入输出通信,主要使用PipedInputStream和PipedOutputStream
9.在IO输出时最好使用打印流(PrintStream,PrintWriter),这样可以方便的输出各种类型数据
10.BuffereReader可以直接从缓冲去读取数据。
11.数据操作流(DataOuputStream/DataInputStream),提供了与平台无关的数据操作。
12.使用合并流(SequenceIputStream),可以将两个文件内容进行合并。
13.如果数据量过大,则可以使用压缩流压缩数据,在java中支持ZIP、JAR、GZIP共3中压缩格式。
14.使用回退流可以将不需要的数据回退到数据缓冲区中以待重新读取。
15.造成字符乱码的根本原因就是在于程序编码与本地编码不一致。
16.对象序列化可以将内存中的数据转化为二进制数据,但对象所在的类必须事先Serializable接口,一个类的属性如果使用transient关键字声明,则此属性内容将不会被序列化。
17.对象的输入输出主要使用ObjectInputStream和ObjectOutputStream两个类完成。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值