long blogs

进一步有进一步惊喜


  • Home
  • Archive
  • Tags
  •  

© 2025 long

Theme Typography by Makito

Proudly published with Hexo

Tij-IO-笔记

Posted at 2019-12-30 java IO 

File类

目录列表器

查询一个文件夹中的文件数据,使用File类的list()函数获得该目录下的文件数据,返回String数组。包括普通的文件名称和文件夹。list()接收一个FilenameFilter的实现类实例来过滤获得相应的文件。

1
2
3
public interface FilenameFilter{
boolean accept(File dir, String name);
}

如何使用该过滤器?当调用list函数的时候会调用该接口的accept()函数来判断是否保留该文件数据返回。每个文件都会调用该函数一次,自己在accept()函数里面设置规则,就可以获得相应的文件数据。

目录的检查及创建

File代表的是一个文件或目录,文件的相关信息都可以使用File中的函数来获得。下面是常用的函数。

函数名 作用
getAbsolutePath() 获得绝对路径,不会处理.和..
getCanonicalPath() 获得标准的绝对路径。将.和..解析之后返回路径
canRead()
canWrite()
getName()
getParent() 解析File文件中路径的父路径。只是对构造文件对象时输入的path字符串
getPath() 获得文件路径,构造时传进来的文件路径
length() 返回文件的字节大小,long类型
lastModified() 上一次修改时间戳,long类型
isFile()
isDirectory()
exists()
createNewFile
renameTo(File f) 重命名
delete()
mkdirs() 创建文件/目录(可以多级创建)
mkdir() 仅一级创建。路径的上一个文件夹必须存在才能创建。

getParent()、getName()、getPath()、getAbsolutePath()、getCanonicalPath()的区别

1
2
3
4
5
6
File f = new File(".\\pom.xml");
System.out.println(f.getParent());
System.out.println(f.getName());
System.out.println(f.getPath());
System.out.println(f.getAbsolutePath());
System.out.println(f.getCanonicalPath());

ouput:

1
2
3
4
5
.
pom.xml
.\pom.xml
E:\Project\Java\.\pom.xml
E:\Project\Java\pom.xml

getParent()获得的只是路径中的上一层,构造对象f时传进去的是.\\pom.xml。是对该字符串解析。如果直接使用pom.xml构造f对象,该函数返回的是空值。

mkdirs()和mkdir()
创建”E:\test\test1\test2\test3”嵌套目录,使用mkdirs()可以顺利创建。但是使用mkdir()如果文件夹组”E:\test\test1\test2”存在,成功创建。否则创建失败。

输入和输出

添加属性和有用的接口

Reader和Writer

自我独立类:RandomAccessFile

I/O流的典型使用方式

  • 缓冲输入文件。使用缓冲来提高文件的输入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static String
read(String filename){
BufferReader in = new BufferReader(
new FileReader(filename)
);
String s;
StringBuilder sb = new StringBuilder();
while(s = in.readLine() != null){
sb.append(s+"\n");
// 使用如下代码是否更好?
/*
sb.append(s);
sb.append("\n");
*/
}
in.close();
return sb.toString();
}
  • 从内存读取文件。将文件加载进内存中,然后再从内存中读取数据。
1
2
3
4
5
6
7
8
StringReader in = new StringReader(
BufferedInputFile.read(filename)
);
int c;
// 从内存缓冲区读取文件的字节数据。
while((c = in.read()) != -1){
System.out.print((char)c);
}
  • 格式化的内存输入。读取格式化的数据,不需要自己进行格式化处理。
1
2
3
4
5
6
7
8
DataInputSteam in = new DataInputStream(
new ByteArrayInputStream(
BufferedInputFile.read(filename).getBytes()
)
);
while(in.available() != 0){
System.out.print((char)in.readBytes());
}
  • 文件输出
1
2
3
4
5
PrintWriter out = new PrintWriter(
new BufferedWriter(new FileWriter(filename))
);
// 将内容输出到文件中
out.println(str);
  • 最简单的创建文件输出
