目录
概述
java 对数据的操作是通过流的形式,流可以这样分类:
- 按流向
- 输入流
- 输出流
- 按操作数据类型
- 字节流(基类:InputStream、OutputStream)
- 字符流(基类:Reader、Writer)
read() 方法为什么返回类型是 int 而不是 byte ?
FileInputStream fis = new FileInputStream("java.txt");
int b;
while((b = bis.read()) != -1){
System.out.println(b);
}
fis.close();
read()
:从此输入流中读取一个数据字节。
解释:因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式存储,如果每次读取都返回 byte,那就有可能在读到中间的时候遇到 11111111,而这 11111111 是 byte 类型的 -1,我们的程序是遇到 -1 就会认为是读到了文件末尾,从而停止读取,可是实际上这种情况并不是文件的末尾,这样会导致后面的数据读取不到,所以在读取的时候选择用 int 类型来接受,如果 11111111 会在其前面补上 24 个 0 凑足 4 个字节,那么 byte 类型的 -1 就变成 int 类型的 255 了,这样可以保证整个数据读完,而结束标记的 -1 就是 int 类型。
FileOutputStream() 示例
FileOutputStream fos = new FileOutputStream("android.txt"); // 默认的每次写入会把之前的内容情况掉
// 第二个参数为 true 表示该文件可以续写
// FileOutputStream fos = new FileOutputStream("android.txt",true);
fos.write(97);
fos.write(98);
fos.close();
字节输出流,如果指定的写入文件没有创建,那么该流会自动帮你创建。
拷贝文件
FileInputStream fis = new FileInputStream("交大文件.mp4");
FileOutputStream fos = new FileOutputStream("copy.mp4");
byte[] byte = new byte[1024 * 2];
int len;
while((len = fis.read(byte)) != -1){
fos.write(byte,0,len);
}
fis.close();
fos.close();
高效字节流
- BufferedInputStream
- BufferedOutputStream
带缓冲区的拷贝
FileInputStream fis = new FileInputStream("大文件.avi");
FileOutStream fos = new FileOutputStream("copy.avi");
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
int b;
while((b = bis.read()) != -1){
bos.write(b);
}
bis.close();
bos.close();
高效的原因:
缓冲区中定义了字节数组,缓冲区的容量比较大(8192个字节),而直接和文件对接的并不是缓冲流(BufferedInputStream 和 BufferedOutputStream),而是文件操作流(FileInputStream 和 FileOutputStream)。虽然这里看似是一个字节一个字节的读取,但是并不像 FileInputStream 那样直接存入内存,而是先存入缓冲区,当达到缓冲区的容量时,再一次性写入内存(缓冲流的操作是在内存中进行的,所以速度特别快)。写入也是相同的道理。
JDK1.7 处理流标准写法
private void handleExceptionJDK7() throws IOException {
try(
FileInputStream fis = new FileInputStream("java.txt");
FileOutputStream fos = new FileOutputStream("javaTry.txt");
){
int b;
while((b = fis.read()) != -1){
fos.write(b);
}
}
}
JDK1.7 提供了一个流自动关闭的接口 AutoCloseable
,里面有一个抽象方法 close()
,上面的写法会自动调用该抽象方法来关闭流。
字符流应用场景
Java 中字符流是专门针对一段文本进行只读操作,或者只写操作提供的。它的读取原理是先将字符转为字节,写出的时候再讲字节转为字符。
字符流不能读写非文本数据。
因为在将字符转为字节的时候很有可能遇到某个字符在码表中找不到对应的字节(写出也一样的道理),导致最后的结果不是正确的。
其他字符流
LineNumberReader
字符输入流,继承自 BufferedReder。该类提供了 setLineNumber(int )
和 getLineNumber()
方法分别来设置行号和获取行号。
可以设置字符编码进行读写的字符流
InputStremReader 和 OutputStreamWriter 。前者是通过设置指定的字符编码读取字节,然后转为字符;后者则是通过指定字符编码将字符转为字节写入。
PrintWriter
继承自 Writer,字符标准输出流,该流不会报出 IOException。
其他字节流
序列流
SequenceInputStream
继承自 InputStream,将多个输入流整合为一个有序的输入流。提供两个构造方法:
SequenceInputStream(Enumeration<? extends InputStream> e) // 初始化新创建 SequenceInputStream 通过记住参数,它必须是一个 Enumeration 产生对象,它们的运行时类型是 InputStream 。 SequenceInputStream(InputStream s1, InputStream s2) // 通过记住两个 SequenceInputStream 来初始化新创建的 SequenceInputStream,这些参数将按顺序读取,首先是 s1 ,然后是 s2 ,以提供要从此 SequenceInputStream 读取的字节。
应用场景
比如将多个音频文件合成一个时,可以使用该流来做。示例代码如下:
FileInputStream fis1 = new FileInputStream("hello world.mp3"); FileInputStream fis2 = new FileInputStream("because of you.mp3"); FileInputStream fis3 = new FileInputStream("love.mp3"); Vector<FileInputStream> vector = new Vector<>(); vector.add(fis1); vector.add(fis2); vector.add(fis3); Enumeration<FileInputStream> enumer = vector.elements(); SequenceInputStream sis = new SequenceInputStream(enumer); FileOutputStream fos = new FileOutputStream("singAll.mp3"); int b; while((b = sis.read()) != -1){ fos.write(b); } sis.close(); // 这句代码会自动关闭它所整合的流 fos.close();
内存输出流
ByteArrayOutputStream
将字节写入内存缓冲区,这个缓冲区会自动扩容,然后一次性写出的字节输出流。该流调用 close() 方法没有任何效果。该类提供了两个构造,一个空参;一个可以指定缓冲区容量大小(int 类型,以字节为单位)的构造。
示例代码
FileInputStream fis = new FileInputStream("java.txt"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] arr = new byte[5]; int len; while((len = fis.read(arr)) != -1){ bos.write(arr,0,len); } fis.close();
应用场景:GUI编程聊天输入内容,点击发送时,把输入的数据一次性发出去。
对象操作流
ObjectOutputStream 将 Java 对象的原始数据类型和图形写入 OutputStream。 可以使用 ObjectInputStream 读取(重构)对象。 可以通过使用流的文件来实现对象的持久存储。需要注意的是,操作的对象必须实现序列化接口 Serializable ,写入和读取的方法分别是 writeObject(Object obj)、readObject()。
示例代码
// 写入对象
ObjectOutputStream oos = new ObjectOutputStream("Coder.txt");
/**
* 这里的 Coder 是实现了 Serializable 接口的实体对象
* 并且重写了 toString() 方法
* 建议给实体类写上序列 ID,没有其他作用,如果出错了好排查问题,比如:当你修改了该实体的属性时,没有写入,而* * 是直接进行读取操作,那么如果不加序列ID,系统会自动给你一个ID,这样就不容易想到是什么原因导致的。
*/
List<Coder> list = new ArrayList<>();
list.add(new Coder("java",3));
list.add(new Coder("android",5));
list.add(new Coder("python",2));
oss.writeObject(list);
oss.close();
// 读取对象
ObjectInputStream ois = new ObjectInputStream("Coder.txt");
ArrayList<Coder> list = (ArrayList<Coder>)ois.readObject();
for(Coder coder : list){
System.out.println(coder);
}
ois.close();
数据输入输出流
该流提供了 Java 基本数据类型的输入和输出,都有对应的方法,直接调用即可。
打印流
字节输出流,该流能够方便地打印各种数据值。 还提供了另外两个功能。 与其他输出流不同, PrintStream
从不抛出IOException
。
标准输出流
PrintStream ps = System.out;
ps.println();
// 上面两句代码等同于
System.out.println();
该流的构造提供了一个是否开启自动刷新的参数,如果启用自动刷新,只会在调用的
println,printf,
或format
方法时生效。
随机访问流
RandomAccessFile 该流既不属于字节流也不属于字符流,该类继承自 Object ,却有着流的功能,特点是集读和写于一身。该类提供了 seek() 方法就是从指定位置去读或者写数据。
构造方法
RandomAccessFile(File file, String mode)
// 创建一个随机访问文件流从File参数指定的文件中读取,并可选地写入文件。
RandomAccessFile(String name, String mode)
// 创建随机访问文件流,以从中指定名称的文件读取,并可选择写入文件。
应用场景
多线程下载。
Properties
该类继承自 Hashtable,但却没有泛型,使用该类所保存的数据是本地持久化的,强烈建议开发者使用 setPropertity(String key, String value) 方法进行存储数据,因为类型都是 String ,如果使用继承自父类的 putXXX() 方法的话,这样存储的数据类型就多了,失去了该类的本身意义。
示例代码1
Properties proper = new Properties();
proper.setPropertity("minSize","256");
proper.setPropertity("maxSize","1024");
Emuneration<String> enumer = (Emumeration<String>)proper.propertyNames();
while(enumer.hasMoreElements()){
String key = enumer.nextElement();
String value = proper.getProperty(key);
System.out.println(key + " == "+ value);
}
示例代码2
读取配置文件、修改配置文件
创建配置文件,名为config.perperties
minSize=256 maxSize=1024 author=zhangsan
读取
Properties pro = new Properties(); pro.load(new FileInputStream("config.properties")); System.out.println(pro);
修改
// 将上面的 author 值改为 lisi ... pro.setProperty("zuthor","lisi"); pro.store(new FileOutputStream("config.properties"),"属性列表描述,不需要可以传 null");
总结
学习记录,画一张图。