java文件操作---概述

字符与字节

该部分转载字符与字节

在Java中有输入、输出两种IO流,每种输入、输出流又分为字节流和字符流两大类。关于字节,我们在学习8大基本数据类型中都有了解,每个字节(byte)有8bit组成,每种数据类型又几个字节组成等。关于字符,我们可能知道代表一个汉字或者英文字母。

但是字节与字符之间的关系是怎样的?

Java采用unicode编码,2个字节来表示一个字符,这点与C语言中不同,C语言中采用ASCII,在大多数系统中,一个字符通常占1个字节,但是在0~127整数之间的字符映射,unicode向下兼容ASCII。而Java采用unicode来表示字符,一个中文或英文字符的unicode编码都占2个字节。但如果采用其他编码方式,一个字符占用的字节数则各不相同。可能有点晕,举个例子解释下。

例如:Java中的String类是按照unicode进行编码的,当使用String(byte[] bytes, String encoding)构造字符串时,encoding所指的是bytes中的数据是按照那种方式编码的,而不是最后产生的String是什么编码方式,换句话说,是让系统把bytes中的数据由encoding编码方式转换成unicode编码。如果不指明,bytes的编码方式将由jdk根据操作系统决定。

getBytes(String charsetName)使用指定的编码方式将此String编码为byte序列,并将结果存储到一个新的 byte 数组中。如果不指定将使用操作系统默认的编码方式,我的电脑默认的是GBK编码。

1
2
3
4
5
6
7
8
9
10
public class Hel {  
public static void main(String[] args){
String str = "你好hello";
int byte_len = str.getBytes().length;
int len = str.length();
System.out.println("字节长度为:" + byte_len);
System.out.println("字符长度为:" + len);
System.out.println("系统默认编码方式:" + System.getProperty("file.encoding"));
}
}

输出结果

1
2
3
字节长度为:9
字符长度为:7
系统默认编码方式:GBK

这是因为:在 GB 2312 编码或 GBK 编码中,一个英文字母字符存储需要1个字节,一个汉字字符存储需要2个字节。 在UTF-8编码中,一个英文字母字符存储需要1个字节,一个汉字字符储存需要3到4个字节。在UTF-16编码中,一个英文字母字符存储需要2个字节,一个汉字字符储存需要3到4个字节(Unicode扩展区的一些汉字存储需要4个字节)。在UTF-32编码中,世界上任何字符的存储都需要4个字节。

简单来讲,一个字符表示一个汉字或英文字母,具体字符与字节之间的大小比例视编码情况而定。有时候读取的数据是乱码,就是因为编码方式不一致,需要进行转换,然后再按照unicode进行编码。

字节输入/输出流

这类流的操作是基于单个字节因此不便于处理以Unicode形式储存的信息,比如用这个操作中文字符会出现乱码

字节输入流
字节输出流

字符输入/输出流

这类流的操作都是基于两个字节的char值的(即,Unicode码元),而不是基于byte值的。

字符输入流
字符输出流

流的接口

Closeable

InputStream,OutputStream,ReaderWriter都实现了Closeable接口。

1
2
3
public interface Closeable extends AutoCloseable {
public void close() throws IOException;
}

从上面的定义可知Closeable扩展AutoCloseable.实现AutoCloseable的类都可以使用try-with-resource.

为什么需要两个接口,因为AutoCloseable.close方法可以抛出任何异常,而Closeable.close方法
只能抛出IOException

Flushable

OutputStreamWriter实现了Flushable

Readable

Reader实现了Readable接口

1
2
3
4
public interface Readable {
//CharBuffer可以按顺序或随机进行读写
public int read(java.nio.CharBuffer cb) throws IOException;
}

Appendable

只有Writer实现了Appendable接口

1
2
3
4
5
6
7
8
9

public interface Appendable {

Appendable append(CharSequence csq) throws IOException;

Appendable append(CharSequence csq, int start, int end) throws IOException;

Appendable append(char c) throws IOException;
}

文件的操作

File

File 是磁盘文件和目录路径名的一种抽象形式,其直接继承自 Object,实现了 Serializable 接口和 Comparable 接口;实现 Serializable 接口意味着 File 对象可以被序列化,而实现 Comparable 接口意味着 File 对象可以比较大小;此外 File 并没有什么特殊之处,就是对文件的一种上层抽象封装实现,方便操作文件。

File类共提供了四个不同的构造函数,以不同的参数形式灵活地接收文件和目录名信息。构造函数:

1
2
3
4
File (String pathname)
File(URI uri)
File (String parent , String child)
File (File parent , String child)

File的常用方法

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
public boolean exists( ) 判断文件或目录是否存在

public boolean isFile( ) 判断是文件还是目录

