Java核心技术梳理-IO
一、引言
IO(输入/输出),输入是指允许程序读取外部数据(包括来自磁盘、光盘等存储设备的数据)、用户输入数据。输出是指允许程序记录运行状态,将程序数据输出到磁盘、光盘等存储设备中。
IO的主要内容包括输入、输出两种IO流,这两种流中又分为字节流和字符流,字节流是以字节为单位来处理输入、输出流,而字符流是以字符为单位来处理输入、输出流。
二、File 类
File 类是用来操作文件和目录的,File能创建、删除、重命名文件和目录,File不能访问文件内容本身,File 类可以通过文件路径字符串来创建对象,创建完对象之后有很多方法来操作文件和目录:
2.1 构造方法
File(String pathname):根据一个路径得到File对象
File(String parent, String child):根据一个目录和一个子文件/目录得到File对象
File(File parent, String child):根据一个父File对象和一个子文件/目录得到File对
2.2 创建方法
//在当前路径来创建一个File对象
File file = new File("1.txt");
//创建文件
System.out.println(file.createNewFile());
File file2 = new File("temp");
//创建对象对应的目录
System.out.println(file2.mkdir());
2.3 重命名和删除功能
//把文件重命名为指定的文件路径
file2.renameTo(new File("temp2"));
//删除文件或者文件夹
file2.delete();
注:重命名中如果路径名相同,就是改名,如果路径名不同,就是改名并剪切。删除不走回收站,要删除一个文件夹,请注意该文件夹内不能包含文件或者文件夹。
2.4 判断功能
//判断文件或目录是否存在
System.out.println(file.exists());
//判断是否是文件
System.out.println(file.isFile());
//判断是否是目录
System.out.println(file.isDirectory());
//是否为绝对路径
System.out.println(file.isAbsolute());
//文件或目录是否可读
System.out.println(file.canRead());
//文件或目录是否可写
System.out.println(file.canWrite());
2.5 获取功能
//返回文件内容长度
System.out.println(file.length());
//获取文件或目录名
System.out.println(file.getName());
//获取文件或目录相对路径
System.out.println(file.getPath());
//获取文件或目录绝对路径
System.out.println(file.getAbsolutePath());
//获取上一级路径
System.out.println(file.getAbsoluteFile().getParent());
//返回当前目录的子目录或文件的名称
String[] list = file1.list();
for (String fileName : list) {
System.out.println(fileName);
}
//返回当前目录的子目录或文件,返回的是File数组
File[] files = file1.listFiles();
//返回系统的所有根路径
File[] listRoots = File.listRoots();
for (File root : listRoots) {
System.out.println(root);
}
三、IO 流
实现输入/输出的基础是IO流,Java把不同的源之间的数据交互抽象表达为流,通过流的方式允许Java程序使用相同的方式来访问不同的数据源。用于操作流的类都在IO包中。
3.1 流的分类
按照不同的分类方式,流也可以分为不同类型
输入流和输出流:根据流向来分,可以分为输入流与输出流
输入流:从中读取数据,而不能向其写入数据
输出流:向其写入数据,而不能读取数据
字节流和字符流:这两种流用法几乎完全一样,区别在于所操作的数据单元不一样,字节流操作的数据单元是8位的字节,而字符流是16位的字符。
3.2 InputStream与Reader
InputStream和Reader是所有输入流的抽象基类,这是输入流的模板,InputStream中有三个方法
int read() :从输入流读取单个字节,返回所读取的字节数据。
int read(byte b[]):从输入流中最多读取b.length个字节的数据,并将其存储在数组b中。
int read(byte b[], int off, int len):从输入流中最多读取len个字节的数据,并将其存储在数组b中,放入的位置是从off中开始。
Reader中也有三个方法
int read() :从输入流读取单个字符,返回所读取的字符数据。
int read(char cbuf[]):从输入流中最多读取cbuf.length个字符的数据,并将其存储在数组cbuf中。
int read(char cbuf[], int off, int len):从输入流中最多读取len个字符的数据,并将其存储在数组cbuf中,放入的位置是从off中开始。
两个类的方法基本相同,用法相同,只是操作单位不一样
InputStream inputStream = new FileInputStream("StreamTest.java");
byte[] bytes = new byte[1024];
int hasRead = 0;
while ((hasRead = inputStream.read(bytes)) > 0) {
System.out.println(new String(bytes, 0, hasRead));
}
inputStream.close();
3.3 OutputStream与Writer
OutputStream与Writer是所有输出流的抽象基类,是输出流模板,OutputStream有三个方法:
void write(int b):指定字节输出到流中
void write(byte b[]):将指定字节数组输出到流中
void write(byte b[], int off, int len):将指定字节数组从off位置到len长度输出到流中
Writer中也有三个方法:
void write(int b):指定字符输出到流中
void write(char buf[]):将指定字符数组输出到流中
void write(char cubf[], int off, int len):将指定字符数组从off位置到len长度输出到流中
由于Writer是以字符为单位进行操作,那可以使用String 来代替,于是有另外的方法
void write(String str):将str字符串输出到流中
void write(String str, int off, int len):将str从off位置开始长度为len输出到流中
FileWriter fileWriter = new FileWriter("test.txt");
fileWriter.write("日照香炉生紫烟\r\n");
fileWriter.write("遥看瀑布挂前川\r\n");
fileWriter.write("飞流直下三千尺\r\n");
fileWriter.write("遥看瀑布挂前川\r\n");
fileWriter.close();
注:操作流时一定要记得关闭流,因为打开的IO资源不属于内存资源,垃圾回收无法回收。
四、输入/输出流体系
Java的输入输出流提供了40多个类,要全部都记住很困难也没有必要,我们可以按照功能进行下分类,其实是非常有规律的
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
过滤流 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
打印流 | PrintStream | PrintWriter | ||
退回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
一般如果输入/输出的内容是文本内容,应该考虑使用字符流,如果输入/输出内容是二进制内容,则应该考虑使用字节流。
4.1 转换流
体系中提供了两个转换流,实现将字节流转换成字符流,InputStreamReader将字节输入流转换成字符输入流,OutputStreamWriter将字节输出流转换成字符输出流,System.in代表标准输入,这个标准输入是字节输入流,但是键盘输入的都是文本内容,这个时候我们可以InputStreamReader转换成字符输入流,普通的Reader读取内容不方便,我们可以使用BufferedReader一次读取一行数据,如:
//先将System.in转换成Reader 对象
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
//再将Reader包装成BufferedReader
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line = null;
while ((line = bufferedReader.readLine()) != null) {
if (line.equals("exit")) {
System.exit(1);
}
System.out.println("输入的内容是:" + line);
}
BufferedReader具有缓冲功能,在没有读到换行符则阻塞,读到换行符再继续。
4.2 推回输入流
推回输入流PushbackInputStream和PushbackReader中都提供了如下方法:
void unread(int b) :将一个字节/字符推回到推回缓冲区,从而允许重复读取刚刚读取的内容。
void unread(byte[] b/char[] b, int off, int len) :将一个字节/字符数组里从off开始,长度为len字节/字符的内容推回到推回缓冲区,从而允许重复读取刚刚读取的内容。
void unread(byte[] b/char[]):将一个字节/字符数组内容推回到推回缓冲区,从而允许重复读取刚刚读取的内容。
这两个推回流都带有一个推回缓冲区,当调用unread()方法时,系统将会把指定的内容推回到该缓冲区,而当每次调用read方法时会优先从推回缓冲区读取,只有完全读取了推回缓冲区的内容后,但还没有read()所需的数组时才会从原输入流中读取。
//创建PushbackReader对象,指定推回缓冲区的长度为64
PushbackReader pushbackReader = new PushbackReader(new FileReader("StreamTest.java"), 64);
char[] buf = new char[32];
//用以保存上次读取的字符串内容
String lastContent = "";
int hasRead = 0;
//循环读取文件内容
while ((hasRead = pushbackReader.read(buf)) > 0) {
//将读取的内容转换成字符串
String content = new String(buf, 0, hasRead);
int targetIndex = 0;
if ((targetIndex = (lastContent + content).indexOf("new PushbackReader")) > 0) {
//将本次内容和上次的内容一起推回缓冲区
pushbackReader.unread((lastContent + content).toCharArray());
//重新定义一个长度为targetIndex的char数组
if (targetIndex > 32) {
buf = new char[targetIndex];
}
//再次读取指定长度的内容
pushbackReader.read(buf, 0, targetIndex);
//打印读取的内容
System.out.print(new String(buf, 0, targetIndex));
System.exit(0);
} else {
//打印上次读取的内容
System.out.print(lastContent);
//将本次内容设为上次读取的内容
lastContent = content;
}
}
五、RandomAccessFile
RandomAccessFile是Java输入/输出流体系中最丰富的文件内容访问类,提供了众多的方法来访问文件内容,既可读取文件内容,也可以向文件输出数据,RandomAccessFile可以自由访问文件的任意位置。
RandomAccessFile包含一个记录指针,用以标识当前读和写的位置,当创建新对象时,指针位置在0处,而当读/写了N个字节后,指针就会向后移动N个字节,并且RandomAccessFile可以自动的移动该指针位置,当然我们也可以直接的获取指针的位置。
getFilePointer():获取文件记录指针的当前位置。
seek(long pos):将文件记录指针定位到pos位置。
RandomAccessFile有两个构造函数:
RandomAccessFile(File file, String mode):使用File文件,指定文件本身 RandomAccessFile(String name, String mode):使用文件名称,指定文件
其中还有一个参数mode(访问模式),访问模式有4个值:
r:以只读方式打开文件
rw:以读、写方式打开文件,如果文件不存在,则创建
rws:以读、写方式打开文件,并要求对文件的内容或者元数据的每个更新都同步写入到底层存储设备
rwd:以读、写方式打开文件,并要求对文件的内容的每个更新都同步写入到底层存储设备
RandomAccessFile raf = new RandomAccessFile("StreamTest.java", "r");
System.out.println("文件指针的初始位置:" + raf.getFilePointer());
//移动指针位置
raf.seek(300);
byte[] buf = new byte[1024];
int hasRead = 0;
while ((hasRead = raf.read(buf)) > 0) {
//读取数据
System.out.println(new String(buf, 0, hasRead));
}
//追加内容
RandomAccessFile randomAccessFile=new RandomAccessFile("out.txt","rw");
randomAccessFile.setLength(randomAccessFile.length());
randomAccessFile.write("追加的内容!\r\n".getBytes());
六、对象序列化
对象序列化机制是允许把内存中的java对象转换成平台无关的二进制流,这样我们可以将这二进制流保存在磁盘上或者通过网络将起传输到另一个网络节点,其他程序获取到此二进制流后,可以将其恢复成原来的java对象。
要使一个对象是可序列化的,只需要继承Serializable或者Externalizable接口,无需实现任何方法。所有可能在网络上传输的对象的类都应该是可序列化的,如我们JavaWeb中的输入参数及返回结果。
6.1 使用对象流实现序列化
我们使用一个对象流来实现序列化对象
先建一个对象类:
@Data
public class Person implements Serializable { private int age; private String name; public Person(String name, int age) {
System.out.println("有参数的构造器");
this.age = age;
this.name = name;
}
}
序列化对象与反序列化对象
//创建输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("object.txt"));
Person person = new Person("张三", 10);
//将person写入文件中
objectOutputStream.writeObject(person);
//创建输入流
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("object.txt"));
try {
//读出数据
Person p = (Person) objectInputStream.readObject();
System.out.println(p);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
反序列化读取的仅仅是Java对象的数据,而不java类,因此反序列化时必须提供对象所属类的class文件,在反序列化对象时没有调用有参数的构造器,说明反序列化时不需要通过构造器来初始化Java对象。
如果一个类中包含了引用类型,那么引用类型也必须是可序列化的,否则该类也是不可序列化的。
如果我们不希望某个变量被序列化,比如敏感信息,那需要使用transient来修饰此变量即可。
七、NIO
上面学习的IO都是阻塞式的,而且是底层都是通过字节的移动来处理的,这样明显效率不高,于是后面新增了NIO来进行改进,这些类都放在java.nio包中。
新IO 是将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件中的内容,相当于虚拟内存概念,这种方式比传统的IO快很多。
新IO的两大核心对象是Channel(通道)与Buffer(缓冲),Channel与传统的InputStream、OutputStream最大的区别在于提供了一个map()方法,这个方法是将一块数据映射到内存中,这样新IO就是面向块进行处理;Buffer本质是一个数组,可以看做一个容器,发送到Channel中的所有对象都必须首先放在Buffer中,读取数据也是从Buffer中读取。
7.1 Buffer
Buffer是一个抽象类,最常用的子类是ByteChannel和CharBuffer,Buffer类都没有提供构造器,都是通过XXXBuffer allocate(int capacity) 来得到对象,如
CharBuffer allocate = CharBuffer.allocate(8);
Buffer有三个重要概念:
容量(capacity):缓冲区的容量,表示该buffer的最大数据容量,即最多可存储多少数据,创建后不可改变。
界限(limit):位于limit后的数据既不可以读,也不可以写。
位置(position):用于指明下一个可以被读出或写入的缓冲区位置索引,类似IO中的指针。
Buffer的主要作用是装入数据,然后输出,当创建buffer时,position在0位置,limit在capacity,当添加数据时,position向后移动。
当Buffer装好数据时,调用flip()方法,这个方法将limit设置为position,position设置为0,也就是说不能继续输入,这就给输出数据做好准备了,而当输出数据结束后,调用clear()方法,这是将position设置为0,limit设置为capacity,这样就为装入数据做好了准备。
除了上面的几个概念,Buffer还有两个重要方法,即put()与get()方法,就是存储与读取数据方法,在存储和读取数据时,分为相对和绝对两种:
相对:从Buffer的position位置开始读取或者写入数据,这时候会改变position的数值。
绝对:根据索引读取或写入数据,这个时候不会影响position的数值。
//创建buffer
CharBuffer buffer = CharBuffer.allocate(10);
System.out.println("capacity: " + buffer.capacity());
System.out.println("limit:" + buffer.limit());
System.out.println("position:" + buffer.position());
//加入数据
buffer.put('a');
buffer.put('b');
buffer.put('c');
System.out.println("加入元素后,position:" + buffer.position());
buffer.flip();
System.out.println("执行flip后,limit:" + buffer.limit());
System.out.println("position:" + buffer.position());
System.out.println("取出一个数据," + buffer.get());
System.out.println("取出数据后,position:" + buffer.position());
buffer.clear();
System.out.println("执行clear后,limit:" + buffer.limit());
System.out.println(",position:" + buffer.position());
System.out.println("执行clear后缓冲区未被清空:" + buffer.get(2));
System.out.println("绝对读取后,position不会改变:" + buffer.position());
7.2 Channel
Channel类似传统流对象,主要区别在于Channel可以将指定文件的部分或者全部直接映射成Buffer,程序不能直接对Channel中的数据进行读写,只能通过Channel来进行数据读写。我们用FileChannel来看看如何使用:
File file = new File("StreamTest.java");
//输入流创建FileChannel
FileChannel inChannel = new FileInputStream(file).getChannel();
//以文件输出流创建FileChannel,控制输出
FileChannel outChannel = new FileOutputStream("a.txt").getChannel();
//将FileChannel映射成ByteBuffer,
MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
Charset charset = Charset.forName("GBK");
//输出数据
outChannel.write(buffer);
buffer.clear();
CharsetDecoder charsetDecoder = charset.newDecoder();
//转换成CharBuffer进行输出
CharBuffer charBuffer = charsetDecoder.decode(buffer);
System.out.println(charBuffer);
7.3 字符集与Charset
我们知道,在计算机底层文件都是二进制文件,都是字节码,那为什么我们还能看到字符,这里面涉及编码和解码两个概念,简单讲,将字符转换成二进制为编码,而将二进制转成字符为解码。
Java默认使用Unicode字符集(字符集是指二进制序列与字符之间的对应关系),但很多操作系统不使用Unicode字符集,这样就会出错,我们要根据实际情况来使用对应的字符集。
Charset包含了创建解码器和编码器的方法,还提供了获取Charset所支持字符集的方法,我们可以通过Charset的forName()获取对象,通过对象获取到CharsetEncoder和CharsetDecoder对象,再通过此对象进行字符序列与字节序列的转换。
SortedMap<String, Charset> stringCharsetSortedMap = Charset.availableCharsets();
for(String name:stringCharsetSortedMap.keySet()){
System.out.println(name);
}
//创建简体中文对应的Charset
Charset cn = Charset.forName("GBK");
//创建对应的编码器及解码器
CharsetEncoder cnEncoder = cn.newEncoder();
CharsetDecoder cnDecoder = cn.newDecoder();
CharBuffer buff = CharBuffer.allocate(8);
buff.put('李');
buff.put('白');
buff.flip();
//将buff的字符转成字节序列
ByteBuffer bbuff = cnEncoder.encode(buff);
for (int i = 0; i <bbuff.capacity() ; i++) {
System.out.print(bbuff.get(i)+ " ");
}
//将bbuff的数据解码成字符
System.out.println("\n"+cnDecoder.decode(bbuff));
7.4 Path、Paths、Files
早期的Java只提供了File类来访问文件系统,功能比较有限且性能不高,后面又提供了Path接口,Path代表一个平台无关路径,并提供了Paths与Files两个工具类,提供了大量的方法来操作文件。
Path path = Paths.get(".");
System.out.println("path包含的文件数量:" + path.getNameCount());
System.out.println("path的根路径:" + path.getRoot());
Path path1 = path.toAbsolutePath();
System.out.println("path的绝对路径:" + path1);
//多个String构建路径
Path path2 = Paths.get("G:", "test", "codes");
System.out.println("path2的路径:" + path2); System.out.println("StreamTest.java是否为隐藏文件:" + Files.isHidden(Paths.get("StreamTest.java")));
//一次性读取所有行
List<String> allLines = Files.readAllLines(Paths.get("StreamTest.java"), Charset.forName("gbk"));
System.out.println(allLines);
//读取大小
System.out.println("StreamTest.java文件大小:" + Files.size(Paths.get("StreamTest.java")));
List<String> poem = new ArrayList<>();
poem.add("问君能有几多愁");
poem.add("恰似一江春水向东流");
//一次性写入数据
Files.write(Paths.get("poem.txt"), poem, Charset.forName("gbk"));
可以看到Paths与Files非常的强大,提供了很多方法供我们使用,在之前这些方法我们自己写的话比较麻烦,更多的方法可以自己去看API。
7.5 文件属性
java.nio.file.attribute包下提供了大量的属性工具类,提供了很方便的方法去获取文件的属性:
BasicFileAttributeView baseView = Files.getFileAttributeView(Paths.get("poem.txt"), BasicFileAttributeView.class);
BasicFileAttributes basicFileAttributes = baseView.readAttributes();
System.out.println("创建时间:" + basicFileAttributes.creationTime().toMillis());
System.out.println("最后更新时间:" + basicFileAttributes.lastModifiedTime().toMillis());
Java核心技术梳理-IO的更多相关文章
- Java核心技术梳理-集合
一.前言 在日常开发中,我们经常会碰到需要在运行时才知道对象个数的情况,这种情况不能使用数组,因为数组是固定数量的,这个时候我们就会使用集合,因为集合可以存储数量不确定的对象. 集合类是特别有用的工具 ...
- Java核心技术梳理-基础类库
一.引言 Oracle为Java提供了丰富的基础类库,Java 8 提供了4000多个基础类库,熟练掌握这些基础类库可以提高我们的开发效率,当然,记住所有的API是不可能也没必要的,我们可以通过API ...
- Java核心技术梳理-泛型
一.引言 在学习集合的时候我们会发现一个问题,将一个对象丢到集合中后,集合并不记住对象的类型,统统都当做Object处理,这样我们取出来再使用时就得强制转换类型,导致代码臃肿,而且加入集合时都是以Ob ...
- Java核心技术梳理-异常处理
一.引言 异常总是不可避免的,就算我们自身的代码足够优秀,但却不能保证用户都按照我们想法进行输入,就算用户按照我们的想法进行输入,我们也不能保证操作系统稳定,另外还有网络环境等,不可控因素太多,异常也 ...
- Java核心技术梳理-类加载机制与反射
一.引言 反射机制是一个非常好用的机制,C#和Java中都有反射,反射机制简单来说就是在程序运行状态时,对于任意一个类,能够知道这个类的所有属性和方法,对于任意一个对象,能够调用它的任意属性和方法,其 ...
- 面试必备!Java核心技术100+面试题
一线互联网公司工作了几年,我作为求职者参加了不少面试,也作为面试官面试了很多同学,整理这份面试指南,一方面是帮助大家更好的准备面试,有的放矢,另一方面也是对自己知识框架做一个体系化的梳理. 这篇文章梳 ...
- Java核心技术点之泛型
1. Why ——引入泛型机制的原因 假如我们想要实现一个String数组,并且要求它可以动态改变大小,这时我们都会想到用ArrayList来聚合String对象.然而,过了一阵,我们想要实现一个大小 ...
- Java核心技术点之集合框架
1. 概述 Java集合框架由Java类库的一系列接口.抽象类以及具体实现类组成.我们这里所说的集合就是把一组对象组织到一起,然后再根据不同的需求操纵这些数据.集合类型就是容纳这些对象的一个容 ...
- java核心技术之流与文件
InputStream和OutputStream构成了输入/输出类层次结构的基础.用于按字节进行读写.而与之处在同一等级的Reader/Writer同样作为抽象类定义了用于对字符进行读取的类层次结构, ...
随机推荐
- linux 运维基本操作
本记录来自腾讯云实验 https://cloud.tencent.com/developer/labs/lab/10000 目录操作 任务时间:5min ~ 10min 创建目录 使用 mkdir ...
- 帝国cms伪静态设置方法
众所周知,动态页面不利于收录和排名.伪静态可以完美的解决这问题,配合百度云加速CDN,可以让动态页面有静态页面一样快的访问速度. 今天开拓族给大家带来帝国CMS伪静态的详细设置方法. 1.栏目设置为动 ...
- 【技术博客】 利用Postman和Jmeter进行接口性能测试
利用Postman和Jmeter进行接口性能测试 作者:ZBW 版本:v1.1 在Phylab的开发过程中,对于生成报告接口的性能考量十分重要.原有的Latex接口虽然生成的报告美观,但编译Latex ...
- 【转】linux sed命令
转自:linux sed命令就是这么简单 参考:Linux三大剑客之sed:https://blog.csdn.net/solaraceboy/article/details/79272344 阅读目 ...
- Bert 时代的创新(应用篇):Bert 在 NLP 各领域的
Bert 时代的创新(应用篇):Bert 在 NLP 各领域的
- mstar gdb调试
当进程崩溃出现coredump提示时,可以利用gdb来定位出错函数. 首先,把core_dump.XXX.gz文件从设备上拷贝出来,放到编译环境下,另外,还要把代码目录下的symbols文件夹也拷贝到 ...
- PHP系列 | [转] PHP中被忽略的性能优化利器:生成器
官方:https://www.php.net/manual/zh/language.generators.overview.php 原文:https://segmentfault.com/a/1190 ...
- WireMock和Spring MVC模拟器
WireMock和Spring MVC模拟器 Spring Cloud Contract提供了一个方便的类,可以将JSON WireMock存根加载到Spring MockRestServiceSer ...
- 各种字符串Hash函数(转)
/// @brief BKDR Hash Function /// @detail 本 算法由于在Brian Kernighan与Dennis Ritchie的<The C Programmin ...
- Jav获取文件的MD5码,比较两个文件内容是否相同
Jav获取文件的MD5码,比较两个文件内容是否相同 代码: System.out.println(DigestUtils.md5Hex(new FileInputStream(new File(&qu ...