Java IO流总结

IO流可以简单的理解为两种设备的中间介质,类似于管道,我们要做的就是把这个管道搭建好。有了管道以后就可以两个设备进行一些操作(读写,复制)。

IO流根据操作数据分为字节流和字符流,根据流向分为输入流和输出流(相对于内存而言),任何一个流必须包含数据和流向。以下四种是IO流的四大基类。下面看一下它们之间的关系。

根据Java命名规则可以想到:前面是功能的扩展,后缀是所属的类型。eg:FileOutputStream 是对文件操作的类,他是字节输出流。

字节流用于操作二进制数据(非文本数据,操作文本数据会乱码),字符流用于操作文本数据。1字符=2字节,其实字符流本身也是字节流,只不过加上一个编码而已。

四大基本是抽象类,无法直接使用,要找其子类,下面是复制文件的一段Demo

FileInputStream   FileOutputStream

 

 

public class Copy {
	public static void main(String[] args) throws IOException {
		//把E盘的txt读取到流中
		FileInputStream fis = new FileInputStream(new File("E:/a.ma4"));
		//把读取到的文件写入到D盘
		FileOutputStream fos = new FileOutputStream("D:/a.mp4");
		int len=0;
		//循环读取数据的同时也向D盘写入数据(连读带写)
		while((len=fis.read())!=-1){
			fos.write(len);
		}
		//记得关流,不然有可能内存溢出
		fis.close();
		fos.close();
		System.out.println("文件复制成功");
	}
}

 

BufferedInputStream BufferedOutputStream

上面的Demo对于小文件的复制是没任何问题的,但是对硬盘的损害较大,如果是大文件的话这种速度就会特别慢,这个时候就需要用到缓冲区了。

BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);

BufferedReader  BufferedWriter

BufferedReader br = new BufferedReader(new FileReader("E:/a.txt"));
br.readLine();
BufferedWriter bw = new BufferedWriter(new FileWriter("D:/a.txt"));

缓冲区有一个特别厉害的方法就是br.readLin();能够读取一行的数据,有的时候为了这一个方法可以为流套上该功能。

对于比较熟悉java的童鞋可能会很自然的理解,父类是向上的抽取共同的属性,子类是对父类功能的扩展,那么应该每一个中都会有一个Buffer的子类吗,其实这样做是可以的,只不过他们的体系太过于臃肿,因为他们的子类都有缓冲区功能,并且这些功能都相同,所以可以单独将缓冲区进行封装变成对象,缓冲区用到的wriite方法还是被包装的,并且增加了高效性,这种叫做装饰设计模式。
缓冲区的实质是定义字节数组,把读到的数据不立即写入目标设备,而是暂时存入缓冲区中,等满了,在一次性写入目标设备。知道原理之后试一下自定义缓冲区

 

 

public class Copy {
	public static void main(String[] args) throws IOException {
		//把E盘的txt读取到流中
		FileInputStream fis = new FileInputStream(new File("E:/a.mp4"));
		//把读取到的文件写入到D盘
		FileOutputStream fos = new FileOutputStream("D:/a.mp4");
		int len=0;
		byte buf [] = new byte[1024];
		//fis.read(buf) 把读到的数据放在buf数组里,返回值是buf数组的长度
		while((len=fis.read(buf))!=-1){
			fos.write(buf, 0, buf.length);
		}
		//记得关流,不然有可能内存溢出
		fis.close();
		fos.close();
		System.out.println("文件复制成功");
	}
}

如果操作的是文本的话,直接把上面的字节流换成字符流即可。

转换流:

如果操作文本文件使用的本地默认编码表完成编码。可以使用FileReader,或者FileWriter,这样比较方便,如果转换文本的同时还需要指定编码这时必须使用转换流来做,

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("b.txt"),"gbk");

打印流:

PrintStream:字节打印流:

