首先要知道:这四个类是抽象类,是一切字符字节输入输出流的父类,因为是抽象类,所以要通过子类来实例化,不能直接实例化;
public abstract class InputStream implements Closeable; public abstract class OutputStream implements Closeable, Flushable; public abstract class Reader implements Readable, Closeable; public abstract class Writer implements Appendable, Closeable, Flushable;
1)FileInputStream:
public static void demo1() throws FileNotFoundException, IOException { FileInputStream fis = new FileInputStream("xxx.txt"); //创建流对象,个人认为,相当于一根管道 int x = fis.read(); //从硬盘上读取一个字节,硬盘上都是以二进制的形式存储 System.out.println(x); int y = fis.read(); System.out.println(y); int z = fis.read(); System.out.println(z); int a = fis.read(); System.out.println(a);//文件的结束标志:-1 int b = fis.read(); System.out.println(b); fis.close(); //关流释放资源 }
打印的结果为:97、98、99、-1、-1
/** * Reads a byte of data from this input stream. This method blocks * if no input is yet available.
* * * @return the next byte of data, or <code>-1</code> if the end of the file is reached. * 返回值为0到255的int类型的值,返回值为字符的ACSII值(如a就返回97,n就返回110). * @exception IOException if an I/O error occurs. */ public native int read() throws IOException; //空参构造,返回读到的内容(第五点阐述有参构造)
从最基本的开始,假如说相关路径下有文件"xxx.txt",文件上面有abc三个字母,从上面源码可以看出,调用一次read()方法,就读一个字母,返回下一个。结束的时候,就返回-1。
所以可以利用循环来判断:
private static void demo2() throws FileNotFoundException, IOException { FileInputStream fis = new FileInputStream("xxx.txt"); //创建流对象 int b; //文件的结束标志:-1,所以定义-1就结束了 while((b = fis.read()) != -1) { System.out.println(b); } fis.close(); }
read()方法读取的是一个字节,为什么返回是int,而不是byte(复制的,其实我看不懂)
因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式的存储的,如果每次读取都返回byte,有可能在读到中间的时候遇到111111111,
那么这11111111是byte类型的-1,我们的程序是遇到-1就会停止不读了,后面的数据就读不到了,所以在读取的时候用int类型接收,如果11111111会在其前面补上,
24个0凑足4个字节,那么byte类型的-1就变成int类型的255了这样可以保证整个数据读完,而结束标记的-1就是int类型。
2)FileOutputStream:
public static void demo1() throws FileNotFoundException, IOException { //创建字节输出流对象,如果没有就自动创建一个 FileOutputStream fos = new FileOutputStream("yyy.txt"); fos.write(97); //虽然写出的是一个int数,但是到文件上的是一个字节,会自动去除前三个8位 fos.write(98); fos.write(99); fos.close(); }
相关路径的文件"yyy.txt"就在里面写出了一个abc内容。
如果继续在上面写一个,如下:
public static void demo1() throws FileNotFoundException, IOException { //创建字节输出流对象,如果没有就自动创建一个 FileOutputStream fos = new FileOutputStream("yyy.txt"); // fos.write(97); //虽然写出的是一个int数,但是到文件上的是一个字节,会自动去除前三个8位 // fos.write(98); // fos.write(99); fos.write(100); fos.close(); }
则文件"yyy.txt"上面只会显示d内容,因为其会在原来的文件上面进行清空,再重新写。
******在创建对象的时候是如果没有这个文件会帮我创建出来
******如果有这个文件就会先将文件清空,是将里面的内容清空,再写入
*****如果不想文件清空,想续写,则在后面加一个布尔值就可以了。
private static void demo2() throws FileNotFoundException, IOException { FileOutputStream fos = new FileOutputStream("yyy.txt",true); //如果想续写就在第二个参数传true fos.write(97); fos.write(98); fos.close(); }
3)拷贝:核心代码就是下面几行
/* * 复制文件,图片,读一次一个字节,写一次一个字节 */ public static void demo1() throws FileNotFoundException, IOException { FileInputStream fis = new FileInputStream("双元.jpg"); //创建输入流对象,关联双元.jpg FileOutputStream fos = new FileOutputStream("copy.jpg"); //创建输出流对象,关联copy.jpg int b; while((b = fis.read()) != -1) { //在不断的读取每一个字节 fos.write(b); //将每一个字节写出 } fis.close(); //关流释放资源 fos.close(); }
一个一个字节去读,去写(去拷贝),所以这种方法特别耗时。读一次写一次,一共要读写900多万次,所以特别耗时。
4)fis.available() 得到输入流文件的全部字节数
/* * 不推荐使用,因为有可能会导致内存溢出 */ public static void demo3() throws FileNotFoundException, IOException { FileInputStream fis = new FileInputStream("致青春.mp3"); FileOutputStream fos = new FileOutputStream("copy.mp3"); byte[] arr = new byte[fis.available()]; fis.read(arr); fos.write(arr); fis.close(); fos.close(); }
5)
private native int readBytes(byte b[], int off, int len) throws IOException; //这个方法使用一个byte的数组作为一个缓冲区,每次从数据源中读取和缓冲区大小(二进制位)相同的数据并将其存在缓冲区中。 //定义的数组长度为10,每次写进去的也是10 /* * 1.从读取流读取一定数量的字节,如果比如文件总共是102个字节 * 2.我们定义的数组长度是10,那么默认前面10次都是读取10个长度 * 3.最后一次不够十个,那么读取的是2个 * 4.这十一次,每次都是放入10个长度的数组. */ public int read(byte b[]) throws IOException { return readBytes(b, 0, b.length); } /* * 1.从读取流读取一定数量的字节,如果比如文件总共是102个字节 * 2.我们定义的数组长度是10,但是这里我们写read(bytes,0,9)那么每次往里面添加的(将只会是9个长度),就要读12次,最后一次放入3个. * 3.所以一般读取流都不用这个而是用上一个方法:read(byte[]); */ public int read(byte b[], int off, int len) throws IOException { return readBytes(b, off, len); } 注意:空参构造和有参构造返回的值内容是不一样
关于read(byte[] buffer,int off, int len):可以看出:参数buffer表示建了多大长度的缓冲区;off表示把东东往第几个缓冲区放,所以就一般为0,要知道,为什么不在第0个放,而要浪费第0个的位置呢;len就表示每次往缓冲区放多少个东东;
public static void main(String[] args) throws Exception { InputStream is = null; byte[] buffer = new byte[5]; char c; try { is = new FileInputStream("test.txt");// 里面的内容为:ABCDEFGHI is.read(buffer, 1, 2); for (byte b : buffer) { if (b == 0) { c = '-'; } else { c = (char) b; } System.out.print(c); } System.out.println(); is.read(buffer, 1, 3); for (byte b : buffer) { if (b == 0) { c = '-'; } else { c = (char) b; } System.out.print(c); } } catch (Exception e) { e.printStackTrace(); } finally { if (is != null) is.close(); } }
输出:
-AB--
-CDE-
public static void demo1() throws FileNotFoundException, IOException { //xxx.txt 文件内容上面为 97,98,99 FileInputStream fis = new FileInputStream("xxx.txt"); byte[] arr = new byte[2]; int a = fis.read(arr); //将文件上的字节读取到字节数组中, //返回的是读到的数组的长度,也就是读到的字节的个数 System.out.println("a:"+a); //读到的有效字节个数 for (byte b : arr) { //第一次获取到文件上的a和b System.out.println(b); } int c = fis.read(arr); System.out.println(c); for (byte b : arr) { System.out.println(b); } fis.close(); }
打印出:
2、97、98
1、99、98
标准代码:
public static void demo2() throws FileNotFoundException, IOException { FileInputStream fis = new FileInputStream("xxx.txt"); FileOutputStream fos = new FileOutputStream("yyy.txt"); byte[] arr = new byte[2]; int len; while((len = fis.read(arr)) != -1) { //结合上面的,其实第二次返回的长度等于1了 fos.write(arr,0,len); //这次每两个字节两个字节地读,比一开始一个个字节读快多了 } //但是在实际中,字节数组长度设置得更大 fis.close(); fos.close(); }
我们设置数组长度,就等于我们自己拿着篮子去买菜了,但是实际上在java中,java已经帮我们备好篮子了
private static int defaultBufferSize = 8192; //每次读取8192个字节 public BufferedInputStream(InputStream in) { //包装,装饰者模式 this(in, defaultBufferSize); } public BufferedInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; }
* E.小数组的读写和带Buffered的读取哪个更快?
* 定义小数组如果是8192个字节大小和Buffered比较的话
* 定义小数组会略胜一筹,因为读和写操作的是同一个数组
* 而Buffered操作的是两个数组
* close方法
* 具备刷新的功能,在关闭流之前,就会先刷新一次缓冲区,将缓冲区的字节全都刷新到文件上,再关闭,
* close方法刷完之后就能写了
* 只有满了才刷新,有点不厚道
*
* flush方法?
* 具备刷新的功能,刷完之后还可以继续写,自动刷新啊 。。用户体验
字节数组处理中文:
public static void demo1() throws FileNotFoundException, IOException { FileInputStream fis = new FileInputStream("yyy.txt"); byte[] arr = new byte[4]; int len; while((len = fis.read(arr)) != -1) { //写中文随时出现乱码 System.out.println(new String(arr,0,len));//码表转中文,string的构造方法 } fis.close(); }
public static void main(String[] args) throws IOException { //demo1(); FileOutputStream fos = new FileOutputStream("zzz.txt"); fos.write("我读书少,你不要骗我".getBytes()); //字节流写出中文,要转换 fos.write("\r\n".getBytes()); //换行 fos.close(); }
标准代码:但是实际上一般都不会写得那么麻烦(1.6版本)
public static void demo1() throws FileNotFoundException, IOException { FileInputStream fis = null; //作用域问题,所以要放在外面 FileOutputStream fos = null; //如果下面两两端代码初始化失败 try { fis = new FileInputStream("xxx.txt"); fos = new FileOutputStream("yyy.txt"); int b; while((b = fis.read()) != -1) { fos.write(b); } }finally { //一定要关流,所以finally try{ //如果初始化失败,没有开启,则不用关闭 if(fis != null) fis.close(); }finally { //try fianlly的嵌套目的是能关一个尽量关一个 if(fos != null) fos.close(); } } }
1.7 版本之后流自动关闭,全实现了AutoCloseable接口,自动调用里面的close方法(这种方式开发中用得比较少)
//当把流写在小括号里面,流就会自动关闭,为什么请见下面
public static void main(String[] args) throws IOException { try( FileInputStream fis = new FileInputStream("xxx.txt"); FileOutputStream fos = new FileOutputStream("yyy.txt"); ){ int b; while((b = fis.read()) != -1) { fos.write(b); } } }
public class FileInputStream extends InputStream; public abstract class InputStream implements Closeable; public interface Closeable extends AutoCloseable; /** * A resource that must be closed when it is no longer needed. * * @author Josh Bloch * @since 1.7 */ public interface AutoCloseable;
END!
END!