public boolean isDirectory( ) 判断是文件还是目录

public String getName( ) 返回文件名或目录名

public String getPath( ) 返回文件或目录的路径。

public long length( ) 获取文件的长度

public String[ ] list () 将目录中所有文件名和目录名保存在字符串数组中返回

public File[] listFiles() 返回某个目录下所有文件和目录的绝对路径,返回的是File数组

public String getAbsolutePath() 返回文件或目录的绝对路径

public boolean renameTo( File newFile ); 重命名文件

public void delete( ); 删除文件

public boolean mkdir( ); 创建单个文件夹

public boolean mkdirs();可以建立多级文件夹

public boolean createNewFile(); 创建文件

注意:当需要创建D:\\test\\test1.txt文件时,如果test目录不存在,则不可以直接使用createNewFile()创建;
同样不能使用mkdir(),因为mkdir()会把D:\\test\\test1.txt中的testtest1.txt都当做文件夹而mkdir()
只能创建一个文件夹;如果使用mkdirs()则会生成testtest1.txt两个文件.

创建一个文件夹和文件

1
2
3
4
5
File file = new File("D:\\test");
if(file.mkdir()){
File f = new File(file,"test1.txt");
f.createNewFile();
}

FileDescriptor

转载Little丶Jerry

File 是磁盘文件和目录路径名的一种抽象形式,其直接继承自 Object,实现了 Serializable 接口和 Comparable 接口;实现 Serializable 接口意味着 File 对象可以被序列化,而实现 Comparable 接口意味着 File 对象可以比较大小;此外 File 并没有什么特殊之处,就是对文件的一种上层抽象封装实现,方便操作文件。

FileDescriptor 是文件描述符,用来表示开放文件、开放套接字等。当 FileDescriptor 表示文件时,我们可以通俗的将 FileDescriptor 看成是该文件,但是不能直接通过 FileDescriptor 对该文件进行操作,若要通过 FileDescriptor 对该文件进行操作,则需要新创建 FileDescriptor 对应的 FileOutputStream,然后再对文件进行操作。

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 final class FileDescriptor {
/**
* 在形式上是一个非负整数。
* 实质是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。
* 当程序打开一个现有文件或者创建一个新文件时内核向进程返回一个文件描述符。
*/
private int descriptor;

public /**/ FileDescriptor() {
descriptor = -1;
}

private /**/ FileDescriptor(int descriptor) {
this.descriptor = descriptor;
}

/**
* (03)err -- 标准错误输出(屏幕)的描述符
* @see java.lang.System#in
*/
public static final FileDescriptor in = dupFd(0);

/**
* (01)in -- 标准输入(键盘)的描述符
* @see java.lang.System#out
*/
public static final FileDescriptor out = dupFd(1);

/**
* (02)out -- 标准输出(屏幕)的描述符
* @see java.lang.System#err
*/
public static final FileDescriptor err = dupFd(2);

......
//工具构造方法
private static FileDescriptor dupFd(int fd) {
try {
return new FileDescriptor(Os.fcntlInt(new FileDescriptor(fd), F_DUPFD_CLOEXEC, 0));
} catch (ErrnoException e) {
throw new RuntimeException(e);
}
}
......
}

所以,针对 System.X 的 API 来说 FileDescriptor 是一种更加底层的操作,其不与文件名相互关联,只与操作系统中对应文件的句柄关联。

RandomAccessFile

RandomAccessFile类可以在文件的任何位置查找或写入数据

构造方法

1
2
3
4
5
6
//模式有四种:r 表示只读 rw 表示读写模式  rws 表示每次更新时都对数据和元数据
//的写磁盘操作进行同步 rwd 表示每次更新时,只对数据的写磁盘操作进行同步
//注:元数据即数据的信息,比如数据的创建修改时间,数据是文件还是目录等
RandomAccessFile file = new RandomAccessFile("文件名","模式");

RandomAccessFile file1 = new RandomAccessFile(File,"mode");

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
long getFilePointer() 返回文件指针的位置

void seek(long pos) 将文件指针设置到距文件开头的pos字节处

long length() 返回文件按照字节来度量的长度

final void writeBoolean(boolean val)
final void writeByte(int val)
final void writeBytes(String str)
final void writeChar(int val)
final void writeChars(String str)
final void writeDouble(double val)
final void writeFloat(float val)
final void writeInt(int val)
final void writeLong(long val)
final void writeShort(int val)
final void writeUTF(String str) 使用utf-8编码格式写入字符串
final void write() 写入字节

注:在Java中char有两个字节,double有八个字节,int有四个字节
float有四个字节,boolean有一个字节

详细的参考:

java io系列26之 RandomAccessFile
RandomAccessFile类使用详解
RandomAccessFile 文件读写中文乱码解决方案