【Java基础知识】IO流--字节流读写数据以及复制文件的几种方式

1、IO的分类

A、按照流向【参照物JVM】
输入流 : 读取数据
输出流 : 写出数据
B、按照数据类型
(1)字节流
a、字节输入流 读取数据 InputStream
b、字节输出流 写出数据 OutputStream
(2)字符流
a、字符输入流 读取数据 Reader
b、字符输出流 写出数据 Writer

注意:一般我们在探讨IO流的时候,如果没有明确说明按哪种分类来说,默认情况下是按照数据类型来分的。

注意:每种基类的子类都是以父类名作为后缀名。
XxxOutputStream
XxxInputStream
XxxReader
XxxWriter

2、字节流的抽象父类 及基本方法

InputStream【抽象类】输入流:JVM从中连续读取字节的对象。

int read():从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值,如果返回-1表示遇到流的末尾,结束。
int read(byte[] b):读入b.length个字节放到b中,并返回实际读入的字节。
int read(byte[] b,int off,int len):这个方法表示把流中的数据读到,数组b中,第off个开始的len个数组元素中。
void close():在操作完一个流后要使用此方法将其关闭, 系统就会释放与这个流相关的资源。

OutputStream【抽象类】输出流:JVM向其中连续写入的对象。

void write(int b):将指定的字节写入此输出流。
void write(byte[] b):将 b.length 个字节从指定的 byte 数组写入此输出流。
void write(byte[] b,int off, int len):将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
void flush():刷新此输出流,并强制写出所有缓冲的输出字节。【彻底完成输出并清空缓冲区】。

3、为何需要调用close()方法?为什么需要缓冲?使用缓冲区技术的优缺点?

3.1为什么需要调用close()方法?

尽管在调用流对象的,在没有引用变量指向它时会变成垃圾,最终垃圾回收器会自动回收。在程序创建一个IO流对象,除了Java程序中我们可见的实例对象外,还有系统本身的资源,Java垃圾回收器只能管理程序中的类的实例对象,没法去管理系统产生的资源,所以程序需要调用close方法,去通知系统释放其自身产生的资源。

3.2 为何需要缓冲区?缓冲技术的优缺点?

计算机访问外部设备,要比直接访问内存慢得多,如果我们每一次write方法的调用都直接写到外部设备(如直接写入硬盘文件),CPU就要花费更多的时间等待外部设备;如果我们开辟一个内存缓冲区,程序的每一次write方法都是写到这个内存缓冲区中,只有这个缓冲区被装满后,系统才将这个缓冲区的内容一次集中写到外部设备。
优点:有效的提高了CPU效率。
缺点:由于缓冲区,数据并没有立即写入到目标中去,就会造成一定的滞后。
缓冲区技术的实现是由编程语言本身决定的:
C语言:默认情况下就会使用缓冲区。
Java:有的类使用了缓冲区,有的类没有使用缓冲区。
flush()方法在缓冲区没有满的情况下,也将缓冲区的内容强制写入外设【刷新】。在调用close()方法,系统在关闭这个流之前也会将缓冲区的内容刷新到硬盘文件。

4、OutputStream输出流的write()方法使用

如何向文本中写入字节?如何实现数据追加?如何实现数据换行?

4.1 直接写入字节

public class FileOutputStreamDemo {
	public static void main(String[] args) throws IOException {
		FileOutputStream fos = new FileOutputStream("IO.txt");
		fos.write("hello,IO".getBytes());//获取本地编码的字节数组,并写入文本文件中。
		fos.write("java".getBytes());
		fos.close();
	}
}
/* IO.txt的内容:
   hello,IOjava
*/

4.2 写入字节的3种方式

public class FileOutputStreamDemo2 {
	public static void main(String[] args) throws IOException {
		//如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。
		FileOutputStream fos = new FileOutputStream("IO.txt", true);		
		fos.write(65); //65 -- 底层二进制数据	-- 通过记事本打开 -- 找65对应的字符值 -- a
		byte[] bys={97,98,99,100,101};
		fos.write(bys);
		fos.write(bys,1,3);
		fos.close();
	}
}
/*  IO.txt文本的内容:
	Aabcdebcd
*/

4.3关于数据换行

不同的系统针对不同的换行符号识别是不一样的。
1、windows:\r\n
2、linux:\n
3、Mac:\r

public class FileOutputStreamDemo3 {
	public static void main(String[] args) throws IOException {
		FileOutputStream fos = new FileOutputStream("IO.txt", true);
		// 写数据
		for (int x = 0; x < 3; x++) {
			fos.write(("Hello Java" + x).getBytes());
			fos.write("\r\n".getBytes());
		}
		fos.close();
	}
}
/*  IO.txt文本的内容:
Hello Java0
Hello Java1
Hello Java2
*/