特点:
1,构造函数接收File对象,字符串路径,字节输出流。意味着打印目的可以有很多。
2,该对象具备特有的方法 打印方法 print println,可以打印任何类型的数据。
3,特有的print方法可以保持任意类型数据表现形式的原样性(原理:数据转换字符串在写入),将数据输出到目的地。
对于OutputStream父类中的write,是将数据的最低字节写出去。

PrintWriter:字符打印流。
特点:
1,当操作的数据是字符时,可以选择PrintWriter,比PrintStream要方便。
2,它的构造函数可以接收 File对象,字符串路径,字节输出流,字符输出流。
3,构造函数中,如果参数是输出流,那么可以通过指定另一个参数true完成自动刷新,该true对println方法有效。

在需要保持数据原样性使用此流。

SequenceInputStream(合并流):

合并流是将多个字节流读取流合并为一个字节读取流,在进行read()的时候,读取到最后一个流的末尾才返回-1,也就是说多个流都能够正确读完。一般用于把多个碎片文件组成一个文件。下面是切割文件 and 合并文件的Demo

切割文件代码:

 

	public static void main(String[] args) throws IOException {
		File file = new File("D:/a.mp4");
		File dir = new File("D:\\aaa");
		if (!dir.exists()) {
			dir.mkdirs();
		}
		// 创建一个流读取被分割的数据
		FileInputStream fis = new FileInputStream(file);
		// 定义多个流的引用
		FileOutputStream fos = null;
		// 创建缓冲区
		byte buf[] = new byte[1024*1024];
		int len = 0;
		// 计数器
		int count = 1;
		while ((len = fis.read(buf)) != -1) {
			//核心代码,同一个数据,用不同的流写入,并重新起名
			fos = new FileOutputStream(new File(dir, count++ + ".part"));
			fos.write(buf, 0, len);
			fos.close();
		}
		//把切割后的信息存储到properties,方面以后合并
		Properties prop = new Properties();
		//给配置文件put键值对(该方法实际调用父类Hashtable的put方法)
		prop.setProperty("partcount", count+"");
		prop.setProperty("filename", file.getName());
		//用流存储到硬盘中
		fos = new FileOutputStream(new File(dir,count+".properties"));
		prop.store(fos, "save file info");
		fis.close();
	}

上面的切割完成的文件后缀可以没有,也可以任意,只不过一般切割的后缀都写part。

 

合并文件代码:

 

	public static void main(String[] args) throws IOException {
		File dir = new File("D:/aaa");
		// 遍历dir目录并过滤,得到后缀是properties
		File[] files = dir.listFiles(new FilterName(".properties"));
		// 获取配置文件
		File file = files[0];
		// 把文件装成流
		FileInputStream fis = new FileInputStream(file);
		Properties prop = new Properties();
		// 设置数据源
		prop.load(fis);
		String filename = prop.getProperty("filename");
		int count = Integer.parseInt(prop.getProperty("partcount"));

		// 获取该目录下的所有碎片文件。 ==============================================
		File[] partFiles = dir.listFiles(new FilterName(".part"));
		ArrayList<InputStream> list = new ArrayList<InputStream>();
		// 把每个文件都用流来读取 添加到集合中
		for (int i = 0; i < count; i++) {
			list.add(new FileInputStream(partFiles[0]));
		}
		// 把流合并
		Enumeration<InputStream> enumeration = Collections.enumeration(list);
		SequenceInputStream sis = new SequenceInputStream(enumeration);
		//合并完成之后将整个文件写入
		FileOutputStream fos = new FileOutputStream(new File("D:/",
				filename));
		byte[] buf = new byte[1024];
		int len = 0;
		//count个流读到最后一个流的结尾处才返回-1
		while ((len = sis.read(buf)) != -1) {
			fos.write(buf, 0, len);
		}
		fos.close();
		sis.close();
	}


过滤文件代码:

 

 

class FilterName implements FilenameFilter{
	private String suffix;
	public FilterName(String suffix) {
		super();
		this.suffix = suffix;
	}

