字节流
字符流:
- FileReader
- FileWriter
- BufferedReader
- BufferedWriter
字节流:
- FileInputStream
- FileOutputStream
- BufferedInputStream
- BufferedOutputStream
想要操作图片数据,这时就要用到字节流。
示例代码如下:
import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;public class FileOutputStreamDemo0 { public static void main(String[] args) throws IOException { //writeFile(); //readFile(); //readFile_2(); readFile_3(); } public static void readFile_3() throws IOException { FileInputStream fis = new FileInputStream("fos.txt"); //int num = fis.available(); byte[] buf = new byte[fis.available()];//定义一个刚刚好的缓冲区,不用在循环了。不过慎用!!! fis.read(buf); //System.out.println("num = " + num); System.out.println(new String(buf)); fis.close(); } public static void readFile_2() throws IOException { FileInputStream fis = new FileInputStream("fos.txt"); byte[] buf = new byte[1024];//还是建议使用1024的整数倍开辟字节数组 int len = 0; while((len = fis.read(buf)) != -1) { System.out.println(new String(buf, 0, len)); } fis.close(); } public static void readFile() throws IOException { FileInputStream fis = new FileInputStream("fos.txt"); int ch = 0; while((ch = fis.read()) != -1) { System.out.println((char) ch); } fis.close(); } public static void writeFile() throws IOException { FileOutputStream fos = new FileOutputStream("fos.txt"); fos.write("abcde".getBytes()); fos.close(); }}
练习1:复制一个图片。
思路:
- 用字节读取流对象和图片关联。
- 用字节写入流对象创建一个图片文件,用于存储获取到的图片数据。
- 通过循环读写,完成数据的存储。
- 关闭资源。
代码:
import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;public class CopyPic { public static void main(String[] args) { FileOutputStream fos = null; FileInputStream fis = null; try { fos = new FileOutputStream("E:\\MyJava\\workspace\\IO_Project\\Krystal.jpg"); fis = new FileInputStream("D:\\Krystal.jpg"); byte[] buf = new byte[1024]; int len = 0; while((len = fis.read(buf)) != -1) { fos.write(buf, 0, len); } } catch (IOException e) { throw new RuntimeException("复制失败!"); } finally { try { if(fis != null) fis.close(); } catch (IOException e) { throw new RuntimeException("读取关闭失败!"); } try { if(fos != null) fos.close(); } catch (IOException e) { throw new RuntimeException("写入关闭失败!"); } } }}
练习2:复制一个MP3文件。
通过缓冲区(BufferedOutputStream/BufferedInputStream),演示MP3的复制。
代码:
import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;public class CopyMp3 { public static void main(String[] args) throws IOException { long start = System.currentTimeMillis(); copy_2(); long end = System.currentTimeMillis(); System.out.println((end - start) + "毫秒"); } /* * 通过字节流的缓冲区完成复制 */ public static void copy_1() throws IOException { BufferedInputStream bufis = new BufferedInputStream( new FileInputStream("E:\\KuGou\\Temp\\滨琦步 - 犬夜叉主题曲.mp3")); BufferedOutputStream bufos = new BufferedOutputStream( new FileOutputStream("E:\\MyJava\\workspace\\IO_Project\\滨琦步 - 犬夜叉主题曲.mp3")); int by = 0; while((by = bufis.read()) != -1) { bufos.write(by); } bufis.close(); bufos.close(); } }
练习3:自定义一个类模拟BufferedInputStream,完成一个MP3文件的复制。
分析:
mp3是由二进制数据组成的:11111111-1110000000000000000000101010110111010111010010110001问题:自定义的myRead()函数为什么会返回int类型,而不直接返回byte类型呢?分析:byte: -1 ----> int: -111111111(-1)提升为11111111 11111111 11111111 11111111(-1)11111111(-1)--->提升为一个int类型,那还不是-1吗?是-1的原因是因为在8个1前面补的都是1导致的。那么我只要在前面补0,即可以保留原字节数据不变,又可以避免-1的出现。怎么补0呢? 11111111 11111111 11111111 11111111& 00000000 00000000 00000000 11111111------------------------------------------ 00000000 00000000 00000000 11111111 所以应把11111111(-1)提升为00000000 00000000 00000000 11111111(255),避免返回-1这种情况
代码:
import java.io.BufferedOutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;class MyBufferedInputStream { private InputStream in; private byte[] buf = new byte[1024 * 4]; private int pos = 0, count = 0; MyBufferedInputStream(InputStream in) { this.in = in; } /* * 一次读一个字节,从缓冲区(字节数组)获取。 */ public int myRead() throws IOException { /* * 通过in对象读取硬盘上数据,并存储到buf中。 */ if(count == 0) { count = in.read(buf); if(count < 0) return -1; pos = 0; byte b = buf[pos]; count--; pos++; return b & 255; } else if(count > 0) { byte b = buf[pos]; count--; pos++; return b & 0xff; } return -1; } public void myClose() throws IOException { in.close(); }}public class MyBufferedInputStreamDemo { public static void main(String[] args) throws IOException { long start = System.currentTimeMillis(); copy_2(); long end = System.currentTimeMillis(); System.out.println((end - start) + "毫秒"); } public static void copy_2() throws IOException { MyBufferedInputStream bufis = new MyBufferedInputStream( new FileInputStream("E:\\KuGou\\Temp\\滨琦步 - 犬夜叉主题曲.mp3")); BufferedOutputStream bufos = new BufferedOutputStream( new FileOutputStream("E:\\MyJava\\workspace\\IO_Project\\滨琦步 - 犬夜叉主题曲_2.mp3")); int by = 0; //System.out.println("第一个字节:" + bufis.myRead());//第一个字节:-1,因为读到了连续的11111111 while((by = bufis.myRead()) != -1) { bufos.write(by); } bufis.myClose(); bufos.close(); }}
System类对IO的支持
读取键盘录入。
System.out:对应的是标准输出设备,控制台。
System.in:对应的是标准输入设备,键盘。
例,需求:通过键盘录入数据。当录入一行数据后,就将该行数据进行打印。如果录入的数据是over,那么录入停止。
代码:
import java.io.IOException;import java.io.InputStream;public class ReadIn { public static void main(String[] args) throws IOException { InputStream in = System.in; StringBuilder sb = new StringBuilder(); while(true) { int ch = in.read(); if(ch == '\r') continue; if(ch == '\n') { String s = sb.toString(); if("over".equals(s)) break; System.out.println(s.toUpperCase()); sb.delete(0, sb.length());//清空缓冲区 } else { sb.append((char) ch); } } //System.out.println('\r' + 0);//13 //System.out.println('\n' + 0);//10 /* int by = in.read(); int by1 = in.read(); int by2 = in.read(); System.out.println(by); System.out.println(by1); System.out.println(by2); */ }}
通过刚才的键盘录入一行数据并打印其大写,发现其实就是读一行数据的原理。也就是readLine()方法。
能不能直接使用readLine()方法来完成键盘录入的一行数据的读取呢?
readLine()方法是字符流BufferedReader类中的方法,而键盘录入的read()方法是字节流InputStream的方法,那么能不能将字节流转成字符流,再使用字符流缓冲区的readLine()方法呢?
此时就需要用到转换流。
转换流:
- InputStreamReader是字节流通向字符流的桥梁:它使用指定的charset读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集(GBK)。
- OutputStreamWriter是字符流通向字节流的桥梁:可使用指定的charset将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
上例优化之后的代码为:
import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.OutputStreamWriter;public class TransStreamDemo0 { public static void main(String[] args) throws IOException { /* * 获取键盘录入对象。 */ //InputStream in = System.in; /* * 将字节流对象转成字符流对象,使用转换流——InputStreamReader */ //InputStreamReader isr = new InputStreamReader(in); /* * 为了提高效率,将字符流进行缓冲区技术高效操作,使用BufferedReader */ //BufferedReader bufr = new BufferedReader(isr); /* * 简写格式,键盘录入最常见写法 */ BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); //OutputStream out = System.out;//屏幕输出 /* * 字符流输出对象转换成字节流输出对象 */ //OutputStreamWriter osw = new OutputStreamWriter(out); //BufferedWriter bufw = new BufferedWriter(osw); /* * 简写格式 */ BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out)); String str = null; while((str = bufr.readLine()) != null) { if("over".equals(str)) break; //System.out.println(str.toUpperCase()); bufw.write(str.toUpperCase()); bufw.newLine(); bufw.flush(); } bufw.close();//并没有写bufr.close();是因为此程序一结束掉,就给关闭了。 }}
流操作的基本规律:
最痛苦的就是流对象有很多,不知道该用哪一个。
通过三个明确来完成:
1、明确源和目的。
源:输入流。InputStream Reader
目的:输出流。OutputStream Writer
2、操作的数据是否是纯文本。
是:字符流。
不是:字节流。
3、当体系明确后,在明确要使用哪个具体的对象。
通过设备来进行区分:
源设备:内存、硬盘、键盘。
目的设备:内存、硬盘、控制台。
例1、需求:将一个文本文件中的数据存储到另一个文件中。(复制文件)
分析:
源:因为是源,所以使用读取流。InputStream Reader
是不是操作文本文件?
是!这时就可以选择Reader,这样体系就明确了。
接下来要明确要使用该体系中的那个对象?
明确设备:硬盘上的一个文件。Reader体系中可以操作文件的对象是FileReader。
是否需要提高效率?是!加入Reader体系中的缓冲区BufferedReader。
FileReader fr = new FileReader("a.txt");BufferedReader bufr = new BufferedReader(fr);
目的:OutputStream Writer
目的是否是纯文本?是!Writer。
设备:硬盘上的一个文件。
Writer体系中可以操作文件的对象是FileWriter。
是否需要提高效率?是!加入Reader体系中的缓冲区BufferedWriter。
FileWriter fw = new FileWriter("b.txt");BufferedWriter bufw = new BufferedWriter(fr);
练习:将一个图片文件中的数据存储到另一个文件中。(复制文件)
代码:
/* * 练习:将一个图片文件中的数据存储到另一个文件中。(复制文件) */import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;public class test0 { public static void main(String[] args) { FileInputStream fis = null; FileOutputStream fos = null; BufferedInputStream bis = null; BufferedOutputStream bos = null; byte[] by = new byte[1024]; int len = 0; try { fis = new FileInputStream("d:\\Krystal.jpg"); bis = new BufferedInputStream(fis); fos = new FileOutputStream("d:\\java\\io123\\Krystal.jpg"); bos = new BufferedOutputStream(fos); while((len = bis.read(by)) != -1) { bos.write(by, 0, len); } } catch (IOException e) { throw new RuntimeException("图片复制失败!"); } finally { try { if(bis != null) bis.close(); } catch (IOException e) { throw new RuntimeException("关闭读取流失败!"); } try { if(bos != null) bos.close(); } catch (IOException e) { throw new RuntimeException("关闭写入流失败!"); } } }}
例2、需求:将键盘录入的数据保存到一个文件中。
分析:
这个需求中有源和目的都存在。那么分别分析
源:InputStream Reader
是不是纯文本?是!Reader
设备:键盘。对应的对象是System.in。
不是选择Reader吗?System.in对应的不是字节流吗?为了操作键盘的文本数据方便,转成字符流,按照字符串操作是最方便的。所以既然明确了Reader,那么就将System.in转换成字符流Reader。用到了Reader体系中的转换流,InputStreamReader。
InputStreamReader isr = new InputStreamReader(System.in);
需要提高效率吗?需要!BufferedReader
BufferedReader bufr = new BufferedReader(isr);
目的:OutputStream Writer
是不是纯文本?是!Writer
设备:硬盘上的一个文件。使用FileWriter。
FileWriter fw = new FileWriter("c.txt");
需要提高效率吗?需要!BufferedWriter
BufferedWriter bufw = new BufferedWriter(fw);
扩展一下,想要把录入的数据按照指定的编码表(UTF-8),将数据存到文件中,怎么办呢?
分析:
目的:OutputStream Writer
是不是纯文本?是!Writer
设备:硬盘上的一个文件。使用FileWriter。但是FileWriter是使用的默认编码表(GBK)。
但是存储时,需要加入指定的编码表(UTF-8),而指定的编码表只有转换流可以指定,所以要使用的对象是OutputStreamWriter。而该转换流对象要接收一个字节输出流,而且还可以操作文件的字节输出流,FileOutputStream。
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"), "UTF-8");
需要提高效率吗?需要!BufferedWriter
BufferedWriter bufw = new BufferedWriter(osw);
所以,记住,转换流什么时候使用?字符和字节之间的桥梁,通常涉及到字符编码转换时,需要用到转换流。
练习:将一个文本数据打印在控制台上。
代码:
/* * 练习:将一个文本数据打印在控制台上。 */import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.FileReader;import java.io.IOException;import java.io.OutputStreamWriter;public class test1 { public static void main(String[] args) { BufferedReader br = null; BufferedWriter bw = null; String line = null; try { br = new BufferedReader(new FileReader("d:\\java\\ArrayTool.java")); bw = new BufferedWriter(new OutputStreamWriter(System.out)); while((line = br.readLine()) != null) { bw.write(line); bw.newLine(); bw.flush(); } } catch (IOException e) { throw new RuntimeException("读取文件失败"); } finally { try { if(br != null) br.close(); } catch (IOException e) { throw new RuntimeException("读取流关闭失败"); } try { if(bw != null) bw.close(); } catch (IOException e) { throw new RuntimeException("写入流关闭失败"); } } }}
异常的日志信息
log4j:记录日志信息的一个工具。
示例代码如下:
import java.io.FileNotFoundException;import java.io.PrintStream;import java.text.SimpleDateFormat;import java.util.Date;public class ExceptionInfo { public static void main(String[] args) { try { int[] arr = new int[2]; System.out.println(arr[3]); } catch (Exception e) { try { Date d = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String s = sdf.format(d); PrintStream ps = new PrintStream("exception.log"); //ps.write(d.toString().getBytes()); ps.println(s); System.setOut(ps); } catch (FileNotFoundException e1) { throw new RuntimeException("日志文件创建失败!"); } e.printStackTrace(System.out); //e.printStackTrace(new PrintStream("ex.txt")); } }}
系统信息
示例代码如下:
import java.io.FileNotFoundException;import java.io.PrintStream;import java.util.Properties;public class SystemInfo { public static void main(String[] args) throws FileNotFoundException { Properties pro = System.getProperties(); //System.out.println(pro); pro.list(new PrintStream("sysinfo.txt")); }}
通过sysinfo.txt文本文件可以知道平台默认的字符集,即默认字符编码是GBK。