Java中的文件和流相关知识

时间:2023-03-09 03:36:06
Java中的文件和流相关知识

1. File

  • File类可以使用文件路径字符串来创建File实例,该文件路径可以是绝对路径或相对路径
  • File类的list()方法中可以接收一个FilenameFilter参数,通过该参数可以只列出符合条件的文件
public class FileNameFilterTest {
public void main(String[] args) {
File file = new File(".");
String[] nameList = file.list(((dir, name) -> name.endsWith(".java") || new File(name).isDirectory()));
for (String name :
nameList) {
System.out.println(name);
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2. 流(Stream)

  • Stream是从起源(source)到接收(sink)的有序数据
  • 按照流向分可以分为输入流和输出流 
    • 输入流:只能从中读取数据,不能写入数据(基类是InputStream和Reader)
    • 输出流:只能向其中写入数据,不能读取数据(基类是OutputStream和Writer)
  • 按照操作的数据单元分为字节流和字符流 
    • 字节流:操作的数据单元是8位的字节(基类是InputStream和OutputStream)
    • 字符流:操作的数据单元是16位的字节(基类时Reader和Writer)
  • 按照角色可以分为节点流和处理流 
    • 节点流:可以从/向一个特定的IO设备中读/写数据的流,也被称为低级流
    • 处理流:用于对一个已存在的流进行连接或封装来实现读/写功能,也称为高级流或包装流

3. 字节流和字符流

  • 字节流输入
    public class FileInputStreamTest {
public void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("FileInputStreamTest.java");
byte[] bytes = new byte[1024];
int hasRead = 0;
while ((hasRead = fis.read(bytes)) > 0) {
System.out.println(new String(bytes,0,hasRead));
}
fis.close();
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 字节流输出
public class FileOutputStreamTest {
public void main(String[] args) {
try (FileOutputStream fileOutputStream = new FileOutputStream("file.txt");
FileInputStream fileInputStream = new FileInputStream("FileInputStreamTest.java")) {
byte[] bytes = new byte[1024];
int hasRead = 0;
while ((hasRead = fileInputStream.read(bytes)) > 0) {
fileOutputStream.write(bytes,0,hasRead);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 字符流输入
public class FileReaderTest {
public void main(String[] args) {
try (FileReader fileReader = new FileReader("FileInputStreamTest.java")) {
char[] chars = new char[1024];
int hasRead = 0;
while ((hasRead = fileReader.read(chars)) > 0) {
System.out.println(new String(chars,0,hasRead));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 字符流输出
public class FileWriterTest {
public void main(String[] args) {
try (FileWriter fileWriter = new FileWriter("poem.txt")){
fileWriter.write("锦瑟 -李商隐\r\n");// \r\n时windows平台的换行符
fileWriter.write("锦瑟无端五十弦,一弦一柱思华年\r\n");
fileWriter.write("庄生晓梦迷蝴蝶,望帝春心托杜鹃\r\n");
fileWriter.write("沧海月明珠有泪,蓝田日暖玉生烟\r\n");
fileWriter.write("此情可待成追忆,只是当时已惘然\r\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

4. 处理流/转换流

  • 构造器参数不是一个物理节点,而是已经存在的流
  • 关闭最上层的流时,会自动关闭被该处理流包装的节点流
  • 如果进行输入/输出的是文本内容,应当考虑字符流,如果进行输入/输出的内容时二进制内容,则应该考虑使用字节流
  • 转换流用于实现将字节流转换成字符流,InputStreamReader将字节输入流转换成字符输入流;OutputStreamWriter将字节输出流转换成字符输出流
  • BufferReader流具有缓冲功能

5. 重定向标准输入/输出

  • 重定向是指改变输入/输出目标(由键盘/屏幕改为文件)
  • 重定向输出流
public class RedirectOut {
public void main(String[] args) {
try (PrintStream printStream = new PrintStream(new FileOutputStream("out.txt"))){
System.setOut(printStream);
System.out.println("普通字符串");
System.out.println(new RedirectOut());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 重定向输入流
    public class RedirectIn {
public void main(String[] args) {
try (FileInputStream fileInputStream = new FileInputStream("poem.txt")) {
System.setIn(fileInputStream);
Scanner scanner = new Scanner(System.in);
scanner.useDelimiter("\n");//只把回车作为换行符
while (scanner.hasNext()) {
System.out.println("键盘输入内容:" + scanner.next());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

6. RandomAccessFile

  • RandomAccessFile支持任意访问,可以直接调整到文件的任意地方读写数据
  • 可以向已存在的文件后追加内容
  • 只能读写文件,不能读写其他IO节点
  • 包含一个记录指针,用以标识当前读写处的位置
  • 直接将文件记录指针移动到中间某位置后进行输出会覆盖原有的内容
  • 读取文件
public class RandomAccessFileTest {
public void main(String[] args) {
try (RandomAccessFile randomAccessFile = new RandomAccessFile("poem.txt", "r")) {
System.out.println("指针初始位置:" + randomAccessFile.getFilePointer());
randomAccessFile.seek(300);
byte[] buffer = new byte[1024];
int hasRead = 0;
while ((hasRead = randomAccessFile.read(buffer)) > 0) {
System.out.println(new String(buffer, 0, hasRead));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 向文件末尾追加内容
public class AppendContent {
public void main(String[] args) {
try (RandomAccessFile randomAccessFile = new RandomAccessFile("poem.txt", "rw")) {
randomAccessFile.seek(randomAccessFile.length());
randomAccessFile.write("追加的内容!\n".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 向指定位置添加内容
    public class InsertContent {
public void main(String[] args) throws IOException {
File file = File.createTempFile("tmp", null);
file.deleteOnExit();
try (RandomAccessFile randomAccessFile = new RandomAccessFile("poem.txt", "rw");
FileOutputStream tmpOut = new FileOutputStream(file);
FileInputStream tmpIn = new FileInputStream(file)) {
randomAccessFile.seek(300);
//将文件内容写入临时文件中
byte[] buffer = new byte[64];
int hasRead = 0;
while ((hasRead = randomAccessFile.read(buffer)) > 0) {
tmpOut.write(buffer, 0, hasRead);
}
randomAccessFile.seek(300);
randomAccessFile.write("插入的内容".getBytes());
//追加之前的内容
while ((hasRead = tmpIn.read(buffer)) > 0) {
randomAccessFile.write(buffer, 0, hasRead);
} }
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

7. 对象序列化

  • 对象序列化(Serialize)是指将一个Java对象写入IO流中
  • 对象反序列化(Deserialize)是指从IO流中恢复该Java对象
  • 序列化必须实现Serializable和Externalizable接口
  • 序列化
public class WriteObject {
public void test() {
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("object.txt"))) {
Person person = new Person("HelloWood", 22);
Person person1 = new Person("HoloWood", 33);
objectOutputStream.writeObject(person);
objectOutputStream.writeObject(person1);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 反序列化
public class ReadObject {
public void test() {
try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("object.txt"))) {
Person p = (Person) objectInputStream.readObject();
Person p1 = (Person) objectInputStream.readObject();
System.out.println(p.getName() + " " + p.getAge());
System.out.println(p1.getName() + " " + p1.getAge());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 反序列化时读取的仅仅时Java对象的数据,而不是Java类
  • 反序列化无须通过构造器来初始化Java对象
  • 如果序列化时写入多个对象,则反序列化时必须按实际写入顺序读取
  • 当一个可序列化类有父类时,这些父类必须时可序列化的
  • 当某个类的成员变量的类型不是基本类型或String类型而是引用类型时,这个引用类型必须也是可序列化的
  • 多次序列化同一个Java对象时,只有第一次序列化时才会把该Java对象转换成字节序列并输出
  • 当序列化某个对象后修改该对象的属性再序列化不会将修改后的属性输出
  • 当对某个对象进行序列化时,系统会自动把该对象的所有实例变量依次进行序列化
  • 如果在实例变量前用transient修饰,则该变量在实例化时会自动跳过
  • 如果重写了writeReplace()方法,则在序列化时会先调用该方法进行替换

8. NIO(New IO)

  • NIO采用内存映射的方式来处理输入输出,通过将文件或部分文件的一段区域映射到内存中进行访问
  • NIO中的所有数据都需要通过通道(Channel)传输
  • 传统IO面向流处理,NIO面向块处理
  • Buffer本质是一个数组

    • 容量(capacity):可以最大存储的数据量,不能为负值,创建后不可改变
    • 界限(limit):第一个不应该被读出或者写入的缓冲区位置索引,limit后的数据不能被读写
    • 位置(position):用于指明下一个可以被读出的或者写入的缓冲区位置索引
    • 标记(mark):标记,用于自定义记录位置
    • 0 <= mark <= position <= limit <= capacity
    • flip()方法将limit设置为position所在位置,将position置为0,为读取数据做准备
    • clear()将position置为0,limit置为capacity,但不清空数据,为再次装入数据做准备
  • Channel

    • 只能和Buffer交互
    • Channel应当通过XXXStream.getChannel()方法获取
     public class ReadFile {
public void test() {
try (FileInputStream fileInputStream = new FileInputStream("poem.txt");
FileChannel fileChannel = fileInputStream.getChannel()) {
ByteBuffer byteBuffer = ByteBuffer.allocate(256);
while (fileChannel.read(byteBuffer) != -1) {
byteBuffer.flip();
Charset charset = Charset.forName("UTF-8");
CharsetDecoder charsetDecoder = charset.newDecoder();
CharBuffer charBuffer = charsetDecoder.decode(byteBuffer);
System.out.println(charBuffer);
byteBuffer.clear();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

9. 文件锁

  • lock()锁定文件时如果无法得到文件锁,程序就会一直阻塞
  • tryLock()锁定文件时如果获得了文件锁,则会返回文件锁,否则返回null
  • tryLock(long position,long size,boolean shared) 
    • 当shared为true时,该锁是共享锁,将允许多个进程读取该文件
    • 当shared为false时,该锁时排他锁,将锁住对该文件的读写
    • 通过FileLock的release()方法释放文件锁
  • 文件锁时Java虚拟机所持有的
public class FileLockTest {
public void test() {
try (FileChannel fileChannel = new FileOutputStream("poem.txt").getChannel()){
FileLock lock = fileChannel.tryLock();
Thread.sleep(10000);
lock.release();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

10. NIO2

  • 使用FileVisitor遍历文件和目录
public class FileVisitorTest {
public void test() throws IOException {
Files.walkFileTree(Paths.get("/","home"),new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("正在访问:"+file+"文件");
if (file.endsWith("poem.txt")) {
System.out.println("---已经找到目标文件---");
System.out.println("文件目录:"+file);
return FileVisitResult.TERMINATE;
}
return FileVisitResult.CONTINUE;
} @Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("正在访问:"+dir+"路径");
return FileVisitResult.CONTINUE;
}
});
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 使用WatchService监控文件变化
    public class WatchServiceTest {
public void test() {
WatchService watchService = null;
try {
watchService = FileSystems.getDefault().newWatchService();
Paths.get("/home/alpha/IdeaProjects/out/production/IdeaProjects/").register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
System.out.println(event.context() + "发生了" + event.kind() + "事件");
}
boolean valid = key.reset();
if (!valid) {
break;
}
} } catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}