	@Override
	public boolean accept(File dir, String name) {
		return name.endsWith(suffix);
	}

 

 

 

 

 

 

 

 

 

RandomAccessFile:

特点:
1,即可读取,又可以写入。
2,内部维护了一个大型的byte数组,通过对数组的操作完成读取和写入。
3,通过getFilePointer方法获取指针的位置,还可以通过seek方法设置指针的位置。
4,该对象的内容应该封装了字节输入流和字节输出流。
5,该对象只能操作文件。

通过seek方法操作指针,可以从这个数组中的任意位置上进行读和写
可以完成对数据的修改。

注意:数据必须有规律。

 

		RandomAccessFile raf = new RandomAccessFile("ranacc.txt", "rw");
		// 从指定位置写数据
		raf.write("张三".getBytes());
		raf.writeInt(97);
		raf.write("小强".getBytes());
		raf.writeInt(99);

		// 往指定位置写入数据。
		raf.seek(3 * 8);
		raf.write("哈哈".getBytes());
		raf.writeInt(108);
		raf.close();
		// 随机读
		raf.seek(1 * 8);// 随机的读取。只要指定指针的位置即可。
		byte[] buf = new byte[4];// 因为上面写张三97小强99,所以他们是有规律的,如果没有规律则无法读取
		raf.read(buf);
		String name = new String(buf);
		int age = raf.readInt();
		System.out.println("name=" + name);
		System.out.println("age=" + age);
		System.out.println("pos:" + raf.getFilePointer());


迅雷的断点续传就是利用多线程分块下载,使用的技术就是该种存储

 

 

LineNumberReader 

 

	public static void main(String[] args) throws IOException {
		FileReader fr = new FileReader("D:/a.txt");
		// 未FileReader套上LineNumberReader实现功能扩展
		LineNumberReader lnr = new LineNumberReader(fr);
		String line = null;
		// 设置行数从100开始
		lnr.setLineNumber(100);
		while ((line = lnr.readLine()) != null) {
			// 计数原理:读到/r/n的时候计数变量++
			System.out.println(lnr.getLineNumber() + ":" + line);
		}
		lnr.close();
	}

 

  PipedInputStream:

 

顾名思义,就是将输入和输入连接在一起,将输入流读到的数据交给输出流,通常写在两个线程里,写在一个有可能会死锁,只要有一个线程不存在,那么认为该管道已坏

 

public class PipedStream {

	public static void main(String[] args) throws IOException {

		PipedInputStream input = new PipedInputStream();
		PipedOutputStream output = new PipedOutputStream();
		input.connect(output);
		new Thread(new Input(input)).start();
		new Thread(new Output(output)).start();
	}

}

class Input implements Runnable {
	private PipedInputStream in;
	Input(PipedInputStream in) {
		this.in = in;
	}
	public void run() {
		try {
			byte[] buf = new byte[1024];
			int len = in.read(buf);
			String s = new String(buf, 0, len);
			System.out.println("s=" + s);
			in.close();
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
}

class Output implements Runnable {
	private PipedOutputStream out;

	Output(PipedOutputStream out) {
		this.out = out;
	}

	public void run() {

		try {
			Thread.sleep(5000);
			out.write("hi,管道来了!".getBytes());
		} catch (Exception e) {
		}
	}
}

 

 

 

 

 

 

设备是内存的流对象。

ByteArrayInputStream      ByteArrayOutputStream       CharArrayReader  CharArrayWriter

 

		ByteArrayInputStream bis = new ByteArrayInputStream("abcedf".getBytes());
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		int ch = 0;
		while((ch=bis.read())!=-1){
			bos.write(ch);
		}
		System.out.println(bos.toString());

 

 

 

 

 

 

在什么情况下使用何种流:

1.明确数据:操作的数据是纯文本吗?是Reader   不是InputStream

2.明确设备:

硬盘:FileXXX
内存:数组。
网络:socket  socket.getInputStream();

3.明确额外功能:

需要转换?是,使用转换流。InputStreamReader OutputStreamWriter
需要高效?是,使用缓冲区。Buffered
需要其他?    

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值