Java代码审计之-IO小记
2022-12-19 08:59:46 Author: 亿人安全(查看原文) 阅读量:20 收藏

I/O(input/output)流,即输入输出流。定义在java.io包中。

分类:

1、字节流和字符流----数据单位不同

2、输入流和输出流----传输方向不同

3、节点流和处理流----功能不同

节点流:也叫低级流,从一个特定的IO设备(如 磁盘)续写数据的流,只能直接连接数据源进行读写。

处理流:高级流,对一个已经存在的节点流进行封装成流,通过封装后的流来实现流的读写能力,不会直接连接到实际的数据。

java.io:

在java.io中有四个流的顶级类

字节流:

1、InputStream

2、OutputStream

字符流:

3、Reader

4、Writer

二、字节流

在计算机中所有的文件都是以二进制(字节)的形式存在的。不管是InputStream还是OutputStream都提供了一个close()方法,该方法可以关闭流并释放与当前IO流相关的资源。

InputStream和OutputStream都是抽象类不能实例化。java针对不通过功能提供了不同的子类。


1、FileInputStream、FileOutputStream

import java.io.*;
public class StreamTest {
public static void main(String[] args) throws Exception {
/*=============================读取文件内容====================================*/
//定义流
FileInputStream file_in = new FileInputStream("D:\\mycode\\Java\\test2\\src\\test.txt");
//定义变量储存读到的字节
int b=0;
//while循环读取字节,当读到-1意味着已经读到最后,结束循环
// read() 每次读一个字节
// read(byte[] bs) 可以读取多个字节,并把读到的字节存入字节数组bs
// read(byte[] bs,int offset,int len) 可以跳过offset个字节读取len个字节,并把读到的字节存入字节数组bs
// readAllBytes() 一次性读取所有字节,返回一个字节数组
while ((b=file_in.read())!=-1){
System.out.println("==========");
System.out.println(b); //直接输出的时ASCII码
System.out.println((char) b); //转字符输出
System.out.println("==========");
}
file_in.close();
/*=============================写出文件====================================*/
FileOutputStream file_out = new FileOutputStream("D:\\mycode\\Java\\test2\\src\\test2.txt");
String str1 = "hello world";
//write支持传入字节或者字节数组,写到文件之后还是hello world。
//write() 和read()类似,都可以传入字节数组操作多个字节。
file_out.write(str1.getBytes());
file_out.close();
/*=============================文件复制====================================*/
FileInputStream copy_in = new FileInputStream("D:\\mycode\\Java\\test2\\src\\test.txt");
FileOutputStream copy_out = new FileOutputStream("D:\\mycode\\Java\\test2\\src\\testCOPY2.txt");
copy_out.write(copy_in.readAllBytes());
}
}

2、BufferedInputStream、BufferedOutputStream

字节缓冲流

两者内部自带一个8192字节的字节数组。调用read()或者write()时先把读写的数据存入自带的字节数组然后在一次性读写。

//没啥好说的,就这样。
import java.io.*;
public class StreamTest {
public static void main(String[] args) throws Exception {
BufferedInputStream buffer_in = new BufferedInputStream(new FileInputStream("D:\\mycode\\Java\\test2\\src\\test.txt"));
BufferedOutputStream buffer_out = new BufferedOutputStream(new FileOutputStream("D:\\mycode\\Java\\test2\\src\\bufferOUT.txt"));
buffer_out.write(buffer_in.readAllBytes());
buffer_in.close();
buffer_out.close();
}
}

三、字符流


1、Reader

Reader和之前的InputStream使用起来类似,可以使用循环每次一个字节的读入也可以把读入的内容存储到一个数组,二者都还有类似的Buffered缓冲流。

