java IO篇

java中为什么BufferedReader,BufferedWriter 要比 FileReader 和 FileWriter高效?

其原理是它开始会先从 IO 读取 8k(默认的设置) 内容到自己设置的内存缓冲区内。然后通过read的时候就从缓冲区里面读取内容

File类

File类能新建,删除,重命名文件和目录,但不能访问文件内容本身,这个需要输入和输出流,详细的用法可查看API文档。

文件过滤器FileNameFilter

FileFilter相似。通过accept()方法来过滤得到对应需要的数据。需结合File类下的list进行作用

java输入流抽象基类InpuStreamReader

以内存的基点,数据输入到内存的统称为输入流,而从内存输出到磁盘的,统称为输出流。
其中InpuStream字节流,而Reader字符流

字节流和字符流

两者的主要区别在于所操作的数据单元不同,字节的数据单元为8位的字节,字符流的数据单元为16位的字符。

节点流和处理流(如图显示)

使用处理流的优势:

  1. 性能提高:增加缓冲的方式提高输入和输出的效率
  2. 操作便捷,提供一系列方法来一次输入/输出大批量的内容。

方法使用
InpuStreamReader

  • read(byte[] b)read(char[] cbuf),将参数相当于竹筒,每次取竹筒的容量,这样一段一段的取。直到不能取的时候,返回-1,而取到的时候返回对应的长度

实例代码:

1
2
3
4
5
byte[] bytes=new byte[1024];//以1024为一组进行读取
StringBuilder strBuilder=new StringBuilder();
while (inputStream.read(bytes)!=-1){
strBuilder.append(new String(bytes,"UTF-8"));
}

注意:使用read(byte[] b)读取的时候中文乱码问题。有可能是读到了半个中文字符导致,因为中文字符占2个字节(在GBK编码下),所以对于有中文的情况下,建议使用字符流

OutputStreamwriter:

  • write(byte[] b),write(char[] cbuf)write(String str),方法的解释跟上面差不多。

实例代码:
byte/char数组,要进行循环写入

1
2
3
4
byte[] bytes=new byte[1024];//以1024为一组进行读取
while (inputStream.read(bytes)!=-1){
fos.write(bytes);
}

字符串String:

1
2
3
4
5
6
byte[] bytes=new byte[1024];//以1024为一组进行读取
StringBuilder strBuilder=new StringBuilder();
while (inputStream.read(bytes)!=-1){
strBuilder.append(new String(bytes,"UTF-8"));
}
fos.write(strBuilder.toString());

处理流:(封装底层实现的细节,更简单使用)
PrintStream类使用细节:

1
2
3
fileOutputStream=context.openFileOutput(fileName, Context.MODE_PRIVATE);//MODE_PRIVATE 覆盖文件的内容
PrintStream ps=new PrintStream(fileOutputStream);
ps.print(dataMsg);

我们可以对比下细节,可以看到简化了设置节点数组的操作new byte[1024]。我们查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public synchronized void print(String str) {
if (out == null) {
setError();
return; }
if (str == null) {
print("null");
return; }

try {
if (encoding == null) {
write(str.getBytes());
} else {
write(str.getBytes(encoding));
}
} catch (IOException e) {
setError();
}
}

最后还是回到write(str.getBytes());进行写入,原理一样的。

总结

字节流的功能比字符流的功能强大。

  • 字节流处理二进制文件。
  • 字符流处理文本内容。(编码问题)

总结写文件步骤

  1. 得到指定的路径:Path
    1. 可选操作通过Path得到File
  2. 通过FileOutputStream的构造函数得到OutputStream

下面两种方式

  1. 封装byte[]数组,将数据读入byte[]数据,然后outputStream.write进行写入
1
2
3
4
5
int byteCount = 0;
byte[] bytes = new byte[1024];
while ((byteCount = inputStream.read(bytes)) != -1){
outputStream.write(bytes);
}
  1. BufferedWriter封装outputStream,然后进行write,这种方式为string的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
InputStreamReader inputreader = new InputStreamReader(is);
BufferedReader buffreader = new BufferedReader(inputreader);//封装输入流

//封装输出流
BufferedWriter bufWriter=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath)));

String line;
StringBuffer sb=new StringBuffer();
while ((line = buffreader.readLine()) != null) {
//将字符串存进文件中
sb.append(line+"\n");
}
bufWriter.write(sb.toString());

这里使用字符串拼接最好使用StringBuffer,因为线程安全

管道流(Piped)

实现进程之间通信功能。

转化流InputStreamReaderOutputStreamWriter

字节流转化成字符流

实际使用:(再转成BufferedReader更方便)

1
bufReader=new BufferedReader(new InputStreamReader(context.openFileInput(fileName)));

推回输入流PushbackInputStream

方法:unread(byte/char [] b)内容推回到缓存区,从而实现可重复读取,到每次read的时候,总是从推回缓冲区读取,只有当完全读取完才读源数据,推回缓存区默认为1,但可自行设置。