4.4 IO流异常处理的代码

开发中还必须考虑异常处理情况下的输入流的操作

public class FileOutputStreamDemo4 {
	public static void main(String[] args) {
		//为了在finally里面能够看到该对象就必须定义到外面,为了访问不出问题,还必须给初始化值
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream("IO.txt");
			fos.write("Java,Hello".getBytes());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			//如果fos不是null,才需要close()
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

5、InputStream输入流的read()方法使用

输入流操作

public class FileInputStreamDemo {
	public static void main(String[] args) throws IOException {
		FileInputStream fis = new FileInputStream("IO.txt");
		int by = 0;
		//读取,赋值,判断
		while ((by = fis.read()) != -1) {
			System.out.print((char)by);
		}
		fis.close();
	}
}

6、通过IO流进行文件复制

6.1 基本的文件复制

public class CopyFileDemo {
	public static void main(String[] args) throws IOException {
		FileInputStream fis = new FileInputStream("a.txt");
		FileOutputStream fos = new FileOutputStream("b.txt");
		int by = 0;
		while ((by = fis.read()) != -1) {
			fos.write(by);
		}
		fos.close();
		fis.close();
	}
}

6.2 计算机是如何识别应该把2个字节转换为一个汉字?

在GBK字符编码集中,汉字是由2个字节组成,因为GBK兼容ISO-8859-1,正数的单字节已被占用
所以汉字的第一个字节必须为负数第二个字节大多也为负数。如:

public class StringDemo {
	public static void main(String[] args) throws UnsupportedEncodingException {
		String s1 = "Xyz123";
		String s2 = "手可摘星辰";
		byte[] bys1 = s1.getBytes();
		byte[] bys2 = s2.getBytes();
		System.out.println(Arrays.toString(bys1));
		System.out.println(Arrays.toString(bys2));
	}
}
/*  GBK编码字符集下:
	[88, 121, 122, 49, 50, 51]
	[-54, -42, -65, -55, -43, -86, -48, -57, -77, -67]
 * */

6.3 IO操作时定义一个字节数组作为缓存

 public class CopyFileDemo {
	public static void main(String[] args) throws IOException {
		FileInputStream fis = new FileInputStream("c:\\a.txt");
		FileOutputStream fos = new FileOutputStream("d:\\b.txt");
		byte[] bys = new byte[1024];
		int len = 0;
		while ((len = fis.read(bys)) != -1) {
			fos.write(bys, 0, len);
		}
		fos.close();
		fis.close();
	}
}

6.5 通过带有缓冲区的字节类【高效类】

写数据:BufferedOutputStream
读数据:BufferedInputStream
看源码:
BufferedOutputStream 继承【过滤流】 FilterOutputStream。进一步地重写过滤流方法中的一些方法,并且还可以提供一些额外的方法和字段。
FilterOutputStream继承自抽象类OutputStream,过滤类本身只是简单地重写那些将所有请求传递给所包含输出流的 OutputStream 的所有方法。
实际上把缓冲写在包装在类中,BufferedInputStream原理类似。
通过定义数组的方式比一次读取一个字节的方式快很多,拥有缓冲区效率提升很多。
Java在设计时提供缓冲区的字节类BufferedOutputStream和BufferedInputStream。
真正的底层读写数据还是依靠基本流对象来实现,见源码解析。
【BufferedOutputStream】源代码解析

public class BufferedOutputStream extends FilterOutputStream {

    protected byte buf[];	//内部缓冲区
    protected int count;    //缓冲区存储的字节个数
    public BufferedOutputStream(OutputStream out) {
        this(out, 8192);
    }

    public BufferedOutputStream(OutputStream out, int size) {
        super(out);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];//开辟一个缓冲区
    }

	//刷新内部缓冲区
    private void flushBuffer() throws IOException {
        if (count > 0) {
            out.write(buf, 0, count);
            count = 0;
        }
    }
	//将指定的字节写入此缓冲的输出流
    public synchronized void write(int b) throws IOException {
        if (count >= buf.length) {
            flushBuffer();
        }
        buf[count++] = (byte)b;
    }
	//将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此缓冲的输出流
    public synchronized void write(byte b[], int off, int len) throws IOException {
        if (len >= buf.length) {
            flushBuffer();
            out.write(b, off, len);
            return;
        }
        if (len > buf.length - count) {
            flushBuffer();
        }
        System.arraycopy(b, off, buf, count, len);
        count += len;
    }

    public synchronized void flush() throws IOException {
        flushBuffer();
        out.flush();
    }
}

6.7 比较4中IO复制操作的效率

/*
 * 需求:把F:\\红玫瑰.mp3【9.25M】复制到当前项目目录下的copy.mp4中
 */
public class CopyMp4Demo {
	public static void main(String[] args) throws IOException {
		long start = System.currentTimeMillis();
		method4("F:\\红玫瑰.mp3", "copy.mp3");
		long end = System.currentTimeMillis();
		System.out.println("共耗时:" + (end - start) + "毫秒");
	}
	