import java.io.*;
public class StreamTest {
public static void main(String[] args) throws Exception {
FileReader fr = new FileReader("D:\\mycode\\Java\\test2\\src\\test.txt");
int len1=0;
while ((len1=fr.read())!=-1){
System.out.println(((Object) len1).getClass().toString());
System.out.println(len1);
}
fr.close();
System.out.println("======================================================================");
FileInputStream fi = new FileInputStream("D:\\mycode\\Java\\test2\\src\\test.txt");
int len2=0;
while ((len2=fi.read())!=-1){
System.out.println(((Object) len2).getClass().toString());
System.out.println(len2);
}
fi.close();
}
}
/*输出
class java.lang.Integer
106
class java.lang.Integer
97
class java.lang.Integer
118
class java.lang.Integer
97
======================================================================
class java.lang.Integer
106
class java.lang.Integer
97
class java.lang.Integer
118
class java.lang.Integer
97
*/

2、Writer

Writer与之前的OutputStream有所不同,可以直接写字符串

import java.io.*;
public class StreamTest {
public static void main(String[] args) throws Exception {
FileWriter fw = new FileWriter("D:\\mycode\\Java\\test2\\src\\Writer.txt");
fw.write("Java是世界上最好的语言.py");
fw.close();
}
}

四、转换流

InputStreamReader、OutputStreamWriter

JDK提供InputStreamReader类和OutputStreamWriter类用于实现将字节流转换成字符流。InputStreamReader是Reader的子类可以将字节输入流转换成字符输入流、OutputStreamWriter是Writer的子类,可以将字节输出流转换成字符输出流。

只能对文本文件的字节流进行转换,如果字节流是字节码内容的文件,例如图片、视频等,在转换成字符流时会导致数据丢失。

五、File类

字节流可以对文件的内容进行读写操作,但是对文件本身的一些常规操作无法提供流实现。例如创建、删除、判断是否存在等等。

File类用于封装一个路径,这个路径即可指向文件也可指向目录。

1、File的常用构造方法

.通过给定的父抽象路径名和子路径名字符串创建一个新的File实例。

File(File parent, String child);

通过将给定路径名字符串转换成抽象路径名来创建一个新 File 实例。

File(String pathname)

根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。

File(String parent, String child)

通过将给定的 file: URI 转换成一个抽象路径名来创建一个新的 File 实例。

File(URI uri)

2、File类常用的普通方法

序号方法描述
1

public String getName(

)返回由此抽象路径名表示的文件或目录的名称。

2

public String getParent()****、返回此抽象路径名的父路径名的路

径名字符串,如果此路径名没有指定父目录,则返回null

3public File getParentFile()返回此抽象路径名的父路径名的抽象路径名,如果此路径名没有指定父目录,则返回null
4public String getPath()将此抽象路径名转换为一个路径名字符串。
5public boolean isAbsolute()测试此抽象路径名是否为绝对路径名。
6public String getAbsolutePath()返回抽象路径名的绝对路径名字符串。
7public boolean canRead()测试应用程序是否可以读取此抽象路径名表示的文件。
8public boolean canWrite()测试应用程序是否可以修改此抽象路径名表示的文件。
9public boolean exists()测试此抽象路径名表示的文件或目录是否存在。
10public boolean isDirectory()测试此抽象路径名表示的文件是否是一个目录。
11public boolean isFile()测试此抽象路径名表示的文件是否是一个标准文件。
12public long lastModified()返回此抽象路径名表示的文件最后一次被修改的时间。
13public long length()返回由此抽象路径名表示的文件的长度。
14

public boolean createNewFile() throws IOException当且仅当不

存在具有此抽象路径名指定的名称的文件时,原子地创建由此抽象路径名指定的

一个新的空文件。

15public boolean delete()删除此抽象路径名表示的文件或目录。
16

public void deleteOnExit()在虚拟机终止时,请求删除此抽象路径

名表示的文件或目录。

17

public String[] list()返回由此抽象路径名所表示的目录中的文件和目录

的名称所组成字符串数组。

18

public String[] list(FilenameFilter filter)返回由包含在目录中的文件和目录