1
2
String filename = "Hello.java";
PrintWriter out = new PrintWriter(filename);
  • 存储和恢复数据。目标是写进去的数据能够正确的读出来。需要统一的写和读,编码统一才可以读出来。使用DataInputStream和DataOutputSteam来进行文件的读写。
    文件数据的读写。
写 读
writeUTF() readUTF()
writeDouble() readDouble()

更多的类型数据读写可以参考JDK.

  • 读写随机文件。
    使用RandomAccessFile 类来随机的读写文件。其它的输入输出流就只是从头开始读写,随机读写流可以使用seek()来读写文件。这是一个类似C中指针一样的东西,可以在特定的地方读写文件。但是这需要对文件的格式很熟悉才能定位到正确的地方。

标准I/O

标准的I/O中含有标准输入、标准输出、标准异常。在java中对应System.in、System.out、System.err。out和err已经被包装成PrintStream对象。

从标准输入中读取

1
2
3
4
5
6
7
BufferedReader stdin = new BufferedRead(
new InputStreamReader(System.in)
);
String s;
while((s = std.readLine()) != null && s.length() != 0){
System.out.println(s);
}

可以使用Scanner来读出特定的类型。

System.out转换成PrintWriter

1
2
// true 自动清空
PrintWriter out = new PrintWriter(System.out,true);

IO重定向。

标准IO一般默认是从控制台输入和输出的,但是可以通过重定向来改变标准IO的方向。

1
2
3
System.setIn(InputStream)
System.setOut(PrintStream)
System.setErr(PrintStream)

经过重定向之后就可以使用System.out、System.in、System.err。

进程控制

在java中运行其它的程序。使用Process类来运行程序。

1
2
3
4
5
6
// comnands 是运行命令的String[]
Process process = new ProcessBuilder(commands).start();
// 获得运行的输出结果
BufferedRead result = new BufferedReader(new InputStreamReader(process.getInputStream()));
// 获得程序运行的错误
BufferedReader errors = new BufferedReader(new InputStreamReader(process.getErrorStream()))

新的I/O

nio不用太过在意,旧的IO已经用nio来实现过了。

通道和缓冲器

唯一和通道交互得缓冲器为ByteBuffer。原始得字节形式输出和读取,没有办法输出和读取对象。也就是存进去得是什么,拿出来得也是什么。不会包装成String或者其它得对象。

FileChannels 文件通道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用文件通道写入数据
FileChannels fc = new FileOutputStream(filename).getChannel();
// 写入字节数组
Byte[] bytes = "Hello".getBytes();
fc.write(ByteBuffer.wrap(bytes));
fc.close();
// 通过文件通道读取数据
fc = new FileInputStream(filename).getChannel();
// 分配缓冲区大小
ByteBuffer buff = ByteBuffer.allocate(SIZE);
fc.read(buff);
// flip()作用是告诉外部,可以读取缓冲中的字节数据了。
buff.flip();
while(buff.hasRemaining()){
// 将字节数据转成字符
System.out.print((char)buff.get());
}
通过通道来复制文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 常量缓冲区的大小
final int BSIZE = 1024;
FileChannel
in = new FileInputStream(source).getChannel(),
out = new FileOutputStream(dest).getChannel();
// 缓冲区
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
while(in.read(buffer) != -1){
// -1标志为读取数据结束
// 准备好写
buffer.flip();
out.write(buffer);
// 准备好读
buffer.clear();
}

flip()表示ByteBuffer已经写完数据,你从ByteBuffer可以读出来了。
clear()表示ByteBuffer已经清空完数据了,你现在已经可以往ByteBuffer写数据了。
rewind() 返回到数据开始部分。

  • 两个通道相连除了通过上述中间变量之外,使用transferTo()和transFrom()可以将两个通道连起来。这两个不常用,知道就好。
1
2
3
4
// in => out
in.transferTo(0,in.size(),out);
// or
// out.transferFrom(in,0,in.size());
转换数据

要想ByteBuffer存储有意义的数据,比如说字符串数据。需要写入字节之前对字符串进行编码,或者在读出的时候进行解码。

