1 // 简单实现复制 2 public static void main(String[] args) throws IOException { 3 //1,读取一个已有的文本文件,使用字符读取流和文件相关联。 4 FileReader fr = new FileReader("IO流_2.txt"); 5 //2,创建一个目的,用于存储读到数据。 6 FileWriter fw = new FileWriter("copytext_1.txt"); 7 //3,频繁的读写操作。 8 int ch = 0; 9 while((ch=fr.read())!=-1){ 10 fw.write(ch); 11 } 12 //4,关闭流资源。 13 fw.close(); 14 fr.close(); 15 } 16 17 // 引入缓冲做复制优化 手动定义缓冲去 18 // 引入字符数组作为缓冲区:(循环次数小,效率高) 19 private static final int BUFFER_SIZE = 1024; 20 public static void main(String[] args) { 21 FileReader fr = null; 22 FileWriter fw = null; 23 try { 24 fr = new FileReader("IO流_2.txt"); 25 fw = new FileWriter("copytest_2.txt"); 26 //创建一个临时容器,用于缓存读取到的字符。 27 char[] buf = new char[BUFFER_SIZE];//这就是缓冲区。 28 //定义一个变量记录读取到的字符数,(其实就是往数组里装的字符个数) 29 int len = 0; 30 while((len=fr.read(buf))!=-1){ 31 fw.write(buf, 0, len); 32 } 33 } catch (Exception e) { 34 // System.out.println("读写失败"); 35 throw new RuntimeException("读写失败"); 36 }finally{ 37 if(fw!=null) 38 try { 39 fw.close(); 40 } catch (IOException e) { 41 42 e.printStackTrace(); 43 } 44 if(fr!=null) 45 try { 46 fr.close(); 47 } catch (IOException e) { 48 49 e.printStackTrace(); 50 } 51 } 52 } 53 54 // 优化三:使用装饰者模式添加上缓冲区功能 55 public static void main(String[] args) throws IOException { 56 FileReader fr = new FileReader("buf.txt"); 57 // 装饰上缓冲区功能 58 BufferedReader bufr = new BufferedReader(fr); 59 60 FileWriter fw = new FileWriter("buf_copy.txt"); 61 BufferedWriter bufw = new BufferedWriter(fw); 62 63 String line = null; 64 while((line=bufr.readLine())!=null){ 65 bufw.write(line); 66 bufw.newLine(); 67 bufw.flush(); 68 } 69 /* 70 int ch = 0; 71 while((ch=bufr.read())!=-1){ 72 bufw.write(ch); 73 } 74 */ 75 bufw.close(); 76 bufr.close(); 77 }
所以引入BufferWriter(缓冲区的出现提高了对数据的读写效率,缓冲区要结合流才可以使用,在流的基础上对流的功能进行了增强) 多层装饰强化:
InputStream input = new GZIPInputStream( // 第二层装饰 new BufferedInputStream( // 第一层装饰 new FileInputStream("test.gz") // 核心功能 ));
GZip常用于linux环境下,是一种非常简单的压缩算法: GZIPOutputStream类用于压缩 GZIPInputStream类用于解压缩
// 压缩 ByteArrayOutputStream out = new ByteArrayOutputStream(); gzip = new GZIPOutputStream(out); gzip.write(str.getBytes(encoding)); // 解压缩 byte[] uncompress(byte[] bytes) { if (bytes == null || bytes.length == 0) { return null; } ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayInputStream in = new ByteArrayInputStream(bytes); try { GZIPInputStream ungzip = new GZIPInputStream(in); byte[] buffer = new byte[256]; int n; while ((n = ungzip.read(buffer)) >= 0) { out.write(buffer, 0, n); } } catch (IOException e) { ApiLogger.error("gzip uncompress error.", e); } return out.toByteArray(); }
File对象来操作文件和目录。
new File("/usr/bin/javac"); // Windows平台使用\作为路径分隔符,在Java字符串中需要用\\表示一个\。Linux平台使用/作为路径分隔符: // 传先对路径,假设当前java文件目录是C:\Docs File f1 = new File("sub\\javac"); // 绝对路径是C:\Docs\sub\javac File f3 = new File(".\\sub\\javac"); // 绝对路径是C:\Docs\sub\javac File f3 = new File("..\\sub\\javac"); // 绝对路径是C:\sub\javac
Path对象和File对象类似,但操作更加简单:如果需要对目录进行复杂的拼接、遍历等操作,使用Path对象更方便。 read()方法是阻塞(Blocking)的,因为读取IO流相比执行普通代码,速度会慢很多,因此,无法确定read()方法调用到底要花费多长时间。
int n; n = input.read(); // 必须等待read()方法返回才能执行下一行代码 int m = n用FileInputStream可以从文件获取输入流,这是InputStream常用的一个实现类。此外,ByteArrayInputStream可以在内存中模拟一个InputStream:
public static void main(String[] args) throws IOException { byte[] data = { 72, 101, 108, 108, 111, 33 }; try (InputStream input = new ByteArrayInputStream(data)) { int n; while ((n = input.read()) != -1) { System.out.println((char)n); } } }
OutputStream void write(int b) 虽然传入的是int参数,但只会写入一个字节,即只写入int最低8位表示字节的部分(相当于b & 0xff) OutputStream还提供了一个flush()方法,它的目的是将缓冲区的内容真正输出到目的地。(通常情况下,我们不需要调用这个flush()方法,因为缓冲区写满了OutputStream会自动调用它,要是输入的不能把缓冲区填满,就需要手动输出目的地了)
// write()方法也是阻塞 OutputStream output = new FileOutputStream("out/readme.txt"); output.write("Hello".getBytes("UTF-8")); // Hello output.close();
InputStream根据来源可以包括: FileInputStream:从文件读取数据,是最终数据源; ServletInputStream:从HTTP请求读取数据,是最终数据源; Socket.getInputStream():从TCP连接读取数据,是最终数据源; ... IO Filter模式(为InputStream和OutputStream增加功能) 可以把一个InputStream和任意个FilterInputStream组合; 可以把一个OutputStream和任意个FilterOutputStream组合, Filter模式可以在运行期动态增加功能(又称Decorator模式)。 如果要给FileInputStream添加缓冲和签名的功能,那么我们还需要派生BufferedDigestFileInputStream。如果要给FileInputStream添加缓冲和加解密的功能,则需要派生BufferedCipherFileInputStream。如下
BufferedFileInputStream extends FileInputStream DigestFileInputStream extends FileInputStream CipherFileInputStream extends FileInputStreamZipInputStream是一种FilterInputStream,另一个JarInputStream是从ZipInputStream派生,它增加的主要功能是直接读取jar文件里面的MANIFEST.MF文件。因为本质上jar包就是zip包,只是额外附加了一些固定的描述文件。 在classpath中的资源文件,路径总是以/开头,我们先获取当前的Class对象,然后调用getResourceAsStream()就可以直接从classpath读取任意的资源文件:
// 如果资源文件不存在,input则为null try (InputStream input = getClass().getResourceAsStream("/default.properties")) { if (input != null) { // TODO: } }
把默认的配置放到jar包中,再从外部文件系统读取一个可选的配置文件,就可以做到既有默认的配置文件,又可以让用户自己修改配置:
Properties props = new Properties(); props.load(inputStreamFromClassPath("/default.properties")); props.load(inputStreamFromFile("./conf.properties"));
对象序列化: 把一个Java对象变为byte[]数组,需要使用ObjectOutputStream。它负责把一个Java对象写入一个字节流:
static <T extends Serializable> T clone(T obj) { T cloneObj = null; try { // 写入字节流 ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bo); oos.writeObject(obj); oos.close(); // 分配内存,写入原始对象,生成新对象 ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());//获取上面的输出字节流 ObjectInputStream oi = new ObjectInputStream(bi); // 返回生成的新对象 cloneObj = (T) oi.readObject(); oi.close(); } catch (Exception e) { e.printStackTrace(); } return cloneObj; }
Windows系统的默认编码可能是GBK,打开一个UTF-8编码的文本文件就会出现乱码。 要避免乱码问题,我们需要在创建FileReader时指定编码: Reader reader = new FileReader("src/readme.txt", StandardCharsets.UTF_8); CharArrayReader可以在内存中模拟一个Reader,它的作用实际上是把一个char[]数组变成一个Reader,这和ByteArrayInputStream非常类似: try (Reader reader = new CharArrayReader("Hello".toCharArray())) {} StringReader可以直接把String作为数据源,它和CharArrayReader几乎一样: try (Reader reader = new StringReader("Hello")) {} Reader和InputStream有什么关系? FileReader实现了文件字符流输入,使用时需要指定编码; 除了特殊的CharArrayReader和StringReader,普通的Reader实际上是基于InputStream构造的,因为Reader需要从InputStream中读入字节流(byte),然后,根据编码设置,再转换为char就可以实现字符流。如果我们查看FileReader的源码,它在内部实际上持有一个FileInputStream。 既然Reader本质上是一个基于InputStream的byte到char的转换器,那么,如果我们已经有一个InputStream,想把它转换为Reader,是完全可行的。InputStreamReader就是这样一个转换器,它可以把任何InputStream转换为Reader。示例代码如下:
// 构造InputStreamReader时,我们需要传入InputStream, // 还需要指定编码,就可以得到一个Reader对象 // 关闭对象时,只需要关闭最外层的Reader对象即可(它会在内部自动调用InputStream的close()方法) try (Reader reader = new InputStreamReader(new FileInputStream("src/readme.txt"), "UTF-8")) { // TODO: }
OutputStream Writer 字节流,以byte为单位 字符流,以char为单位 写入字节(0~255):void write(int b) 写入字符(0~65535):void write(int c) 写入字节数组:void write(byte[] b) 写入字符数组:void write(char[] c) 无对应方法 写入String:void write(String s)
try (Writer writer = new FileWriter("readme.txt", StandardCharsets.UTF_8)) { writer.write('H'); // 写入单个字符 writer.write("Hello".toCharArray()); // 写入char[] writer.write("Hello"); // 写入String }
java IO包装流如何关闭? 如下例子代码:
如下例子代码: FileInputStream is = new FileInputStream("."); BufferedInputStream bis = new BufferedInputStream(is); bis.close(); 从设计模式上看: java.io.BufferedInputStream是java.io.InputStream的装饰类。 BufferedInputStream装饰一个 InputStream 使之具有缓冲功能, is要关闭只需要调用最终被装饰出的对象的 close()方法即可, 因为它最终会调用真正数据源对象的 close()方法。 BufferedInputStream的close方法中对InputStream进行了关闭, 下面是jdk中附带的源代码: java.io.BufferedInputStream的api: close public void close()throws IOException 关闭此输入流并释放与该流关联的所有系统资源。 如果不想用上述方式关闭流:可以逐层关闭,主要最外层的先关闭### 如下例子: public static void main(String[] args) throws Exception { FileOutputStream fos = new FileOutputStream("d:\\a.txt"); OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8"); BufferedWriter bw = new BufferedWriter(osw); bw.write("java IO close test"); //从内带外顺序顺序会报异常 fos.close(); osw.close(); bw.close(); } 报出异常: Exception in thread "main" java.io.IOException: Stream closed at sun.nio.cs.StreamEncoder.ensureOpen(StreamEncoder.java:26) at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:99) at java.io.OutputStreamWriter.write(OutputStreamWriter.java:190) at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:111) at java.io.BufferedWriter.close(BufferedWriter.java:246) at com.my.test.QQ.main(QQ.java:22) 如下例子: public static void main(String[] args) throws Exception { FileOutputStream fos = new FileOutputStream("d:\\a.txt"); OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8"); BufferedWriter bw = new BufferedWriter(osw); bw.write("java IO close test"); // 从外到内顺序关闭ok bw.close(); osw.close(); fos.close(); } 验证ok
下面的流是装饰流对其他流进行强化 NIO 那么NIO和IO各适用的场景是什么呢? 如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,这时候用NIO处理数据可能是个很好的选择。 而如果只有少量的连接,而这些连接每次要发送大量的数据,这时候传统的IO更合适。使用哪种处理数据,需要在数据的响应等待时间和检查缓冲区数据的时间上作比较来权衡选择。 NIO是为弥补传统IO的不足而诞生的,但是尺有所短寸有所长,NIO也有缺点,因为NIO是面向缓冲区的操作,每一次的数据处理都是对缓冲区进行的,那么就会有一个问题,在数据处理之前必须要判断缓冲区的数据是否完整或者已经读取完毕,如果没有,假设数据只读取了一部分,那么对不完整的数据处理没有任何意义。所以每次数据处理之前都要检测缓冲区数据。