Java中的IO流 作为一段能实现完整的功能的长期使用的程序,能够读写数据是其必须具备的能力,今天就来看一看关于如何实现不同种类的数据的处理
什么是IO,input和output,即输入和输出,这两种流分别负责了对不同的数据的输入与输出,想要掌握好IO流,要涉及的方方面面很多,我就得网上找到的一段总结非常好(以下内容引自博客园的@宜春)
(1)明确要操作的数据是数据源还是数据目的(也就是要读还是要写) (2)明确要操作的设备上的数据是字节还是文本 (3)明确数据所在的具体设备 (4)明确是否需要额外功能(比如是否需要转换流、高效流等)
如果能实现这些要点,那么一个程序员就真的算的上掌握了IO流
File中的流 为什么要先从文件流讲起?因为事实上是在发展过程中先出现了文件的输入与输出流,后来流的概念被完善才进一步出现了更多的种类的流,这些流其实是以文件流为模版建立的所以我们可以直接先来学习文件流(这部分可以先去看一看我以前写的[Java的文件处理](浅谈Java中的文件处理 - Soul的小站 (soulmate.org.cn) )),我们先来创建一个文件输入流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Main { public static void main (String[] args) { FileInputStream fis = null ; try { fis = new FileInputStream ("你的路径" ); } catch (FileNotFoundException e) { throw new RuntimeException (e); } finally { if (fis != null ) { try { fis.close(); } catch (IOException e) { throw new RuntimeException (e); } } } } }
当然,这里也可以使用try-with-resource的语法
1 2 3 4 5 try (FileInputStream fileInputStream=new FileInputStream ("asd" )){ }catch (FileNotFoundException e){ e.printStackTrace(); }
这样,在括号中的内容在try块结束后会被自动结束
那么,我们在得到文件的输入流后可以做哪些操作呢,可以看看源码(下面的源码中我直接将private的部分删除,有兴趣可以自己阅读)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 public class FileInputStream extends InputStream { public FileInputStream (String name) throws FileNotFoundException { this (name != null ? new File (name) : null ); } public FileInputStream (File file) throws FileNotFoundException { String name = (file != null ? file.getPath() : null ); @SuppressWarnings("removal") SecurityManager security = System.getSecurityManager(); if (security != null ) { security.checkRead(name); } if (name == null ) { throw new NullPointerException (); } if (file.isInvalid()) { throw new FileNotFoundException ("Invalid file path" ); } fd = new FileDescriptor (); fd.attach(this ); path = name; open(name); FileCleanable.register(fd); } / public FileInputStream (FileDescriptor fdObj) { @SuppressWarnings("removal") SecurityManager security = System.getSecurityManager(); if (fdObj == null ) { throw new NullPointerException (); } if (security != null ) { security.checkRead(fdObj); } fd = fdObj; path = null ; fd.attach(this ); } @Override public int read () throws IOException { long comp = Blocker.begin(); try { return read0(); } finally { Blocker.end(comp); } } @Override public int read (byte [] b) throws IOException { long comp = Blocker.begin(); try { return readBytes(b, 0 , b.length); } finally { Blocker.end(comp); } } @Override public int read (byte [] b, int off, int len) throws IOException { long comp = Blocker.begin(); try { return readBytes(b, off, len); } finally { Blocker.end(comp); } } @Override public byte [] readAllBytes() throws IOException { long length = length(); long position = position(); long size = length - position; if (length <= 0 || size <= 0 ) return super .readAllBytes(); if (size > (long ) Integer.MAX_VALUE) { String msg = String.format("Required array size too large for %s: %d = %d - %d" , path, size, length, position); throw new OutOfMemoryError (msg); } int capacity = (int )size; byte [] buf = new byte [capacity]; int nread = 0 ; int n; for (;;) { while ((n = read(buf, nread, capacity - nread)) > 0 ) nread += n; if (n < 0 || (n = read()) < 0 ) break ; capacity = Math.max(ArraysSupport.newLength(capacity, 1 , capacity), DEFAULT_BUFFER_SIZE); buf = Arrays.copyOf(buf, capacity); buf[nread++] = (byte )n; } return (capacity == nread) ? buf : Arrays.copyOf(buf, nread); } @Override public byte [] readNBytes(int len) throws IOException { if (len < 0 ) throw new IllegalArgumentException ("len < 0" ); if (len == 0 ) return new byte [0 ]; long length = length(); long position = position(); long size = length - position; if (length <= 0 || size <= 0 ) return super .readNBytes(len); int capacity = (int )Math.min(len, size); byte [] buf = new byte [capacity]; int remaining = capacity; int nread = 0 ; int n; do { n = read(buf, nread, remaining); if (n > 0 ) { nread += n; remaining -= n; } else if (n == 0 ) { byte b = (byte )read(); if (b == -1 ) break ; buf[nread++] = b; remaining--; } } while (n >= 0 && remaining > 0 ); return (capacity == nread) ? buf : Arrays.copyOf(buf, nread); } @Override public long transferTo (OutputStream out) throws IOException { long transferred = 0L ; if (out instanceof FileOutputStream fos) { FileChannel fc = getChannel(); long pos = fc.position(); transferred = fc.transferTo(pos, Long.MAX_VALUE, fos.getChannel()); long newPos = pos + transferred; fc.position(newPos); if (newPos >= fc.size()) { return transferred; } } try { return Math.addExact(transferred, super .transferTo(out)); } catch (ArithmeticException ignore) { return Long.MAX_VALUE; } } @Override public long skip (long n) throws IOException { long comp = Blocker.begin(); try { return skip0(n); } finally { Blocker.end(comp); } } @Override public int available () throws IOException { long comp = Blocker.begin(); try { return available0(); } finally { Blocker.end(comp); } } @Override public void close () throws IOException { if (closed) { return ; } synchronized (closeLock) { if (closed) { return ; } closed = true ; } FileChannel fc = channel; if (fc != null ) { fc.close(); } fd.closeAll(new Closeable () { public void close () throws IOException { fd.close(); } }); } public final FileDescriptor getFD () throws IOException { if (fd != null ) { return fd; } throw new IOException (); } public FileChannel getChannel () { FileChannel fc = this .channel; if (fc == null ) { synchronized (this ) { fc = this .channel; if (fc == null ) { this .channel = fc = FileChannelImpl.open(fd, path, true , false , false , this ); if (closed) { try { fc.close(); } catch (IOException ioe) { throw new InternalError (ioe); } } } } } return fc; } }
如果你去看源码,会发下有很多有native关键字的方法,这些方法通过c或c++实现,所以看不懂属于正常情况
解下来是文件的输出流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 public class FileOutputStream extends OutputStream { public FileOutputStream (String name) throws FileNotFoundException { this (name != null ? new File (name) : null , false ); } public FileOutputStream (String name, boolean append) throws FileNotFoundException { this (name != null ? new File (name) : null , append); } public FileOutputStream (File file) throws FileNotFoundException { this (file, false ); } public FileOutputStream (File file, boolean append) throws FileNotFoundException { String name = (file != null ? file.getPath() : null ); @SuppressWarnings("removal") SecurityManager security = System.getSecurityManager(); if (security != null ) { security.checkWrite(name); } if (name == null ) { throw new NullPointerException (); } if (file.isInvalid()) { throw new FileNotFoundException ("Invalid file path" ); } this .fd = new FileDescriptor (); fd.attach(this ); this .path = name; open(name, append); FileCleanable.register(fd); } public FileOutputStream (FileDescriptor fdObj) { @SuppressWarnings("removal") SecurityManager security = System.getSecurityManager(); if (fdObj == null ) { throw new NullPointerException (); } if (security != null ) { security.checkWrite(fdObj); } this .fd = fdObj; this .path = null ; fd.attach(this ); } @Override public void write (int b) throws IOException { boolean append = FD_ACCESS.getAppend(fd); long comp = Blocker.begin(); try { write(b, append); } finally { Blocker.end(comp); } } @Override public void write (byte [] b) throws IOException { boolean append = FD_ACCESS.getAppend(fd); long comp = Blocker.begin(); try { writeBytes(b, 0 , b.length, append); } finally { Blocker.end(comp); } } @Override public void write (byte [] b, int off, int len) throws IOException { boolean append = FD_ACCESS.getAppend(fd); long comp = Blocker.begin(); try { writeBytes(b, off, len, append); } finally { Blocker.end(comp); } } @Override public void close () throws IOException { if (closed) { return ; } synchronized (closeLock) { if (closed) { return ; } closed = true ; } FileChannel fc = channel; if (fc != null ) { fc.close(); } fd.closeAll(new Closeable () { public void close () throws IOException { fd.close(); } }); } }
现在我们试着写一个简单的复制文件的程序
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Main { public static void main (String[] args) { try (FileInputStream fileInputStream=new FileInputStream ("E:\\1.txt" ); FileOutputStream fileOutputStream=new FileOutputStream ("E:\\2.txt" )){ fileInputStream.transferTo(fileOutputStream); fileOutputStream.flush(); }catch (FileNotFoundException e){ e.printStackTrace(); } catch (IOException e) { throw new RuntimeException (e); } } }
看,是不是非常简洁
这里插播一个小知识点,所有的文件类型本质上都是一串又一串的0与1,而每个文件的开头都会有一个事先约定好的文件类型标识,这一标识是一串固定的数字计算机通过这一串的数字来决定选用什么样的解析规则将接下来的内容转换为人类可以理解的内容,所以只要输入与输出是同一个文件类型就可以正常复制
但是这种流存在一个问题如果我们希望对文件进行某种处理,那么由于人是无法直接理解字节内容的,所以我们对内容的操作就特别困难,所以除了字节流以外还存在另一种字符流,当然实际上这东西用的不是特别多所以我写的简单一点
1 2 3 4 5 6 7 8 public static void main (String[] args) { try (FileReader reader = new FileReader ("test.txt" )){ reader.skip(1 ); System.out.println((char ) reader.read()); }catch (IOException e){ e.printStackTrace(); } }
简单的读取,需要注意的是read返回的值实际上是char类型的数组
1 2 3 4 5 6 7 8 9 10 public static void main (String[] args) { try (FileWriter writer = new FileWriter ("output.txt" )){ writer.getEncoding(); writer.write('牛' ); writer.append('牛' ); writer.flush(); }catch (IOException e){ e.printStackTrace(); } }
写入文件,显然是很简单的,唯一需要注意的是不要关于File类的使用,可以去看看我之前写的文件处理,也可以自己读一读File类的源码
搞定了这些一般的文件读写其实就没什么难点了
缓冲流 解下来我们再看一看缓冲流,其实缓冲流的输入与输出本质上还是使用了普通的输入与输出流的方法来进行数据的操作,但是缓冲流添加了缓冲的过程,将内容先预加载到内存中的缓冲区中,当内容需要多次使用时可以省去反复的向外部设备读取的时间,提高程序的运行效率.
缓冲流是对应的字符/字节流的子类,所以字符字节流怎么用对应的缓冲流就怎么用,而在创建对应的缓冲流时可以直接将原来的输入输出流作为参数传入,就像这样
1 BufferedInputStream bufferedInputStream=new BufferedInputStream (fileInputStream);
同样的不要忘记关闭流(只需要关闭缓冲流,对应的输入输出流同时会被关闭)
接下来请考虑一个问题,正常的输入流和输出流可以反复读取已经读取完成的内容吗(在较老版本的jdk中可以但不推荐),事实上是不存在这样的方法的.数据的读取过程实际上是一个指针的移动过程,是不可逆的.但是在缓冲输入流中却实现了这样的方法,,因为在缓冲流中数据被一字节或字符数组的形式保存在内存中,数组当然能做到随机查询了
看两个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public void mark (int readlimit) { if (lock != null ) { lock.lock(); try { implMark(readlimit); } finally { lock.unlock(); } } else { synchronized (this ) { implMark(readlimit); } } }public void reset () throws IOException { if (lock != null ) { lock.lock(); try { implReset(); } finally { lock.unlock(); } } else { synchronized (this ) { implReset(); } } }将读取指针回退到最后一次使用mark处
用这两个方法就可以实现对同一段内容的多次读入
转换流 在网络传输时所有的传输都是通过字节流的方式进行的,但受到信息进行展示时我们需要将这些数据转换为字符,该怎么做呢,其实不同的包中提供了很多方法实现,我这里提供一种最常用的–转换流
1 2 3 4 5 6 7 public static void main (String[] args) { try (OutputStreamWriter writer = new OutputStreamWriter (new FileOutputStream ("test.txt" ))){ writer.write("lbwnb" ); }catch (IOException e){ e.printStackTrace(); } }
1 2 3 4 5 6 7 public static void main (String[] args) { try (InputStreamReader reader = new InputStreamReader (new FileInputStream ("test.txt" ))){ System.out.println((char ) reader.read()); }catch (IOException e){ e.printStackTrace(); } }
非常的简单
对象流 对象流提供了将对象直接进行传输的方法,使用这种流可以直接将数据转换为一个对象,下面给一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Main { public static void main (String[] args) { try (ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("output.txt" )); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("output.txt" ))){ People people = new People ("aaa" ); outputStream.writeObject(people); outputStream.flush(); people = (People) inputStream.readObject(); System.out.println(people.name); }catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
当然,要求传输的对象必须实现序列化
1 2 3 4 5 6 ublic class People implements Serializable { String name; public People (String name) { this .name = name; } }
关于序列化到底是什么东西,我们会在将来的网络编程部分学到(注意,序列化后的内容人类无法直接阅读,所以输出的会是一串乱码),但读取时自带的反序列化可以将数据还原
如果我们希望对象的某个属性不被传输,则添加transient
关键字,就像下面这样
1 2 3 4 5 6 7 ublic class People implements Serializable { transient String name; public People (String name) { this .name = name; } }
此时的name就无法被序列化,数据传输时也不会包含name.如果你有阅读部分源码,就会发现很多源码中就有这个关键字用来避免被序列化
结语 其实流还有好几种,例如打印流,数据流等,但是并不是很常用,所以我就没有写,有兴趣的可以自行了解
放张图