1
2
3
4
// 获得字节数据的时候编码
"hello world".getBytes("UTF-16BE");
// 读取数据的时候解码
buffer.asCharBuffer();

使用java.nio.charset.Charset来实现编码和解码。

视图

由于通道存储的只是字节数据。不方便进行读取和管理,这时候需要一个视图缓冲器来作为中间人让使用者直接读写为整型、字符、长整型、浮点、双精度浮点型。
注意: 由于通道存储的是字节数据,转换成相应的数据类型会占用不同字节大小。

类型 字节大小
shorts 2
chars 2
ints 4
floats 4
longs 8
double 8

转换成Chars类型。

1
2
3
4
5
CharBuffer cb = ((ByteBuffer)bb.rewind()).asCharBuffer();
// 读取Char数据
while(cb.hasRemaining()){
System.out.print(cb.position() + "->" + cb.get()+ ",");
}

其中bb是一个ByteBuffer的对象。
同理转换成其它的类型的视图

1
2
3
4
5
FloatBuffer fb = ((ByteBuffer)bb.rewind()).asFloatBuffer();
IntBuffer ib = ((ByteBuffer)bb.rewind()).asIntBuffer();
ShortBuffer sb = ((ByteBuffer)bb.rewind()).asShortBuffer();
LongBuffer lb = ((ByteBuffer)bb.rewind()).asLongBuffer();
DoubleBuffer db = ((ByteBuffer)bb.rewind()).asDoubleBuffer();

字节存放次序

有些基本的数据类型需要占用两个及两个以上的字节。计算机的基本存储单位是字节,对于像字符这种占用2个字节,存放有高位优先和低位优先两种方式。java默认的是高位优先。也就是说字符‘a’在内存中存放次序是[0,97]。如果是低位优先则是[97,0]。优先次序使用order来设置。

1
2
3
4
// 高位优先
bb.order(ByteOrder.BIG_ENDIAN);
// 低位优先
bb.order(ByteOrder.LITTLE_ENDIAN);

字节存放次序并无优劣之分。重要的数据的一致性,存进去的和读出来的一致便可。

交换相邻两个字符

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
public class UsingBuffers {
private static void symmetricScramble(CharBuffer buffer){
while (buffer.hasRemaining()){
// 做标记
buffer.mark();
char c1 = buffer.get();
char c2 = buffer.get();
// 回到标记点
buffer.reset();
// 交换次序存储
buffer.put(c2).put(c1);
}
}

public static void main(String[] args) {
char[] data = "UsingBuffers".toCharArray();
// 一个字符占有两个字节
ByteBuffer bb = ByteBuffer.allocate(data.length*2);
CharBuffer cb = bb.asCharBuffer();
cb.put(data);
System.out.println(cb.rewind());
// 交换
symmetricScramble(cb);
System.out.println(cb.rewind());
// 交换
symmetricScramble(cb);
System.out.println(cb.rewind());
}
}

输出结果:

1
2
3
UsingBuffers
sUniBgfuefsr
UsingBuffers

性能

使用“映射文件访问”更好,更高效。下面是使用传统的io流和通道map的读写对比差距。使用映射速度更加显著。

1
2
3
4
5
6
Stream Write:73.48 
Mapped Write:0.02
Stream Read:0.06
Mapped Read:0.01
Stream Read/Write:6.05
Mapped Read/Write:0.01

使用mapped写数据

1
2
3
4
5
6
7
FileChannel fc = new RandomAccessFile("temp.tmp","rw").getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE,0,fc.size()).asIntBuffer();
// 使用通道写入数据
for (int i = 0; i < numOfInts; i++) {
ib.put(i);
}
fc.close();

使用mapped读数据

1
2
3
4
5
6
FileChannel fc = new FileInputStream(new File("temp.tmp")).getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_ONLY,0,fc.size()).asIntBuffer();
while (ib.hasRemaining()){
ib.get();
}
fc.close();

文件加锁

压缩

对象序列化

XML

Preferences Api

Share 

 Previous post: 那些年读过的书 Next post: TiJ String 笔记 

© 2025 long

Theme Typography by Makito

Proudly published with Hexo