	//1、基本字节流一次读写一个字节
	public static void method1(String srcString, String destString)throws IOException {		
		FileInputStream fis = new FileInputStream(srcString);
		FileOutputStream fos = new FileOutputStream(destString);
		int by = 0;
		while ((by = fis.read()) != -1) {
			fos.write(by);
		}
		fos.close();
		fis.close();
	}
	//2、基本字节流一次读写一个字节数组
	public static void method2(String srcString, String destString)throws IOException {		
		FileInputStream fis = new FileInputStream(srcString);
		FileOutputStream fos = new FileOutputStream(destString);
		byte[] bys = new byte[1024];
		int len = 0;
		while ((len = fis.read(bys)) != -1) {
			fos.write(bys, 0, len);
		}
		fos.close();
		fis.close();
	}
	//3、高效字节流一次读写一个字节:
	public static void method3(String srcString, String destString)throws IOException {	
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcString));			
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destString));				
		int by = 0;
		while ((by = bis.read()) != -1) {
			bos.write(by);
		}
		bos.close();
		bis.close();
	}	

	//4、高效字节流一次读写一个字节数组:
	public static void method4(String srcString, String destString)throws IOException {		
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcString));		
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destString));	
		byte[] bys = new byte[1024];
		int len = 0;
		while ((len = bis.read(bys)) != -1) {
			bos.write(bys, 0, len);
		}
		bos.close();
		bis.close();
	}
}
/*
* 字节流4种方式复制文件所耗费的时间:
* 	method1基本字节流一次读写一个字节:	 共耗时:115906毫秒
* 	method2基本字节流一次读写一个字节数组:共耗时:342毫秒
* 	method3高效字节流一次读写一个字节:        共耗时:550毫秒
* 	method4高效字节流一次读写一个字节数组:共耗时:61毫秒
**/

7、IO流字符集可能出现的乱码

/*
 * 字节流读取中文可能出现的乱码问题:
 * a.txt文件的具体内容
 * 手可摘星辰
 * 2017.01.18.Java
 */
public class FileInputStreamDemo {
	public static void main(String[] args) throws IOException {
		FileInputStream fis = new FileInputStream("a.txt");		
		/*//读取数据,英文正常,中文乱码异常。【此方案仅对单字节字符有效】
		int by = 0;
		while ((by = fis.read()) != -1) {
		 System.out.print((char) by);
		} */
		/*运行结果:??????????
			2017.01.18.Java	*/	 
		//解决:采用系统默认的编码字符集 
		byte[] bys = new byte[1024];
		int len = 0;
		while ((len = fis.read(bys)) != -1) {
			System.out.print(new String(bys, 0, len));
		}
		fis.close();		
		/* 手可摘星辰
		 * 2017.01.18.Java*/
	}
}

关于字符串的编解码说明

/*
 * 乱码产生原因:编解码采用不同方案所致。
 * 解决方案:编解码采用同一套字符集。
 * 编码:byte[] -- String : new String(byte[] bytes, String CharsetName )
 * 解码:String -- byte[] : getBytes(String CharsetName);
 * */
public class StringDemo {
	public static void main(String[] args) throws UnsupportedEncodingException {
		String s = "中国";
		byte[] bys1 = s.getBytes(); 
		byte[] bys2 = s.getBytes("GBK");
		byte[] bys3 = s.getBytes("UTF-8");
		System.out.println(Arrays.toString(bys1));
		System.out.println(Arrays.toString(bys2));
		System.out.println(Arrays.toString(bys3));
		
		String s1 = new String(bys1); 
		String s2 = new String(bys2, "GBK"); 
		String s3 = new String(bys3, "UTF-8"); 
		System.out.println(s1);
		System.out.println(s2);
		System.out.println(s3);
	}
}
/*  运行结果:
	[-42, -48, -71, -6]
	[-42, -48, -71, -6]
	[-28, -72, -83, -27, -101, -67]
	中国
	中国
	中国
 * *
  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值