的名称所组成的字符串数组,这一目录是通过满足指定过滤器的抽象路径名来表

示的。

19

public File[] listFiles()返回一个抽象路径名数组,这些路径名表示此抽象路

径名所表示目录中的文件。

20public File[] listFiles(FileFilter filter)返回表示此抽象路径名所表示目录中的文件和目录的抽象路径名数组,这些路径名满足特定过滤器。
21public boolean mkdir()创建此抽象路径名指定的目录。
22public boolean mkdirs()创建此抽象路径名指定的目录,包括创建必需但不存在的父目录。
23public boolean renameTo(File dest)重新命名此抽象路径名表示的文件。
24public boolean setLastModified(long time)设置由此抽象路径名所指定的文件或目录的最后一次修改时间。
25public boolean setReadOnly()标记此抽象路径名指定的文件或目录,以便只可对其进行读操作。
26

public static File createTempFile(String prefix, String suffix, File

directory) throws IOException在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。

27

public static File createTempFile(String prefix, String suffix) throw

s IOException在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生

成其名称。

28public int compareTo(File pathname)按字母顺序比较两个抽象路径名。
29public int compareTo(Object o)按字母顺序比较抽象路径名与给定对象。
30public boolean equals(Object obj)测试此抽象路径名与给定对象是否相等。
31public String toString()返回此抽象路径名的路径名字符串。

3、获取当前路径

import java.io.*;
public class StreamTest {
public static void main(String[] args) throws Exception {
/*1、使用File*/
File f = new File(""); //设定为当前文件夹
System.out.println(f.getAbsolutePath()); //获取绝对路径
/*2、使用System*/
System.out.println(System.getProperty("user.dir"));
}
}

getProperty方法是从System类的一个静态属性props里面获取相应的值,而props属性是通过本地方法initProperties来赋予初值的。也就是说,在JVM启动时通过执行本地方法自动初始化了这个系统属性。好在jdk的文档注释上向我们说明了JVM确保有哪些属性,我们通过下表列出:

属性名说明
java.versionJava版本号
java.version.dateJava版本日期
java.vendorJava供应商指定字符串
java.vendor.urlJava供应商URL
java.vendor.versionJava供应商版本
java.homeJava安装根目录
java.class.versionJava 类文件版本号
java.class.pathJava 类路径
os.name操作系统名
os.arch操作系统架构
os.version操作系统版本
file.separator文件分隔符
path.separator路径分隔符
line.separator换行符
user.name用户账号
user.home用户根目录
user.dir用户当前工作目录

5.1、文件过滤器

如果File对象指向的是一个目录,我们可以使用list()方法来遍历获取目录下的所有文件夹。同时File提供了一个重载的list()方法,接受一个FilenameFilter接口类型参数,FilenameFilter是一个函数式接口,称作文件过滤器。在调用这个重载的list方法时需要实现文件过滤器并在accept()方法中筛选。

import  java.io.*;
import java.util.Arrays;
public class FileTest {
public static void main(String[] args) {
File f = new File("C:\\Users\\Administrator\\Desktop\\JAVA");
if (f.isDirectory()){
String[] filenames=f.list(((dir, name) -> name.endsWith(".md"))); //使用lambda表达式实现list
Arrays.stream(filenames).forEach(ff-> System.out.println(ff));
}
}
}

如果目录下存在子目录则可以使用listFile()来遍历

listFile()返回一个File对象数组。

import  java.io.*;
public class FileTest {
public static void main(String[] args) {
File f = new File("C:\\Users\\Administrator\\Desktop\\JAVA");
File[] list_Files= f.listFiles(); //把listFiles()的返回值存入File类型的数组
for (File file : list_Files){ //遍历数组
if (file.isDirectory()){ //判断数组成员是否还有目录
for (File towFile : (File[]) file.listFiles()){ //if成立就继续遍历二级目录
System.out.println(towFile);
}
}
System.out.println(file);
}
}
}