重定向标准输入/输出

改变system.in和system.out,将默认输出到屏幕的,转化为输出到文件上

实例使用:(关键代码)
输出:

1
2
3
4
PrintStream ps=new PrintStream(new FileOutStream("test.txt"));
//重定向系统输出
System.setOut(ps);
System.out.prinln("hello world");

通过上面代码,可将hello ,..输出到test.txt文件上。

输入:

1
2
3
4
5
6
7
FileInputStream fis=new FileInputStream("test.txt");
System.setIn(fis);
Scanner sc=new Scanner(System.in);
sc.useDelimiteer("\n");
while(sc.hasNext()){
System.out.println("键盘键入内容:"+sc.next());
}

通过上面修改,不再以键盘输入为标准输入,而是以test.txt为标准输入源。

java虚拟机读取其他进程的数据

使用的是Runtime.exec()的方法。

RandomAccessFile(任意访问)

提供read和write即:可读可写。而且,能够在任意地方读写数据,而不是从文件的开头进行顺序读写。使用记录指针实现

  • long getFilePointer() 返回记录指针当前位置
  • void seek(long pos) 定位到指定位置

创建RandomAccessFile对象,需要设置访问模式r只读,rw读写,…
实现追加内容

1
2
raf.seek(raf.length());//移动记录指针开始的位置
raf.write(addContent.getBytes());//追加的内容。

然后,RandomAccessFile并不能向文件插入指定的内容,盲目向指定文件位置write只会覆盖而已
替换方式,将指定位置的后面进行缓存,然后将需要插入的内容追加到末尾,最后将缓存的数据再追加到原来的位置上。

实现代码:

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
public void randomAccessFileInsert(File fileSDPath, int pos,String insertContent){
RandomAccessFile raf=null;
FileOutputStream fos=null;
FileInputStream fis=null;
try {
File tmp=File.createTempFile("tmp",null);//创建临时文件,退出将消失。参数分别为prefix:文件名,suffix:后缀名(默认.tmp)
tmp.deleteOnExit();//加入到对应的路径上
raf=new RandomAccessFile(fileSDPath,"rw");
//为临时文件创建对应的流
fos=new FileOutputStream(tmp);
fis=new FileInputStream(tmp);

raf.seek(pos);
//将插入点后面的数据读入到临时文件进行保存
byte[] bytes=new byte[64];
int hasRead=0;
while ((hasRead=raf.read(bytes))>0){
fos.write(bytes,0,hasRead);
}
//进行重新记录指针的位置pos,数据改变会改变,需要重新
raf.seek(pos);
raf.write(insertContent.getBytes());//追加内容
while ((hasRead=fis.read(bytes))>0){//追加临时文件的内容
raf.read(bytes,0,hasRead);
}

} catch (IOException e) {
Log.e(TAG, e.getMessage());

}finally {

try {
if(raf!=null)
raf.close();

if(fis!=null)
fis.close();

if(fos!=null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

其中临时文件deleteOnExit方法,看下源码就知道怎么回事了。

对象序列化(ObjectInputstream

将java对象转成与平台无关的二进制流,再保存磁盘或者进行网络传输。序列化进行传输,反序列化进行恢复java对象

两个接口:(对象序列化必须实现的接口)

  • Serializable
  • Externalizable

关键使用代码:

1
2
3
4
5
6
7
# 读取
ObjectInputStream ois=new ...;
Person p=(Person)ois.readObject;

# 写入
...
oos.writeObject(new Person);

注意:父类如果没有实现接口,则必须为无惨的构造函数

对象引用的序列化

当一个类中的元素存在其他类的引用时,此引用也必须实现接口,可序列化。

序列化算法

序列化的时候会检测该对象是否已序列化(检测序列化编号),如果存在则返回编号,不存在则创建。
如果改变对象的field,再进行write。最后结果将无法显示已改变的field

1
person per=new Person("孙",500);
oos.writeObject(per);
per.setName("珠");
oos.writeObject(per);
Person p1=ois.readObject();
Person p2=ois.readObject();
System.out.println(p1=p2);//将返回true;
System.out.println(p2.getName());//将输出"孙"

Serializable和Externalizable两者的区别:


对象序列化需要注意的:

  • static/transiend field不可序列化

版本的兼容

1
private static final long serialVersionUID=512L;

方法介绍

  • 什么时候该调用flush()方法

    当输出内容大于8K的时候并不需要调用。但通常会进行close()的操作,而这个操作会调用flush().所以养好习惯去调用flush()/close()操作。

类型换换

  • FileInputStream -> InputStream (注意,本来InputStream就是FileInputStream的父类)
    1
    2
    3
    4
    5
    6
    // 如果是FileInputStream类型,进行转换
    if (in instanceof FileInputStream) {
    fin = (FileInputStream) in;
    } else { // 否则,转型失败
    throw new Exception();
    }