前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java代码审计之-IO小记

Java代码审计之-IO小记

作者头像
亿人安全
发布2022-12-23 15:21:35
2610
发布2022-12-23 15:21:35
举报
文章被收录于专栏:红蓝对抗红蓝对抗

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
代码语言:javascript
复制
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()时先把读写的数据存入自带的字节数组然后在一次性读写。

代码语言:javascript
复制
//没啥好说的,就这样。
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缓冲流。

代码语言:javascript
复制
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有所不同,可以直接写字符串

代码语言:javascript
复制
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实例。

代码语言:javascript
复制
File(File parent, String child);

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

代码语言:javascript
复制
File(String pathname)

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

代码语言:javascript
复制
File(String parent, String child)

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

代码语言:javascript
复制
File(URI uri)

2、File类常用的普通方法

代码语言:javascript
复制
public String getName(
)返回由此抽象路径名表示的文件或目录的名称。

3、获取当前路径

代码语言:javascript
复制
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.version

Java版本号

java.version.date

Java版本日期

java.vendor

Java供应商指定字符串

java.vendor.url

Java供应商URL

java.vendor.version

Java供应商版本

java.home

Java安装根目录

java.class.version

Java 类文件版本号

java.class.path

Java 类路径

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()方法中筛选。

代码语言:javascript
复制
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对象数组。

代码语言:javascript
复制
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、构造函数
代码语言:javascript
复制
RandomAccessFile(File file, String mode) //创建一个随机访问文件流,以读取或写入由File参数指定的文件。
RandomAccessFile(String name, String mode) //创建一个随机访问文件流,以读取和写入具有指定名称的文件。
2、常用方法
代码语言:javascript
复制
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可以在需要序列化的类中进行显式的定义:

代码语言:javascript
复制
private final long serialVersionUID = 1L;   //1L是默认值。

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

示例:

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

代码语言:javascript
复制
/*文件: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:

代码语言:javascript
复制
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)

代码示例:

代码语言:javascript
复制
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通信

示例:

代码语言:javascript
复制
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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-12-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 亿人安全 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二、字节流
    • 1、FileInputStream、FileOutputStream
      • 2、BufferedInputStream、BufferedOutputStream
      • 三、字符流
        • 1、Reader
          • 2、Writer
          • 四、转换流
          • 五、File类
            • 5.1、文件过滤器
            • 六、RandomAccessFile
              • 1、构造函数
                • 2、常用方法
                • 七、对象序列化
                • 八、NIO
                  • 1、Buffer
                    • 2、Channel
                      • 3、NIO.2
                      相关产品与服务
                      文件存储
                      文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档