六、RandomAccessFile

在I/O包中有一个RandomAccessFile类,不属于流类,但是可以随机从文件的任意位置开始执行都读写操作。

RandomAccessFile对象包含一个记录指针来标识当前读写的位置。当程序新建RandomAccessFile对象时指针在文件开始处(标识为0),读写了n个字节,指针会后移n个字节。RandomAccessFile还可以自由移动指针。

1、构造函数

RandomAccessFile(File file, String mode) //创建一个随机访问文件流,以读取或写入由File参数指定的文件。
RandomAccessFile(String name, String mode) //创建一个随机访问文件流,以读取和写入具有指定名称的文件。

2、常用方法

long getFilePointer()  //返回当前指针所在位置
void seek(long pos) //设置文件指针偏移量,从该文件的开头开始测量,下一次读取或写入发生在于文件开头相隔pos个字节的位置。
int skipBytes(int n) //尝试从当前跳过n字节的输入丢弃跳过的字节。

七、对象序列化

序列化:为了将对象保存到磁盘或者允许在网络上传输,将一个内存中的java对象转换成一个于平台无关的I/O流中字节序列的过程。

反序列化:将序列化的二进制流恢复成java对象。

Java中进行序列化必须实现Serializable或者Externalizable两个接口之一。

Externalizable:

性能刚好但是代码更复杂。存储的信息可以自定义。接口中只提供俩空方法,实现接口必须具体实现俩空方法。

Serializable:

代码简单。存储信息由系统自动完成。只需要使用implement实现接口即可,其他由java内部支持。

serialVersionUID:

java的序列化机制通过判断类的serialVersionUID来验证版本一致性。反序列化时JVM会把字节流中传来的UID与本地相应实体类的UID进行比较。如果不一致会出现异常。这个UID可以在需要序列化的类中进行显式的定义:

private final long serialVersionUID = 1L;   //1L是默认值。

如果没有显式的定义UID,JVM会根据类的相关信息(类名、接口名、成员属性、成员方法等等属性)生成一个64为hash字段。

示例:

再序列化和反序列化时要用到ObjectOutputStream

/*文件:SerializableTest.java*/
import java.io.*;
public class SerializableTest {
public static void main(String[] args) throws Exception{
//序列化写入到test1.txt
FileOutputStream fos1 = new FileOutputStream("D:\\mycode\\Java\\test2\\src\\test1.txt");
ObjectOutputStream oos1 = new ObjectOutputStream(fos1);
Person ps1 = new Person("test",8848);
oos1.writeObject(ps1);
oos1.close();
fos1.close();
//从test1.txt读取数据并反序列化为对象
FileInputStream fis1 = new FileInputStream("D:\\mycode\\Java\\test2\\src\\test1.txt");
ObjectInputStream ois1 = new ObjectInputStream(fis1);
Person ps2 = (Person) ois1.readObject();
fis1.close();
ois1.close();
ps2.getInfo();
}
}

/*文件:Person.java*/
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID=1L;
private String name;
private int age;
public Person(String name,int age){
this.name=name;
this.age=age;
}
public void getInfo(){
System.out.println("name: "+name+" | age : "+age);
}
}

八、NIO

从JDK1.4开始提供了NIO(New I/O)。NIO将文件或者文件的一段区域映射到内存中,这样可以像访问内存一样访问文件。

NIO使用通道(Channel)和缓冲区(Buffer)。数据总是从通道读入缓冲区或者从缓冲区写入通道。

NIO有三大核心部分:Buffer、Channel、Selector。

Buffer:是一个数组缓冲区,所有读入或写出到Channel的对象都先放在这里。

Channel:对传统输入输出的模拟,NIO中所有数据都以通道流的形式传输。

Selector:选择器用于监听多个通道事件(连接打开、数据到达...)主要用于多线程。

1、Buffer

从结构上看buffer是一个数组,从类型上看buffer是一个抽象类。buffer包含以下子类:

ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer

Buffer的 !子类 !都提供 put() 和 get() 方法用于向Buffer中写入和提取数据。类似Python的queue队列一样。

Buffer的子类中都没有提供构造方法,想要创建Buffer对象可以通过allocate:

static <类型>Buffer allocate(int capacity) 
//allocate是静态方法,直接通过类名.allcate调用
//capacity--表示容量
/*例子*/
CharBuffer buffer = CharBuffer.allocate(10)

Buffer的三个属性:

1、capacity:容量,值不能为负数,定义后不可改变。

2、limit:界限,Buffer中不可读取的区域的第一个索引,就是0~limit之间的区域是可读取的,不可为负数,不大于容量。

3、position:位置,代表下一个可读写的位置索引,类似文件的指针,新建的Buffer对象position默认为0,每一次读写会使position的值向后移动一步,意味着每一次put或者get,postion都会后移!可以使用flip()反转缓冲区(将limit设置为当前位置,再把position设置到0)

代码示例:

import java.nio.CharBuffer;
public class FileTest {
public static void main(String[] args) {
CharBuffer cbf1=CharBuffer.allocate(10);
for(int i=0;i<=9;i++){
cbf1.put((char) i);
}
//打印缓冲区大小
System.out.println(cbf1.capacity());
//获取limit的值
System.out.println(cbf1.limit());
//获取position
System.out.println(cbf1.position());
//反转缓冲区
cbf1.flip();//再执行flip之前position因为前面的put已经移动到后面,不执行flip直接get会报nextGetIndex。
for (int i=0;i<=9;i++){
System.out.println((int) cbf1.get());
}
}
}

2、Channel

Channel是一个接口对象,类似传统流对象,又和传统流对象有以下不同:

1、Channel可以异步执行I/O操作。

2、Channel双向读写,既可以从Channel读取,也可以写入数据到Channel。

3、Channel可以直接将文件的部分或全部直接映射成Buffer

4、Channel只能和Buffer交互,程序不能直接读写Channel中的数据

java.nio.channel 提供了很多Channel的实现类可供使用:

  1. DatagramChannel------UDP通信

  2. FileChannel------文件读写

  3. Pipe.sinkChannel、Pipe.SourceChannel------线程间通信

  4. ServerSocketChannel、SocketChannel------TCP通信

示例:

import  java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
public class FileTest {
public static void main(String[] args) throws Exception{
RandomAccessFile raf1 = new RandomAccessFile("D:\\mycode\\Java\\test2\\src\\test1.txt","rw");
RandomAccessFile raf2 = new RandomAccessFile("D:\\mycode\\Java\\test2\\src\\test2.txt","rw");
FileChannel fc1 = raf1.getChannel();
FileChannel fc2 = raf2.getChannel();
long transferto = fc1.transferTo(0,fc1.size(),fc2);
System.out.println(transferto);
}
}

3、NIO.2

JDK7引入新的I/OAPI是对原有的I/OAPI的改进称为NIO.2。

改进内容:

  • 提供全面的文件输入输出以及文件系统的访问与支持。

  • 新增 java.nio.file 及其子包。

  • 基于异步Channel的输入输出。

3.1-Path接口

Path接口是一个共用在系统中定位文件的对象,通常表示一个依赖于系统的文件路径。

3.2-Paths工具类、Files工具类

Paths:

提供两个返回Path的静态方法,可以用此创建Path对象。

Files:

提供大量静态方法用来操作文件。

原文链接:https://www.freebuf.com/articles/web/352413.html


文章来源: http://mp.weixin.qq.com/s?__biz=Mzk0MTIzNTgzMQ==&mid=2247502365&idx=1&sn=f4ef7d2d63389b5aef0ad95bb6b6618c&chksm=c2d70105f5a08813a0fa6293002063319fd64f96c5e85159970a01534edc18c651218885072b#rd
如有侵权请联系:admin#unsafe.sh