Java-IO流详解

1.File类

File类可以使用文件路径字符串来创建File实例,该文件路径可以是绝对路径,也可以是相对路径,相对路径的解释是依据默认路径(user.dir),即用户工作路径

  • 一个File对象就代表一个文件或一个目录

  • 路径分隔符

    • windows中使用的是\\

    • Linux、Unix以及URL中使用的是/

    • java也支持在windows中使用/如果为了避免出现跨平台问题,可以使用File类的一个常量public static final String separator

File file=new File("D:"+File.separator+"temp"+File.separator+"java.txt")//"D:\\temp\\java.txt"

构造器

File(String filePath)//文件路径

File(String parentPath,String childPath)//父路径拼接上子路径,表示完整指定的路径

File(File parentPath,String childPath)//父路径拼接子路径,表示完整指定路径–方便的一点就是可以将File对象直接作为父路径,减少开发量

File file1 = new File("E:/文档/temp/java.txt");
File file2 = new File("E:/文档/temp/","java.txt");
File file2 = new File(new File("E:/文档/temp/"),"java.txt");

常用方法

//访问文件名相关方法
* String getName():返回当前File对象所表示的文件名或路径名(最末级节点名称)
* String getPath():返回此对象所对应的路径名
* File getAbsoluteFile():返回File对象的绝对路径
* String getAbsoluteFile():返回File对象所对应的绝对路径名
* String getParent():获取File对象所对应的目录的父目录
* boolean renameTo(File newName):重命名此File对象所对应的目录或文件,成功返回true,失败返回false

测试代码:

@Test
public void findFile() {
    //并不会去访问文件的操作getName()和getPath()
    File file=new File("E:\\Documents");//这是一个目录
    String name = file.getName();//Documents
    String path = file.getPath();//E:\Documents
    System.out.println(path);
    System.out.println(name);//如果是目录则返回当前最后一级目录名
    File file1=new File("E:\\Documents\\IO流.xmind");//这是一个文件
    String name1 = file1.getName();//IO流.xmind
    String path1 = file1.getPath();//E:\Documents\IO流.xmind
    System.out.println(path1);
    System.out.println(name1);//如果是目录则返回当前文件名

    //getAbsoluteFile()会去访问该目录--如果访问不到则会拼接到默认路径后作为目录名存在
    File file2=new File("222:\\Documents");//这是一个目录
    File absoluteFile = file2.getAbsoluteFile();//D:\文档\Java_WorkSpace\JAVACore\222:\Documents

    //getAbsolutePath()--这个方法返回String绝对路径,不能进行File操作--可以使用new File(absolutePath,length)
    String absolutePath = file2.getAbsolutePath();
    System.out.println(absolutePath);//D:\文档\Java_WorkSpace\JAVACore\222:\Documents
    System.out.println(absoluteFile);//D:\文档\Java_WorkSpace\JAVACore\222:\Documents

    //String getParent()
    String parent = file2.getParent();
    System.out.println(parent);//222:

    //重命名
    File file4=new File("E:\\tempBySRMYY\\");//这是一个目录
    File file5=new File("E:\\tempBy\\");
    boolean b = file4.renameTo(file5);
    System.out.println(b);//true修改成功
}
//文件检测相关方法
boolean exists():判断File对象所对应的文件或目录是否存在
boolean canWrite():判断File对象所对应的文件和目录是否可写
boolean isFile():判断File对象所对应的文件和目录是否可读
boolean isDirectory():判断File对象所对应的是否是目录,而不是文件
boolean isAbsolute():判断File对象所应的文件或目录是否是绝对路径。该方法消除了不同平台的差异,可以直接判断File对象是否为绝对路径,在Unix/Linux/BSD等系统上,如果路径名开头是一条斜线(/),则表明该File对象对应一个绝对路径,在Windows系统上,如果路径开头是盘符,则说明是绝对路径

测试代码:

@Test
public void detectFile(){
    File directory = new File("E:\\文档\\temp");
    File file = new File("E:\\文档\\temp\\java.txt");
    //检测文件是否存在
    System.out.println(file.exists());//true
    System.out.println(directory.exists());//true

    //检测文件是否可写
    System.out.println(file.canWrite());//true
    System.out.println(directory.canWrite());//true

    //检测文件是否可读
    System.out.println(file.canRead());//true
    System.out.println(directory.canRead());//true

    //检测是否是文件,是文件返回true,是其他的返回false
    System.out.println(file.isFile());//true
    System.out.println(directory.isFile());//false

    //检测是否是目录
    System.out.println(file.isDirectory());//false
    System.out.println(directory.isDirectory());//true

    //检测是否是绝对路径--该方法不用考虑系统平台
    System.out.println(file.isAbsolute());//true
}
//获取常规文件信息
long lastModified():返回文件的最后修改时间
long length():返回文件内容的长度

测试代码:

@Test
public void getFileInfo(){
    File directory = new File("E:\\文档\\temp");
    File file = new File("E:/文档/temp/java.txt");
    long l = file.lastModified();
    Date date = new Date(l);
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String format = simpleDateFormat.format(date);
    System.out.println(format);//2021-08-20 09:55:38
    //文件内容长度--bit
    long length = file.length();
    System.out.println(length);//98  3字节一个汉字
}
//文件操作相关方法
boolean createNewFile():当此File对象所对应的文件不存在时,该方法将新建一个该File对象所指定的新文件,如果创建成功则返回true,否则返回false
boolean delete():删除File对象所对应的文件或路径。
static File createTempFile(String prefix,String suffix):在默认的临时文件目录中创建一个临时的空文件,使用给定前缀、系统生成的随机数和定后缀作为文件名,这是一个静态方法,可以直接通过File类来调用。prefix参数必须至少是3字节长,suffix为空则表示默认为.tmp
static File createTempFile(String prefix,String suffix,File directory):File directory用于指定创建文件的目录
void deleteOnExit():注册一个删除钩子,指定当java虚拟机退出时,删除File对象所对应的文件和目录

测试代码:

@Test
public void fileOp() throws IOException {
    File directory = new File("E:\\文档\\temp");
    File file = new File("E:/文档/temp/java.md");
    //创建一个新文件--只能创建文件,不能创建目录
    boolean newFile = file.createNewFile();
    System.out.println(newFile);//true--返回true,表示创建成功,如果该文件存在,则返回true

    //删除文件或目录--true删除成功,false删除失败
    boolean delete = file.delete();
    System.out.println(delete);//true

    //创建一个临时文件
    //prefix:文件名--这个文件名会拼接一个系统生成的随机数作为最终生成的文件名
    //suffix:文件后缀--如果为null,默认后缀名为.tmp
    //directory:指定目录,在该目录下创建文件,如果不指定则在默认路径下
    File md = File.createTempFile("xzt", ".md",new File("E:\\文档\\temp"));

    //注册删除的钩子
    File directory1 = new File("E:\\文档\\temp\\test.md");
    directory1.deleteOnExit();
    System.exit(0);

}
//目录操作相关方法
boolean mkdir():试图创建一个File对象所对应的目录,如果创建成功,返回true,调用该方法时,File对象时File对象必须对应一个路径,而不是一个文件
String[] list():列出File对象的所有子文件名和目录名,返回一个String数组
File[] listFiles():列出File对象的所有子文件和路径,返回File数组
static File[] listRoot():列出系统所有的根路径,这是一个静态发给发,可以直接通过File类来调用

测试代码:

    @Test
    public void directoryOp(){
        File directory = new File("E:/文档/temp/");
        File file = new File("E:/文档/temp/java.txt");

        //mkdir创建File对象对应的目录--如果存在创建失败
        //如果指定的路径是一个文件名,创建时也会当作目录来创建
        //如果指定的路径是一个文件名,且文件名存在,目录也无法创建
        System.out.println(directory.mkdir());//false
        System.out.println(file.mkdir());//true

        //获取当前目录下的所有文件-可以通过FilenameFilter参数添加文件过滤
        String[] list = directory.list();
        for (String s : list) {
            System.out.println(s);
        }
        //如果指定的路径是一个文件则会报错java.lang.NullPointerException
//        String[] list1 = file.list();
//        for (String s : list1) {
//            System.out.println(s);
//        }
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        //获取当前目录下的所有文件和目录信息--绝对路径
        File[] files = directory.listFiles();
        for (File file1 : files) {
            System.out.println(file1);
        }
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        //根目录数
        File[] roots = File.listRoots();
        for (File file1 : roots) {
            System.out.println(file1);
        }
    }

1.2File文件过滤器

	@Test
    public void filterFile(){
        File directory = new File("E:/文档/temp/");
//        File file = new File("E:/文档/temp/java.txt");
        //list方法接收的是一个FilenameFilter接口参数,这个接口和FileFilter抽象累的功能非常相似,可以把FileFilter当成FilenameFilter的实现类--当成,不是真的实现类
        //这个lambda实现的就是FilenameFilter接口中的accept方法,该方法将依次对指定的File的所有子目录或文件进行迭代,如果返回true则list就会列出该目录下的所有目录或文件
        String[] list = directory.list((dir, name) -> name.endsWith(".md") || new File(name).isDirectory());
        for (String s : list) {
            System.out.println(s);
        }
    }

2.IO流

2.1IO流基本概念

  • I/O是Intput/Output的缩写,用于处理设备之间的数据传输–如:读写文件,网络传输等
  • Java程序中对于数据的传输操作采用==流(Stream)==的方式进行,因此叫做IO流
  • Java.io包下提供了各种流的类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出

2.2流的分类

  • 按操作数据单位不同分为:字符流(8bit)和字节流(16bit);字符流常用于处理文本文件、字节流常用于处理图片视频等。
  • 按数据流的流向不同分为:输入流和输出流
  • 按流的角色不同分为:节点流和处理流
分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类==(不允许实例化)== InputStream OutputStream Reader Writer
节点流 访问文件 FileInputStream FileOutputStream FileReader FileWriter
节点流 访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
节点流 访问管道 PipedInputStream PipedOutputStream PipedReader PipedWirter
节点流 访问字符串 StringReader StringWriter
处理流 缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
处理流 转换流 InputStreamReader OutputStreamWriter
处理流 对象流 ObjectInputStream ObjectOutputStream
处理流 FilterIntputStream FilterOutputStream FilterReader FilterWriter
处理流 打印流 PrintStream PrintWriter
处理流 推回输入流 PushbackInputStream PushbackReader
处理流 特殊流 DataInputStream DataOutputStream

节点流:可以从一个特定的I/O设备读写数据的流,称为节点流(也被称为低级流)。从实际的数据源连直接连接到直接的输入/输出节点

处理流:是对已经存在的流进行连接或封装,通过封装后的流来实现数据读写功能,也称为高级流

InputStream三个读方法

int read();

int read(byte[] b)

int read(byte[] b,int off,int len);

Reader三个读方法

int read();

int read(char[] cbuf);

int read(char[] cbuf,int off,int len);

InputStream和Reader都有的方法

void mark(int readAheadLimit):在记录指针当前位置记录一个标记mark

boolean markSupported():判断此输入流是否支持mark()操作,是否支持记录标记

void reset():将此流的记录指针重定位到上一次记录标记mark的位置

long skip(long n):记录指针向前移动n个字节/字符

OutputStream的三个写方法:

void write(int c);

void write(byte[] b);

void write(byte[] b,int off,int len);

Writer的五个写方法:

void write(int c);
void write(char[] cbuf);
void write(char[] cbuf,int off,int len);
void write(String str);
void write(String,int off,int len);

2.3字节流与字符流

字符流内部实际上是使用了转换流将字节流的实例对象进行转换后的结果

  • 字节流:主要用于对图片、视频等非文本文件进行操作
  • 字符流:主要用于对.txt、.java、.cpp等文本文件进行操作
字节流与字符流的使用:
1. 创建一个文件对象--用于指定读取或写入的文件
2. 创建流对象--字节流/字符流--这里读取或写入的是文件,就需要使用访问文件的字节流/字符流
3. 输入流调用read()、输出流调用write()
4. 关闭流连接

字符流实现将内容读取到本地文件

//1.实例化File对象--指定需要读取的文件
File file = new File("E:\\文档\\temp\\java.txt");
//方式一:read()
//2.将指定的文件传给FileReader对象
//FileReader fr=null;
//try {
	//fr=new FileReader(file);
	//3.调用read()方法读取文件--如果读取到文件末尾返回-1
	//int data = fr.read();
	//while (data != -1) {
	//不要自己使用换行符,读取文件时会读取换行符的
	//	System.out.print((char)data);
	//  data=fr.read();
	//}
//} catch (IOException e) {
		//try {
			//throw new IOException("文件读取异常");
		//} catch (IOException ioException) {
			//ioException.printStackTrace();
		//}
//} finally {
		//4.一定要关闭流--垃圾回收机制不能清除物理连接--流,数据库连接和网络连接
		//if(fr != null) {
			//try {
				//fr.close();
			//} catch (IOException e) {
				//e.printStackTrace();
			//}
		//}
//}
//方式二:read(char[] c)
FileReader fileReader =null;
try {
    fileReader = new FileReader(file);
    char[] cbuf=new char[8];
    int len = 0;
    while((len=fileReader.read(cbuf))!=-1){//read(char[] cbuf)返回值是读取字符的个数
        //方式一的正确写法和错误写法:
        //错误方式:使用char数组时,是将读取的字符依次覆盖到char数组中,未覆盖部分会保留上一次读取的内容,因此执行结果为:
        //如果你是一个孩子,一定要学会坚强
        //如果你是一个大人,一定要学会承担,一定要学会
        //            for (int i = 0; i < cbuf.length; i++) {
        //                System.out.print(cbuf[i]);
        //            }
        //如果你是一个孩子,一定要学会坚强
        //如果你是一个大人,一定要学会承担
        //            for (int i = 0; i < len; i++) {
        //                System.out.print(cbuf[i]);
        //            }

        //方式二的正确写法和错误写法
        //错误方式:与上同理,使用覆盖的方式,未覆盖部分保留了上一次读取的字符
        //如果你是一个孩子,一定要学会坚强
        //如果你是一个大人,一定要学会承担,一定要学会
        //            String str=new String(cbuf);
        //            System.out.print(str);
        //正确方式:采用String构造器,设置每次转换为String的偏移量
        String str = new String(cbuf,0,len);
        System.out.print(str);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (fileReader != null) {
        try {
            fileReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

字符流实现将内容写入到本地文件

注意:在实现内容写入的时候,如果不关闭流,不会默认调用flush()方法,数据也就无法输出到文件中–所以要么关闭流,要么flush(),但是这种情况,关闭流一定要执行,否则容易造成资源浪费,内存泄漏。

/**
* @Description:字符流写入操作
*  注意事项:
*      1.写入流一定要关闭后,文件写入才算生效,否则不生效
*      2.被写入的文件可以不存在,如果不存在则自动创建一个文件
*      3.如果调用FileWriter(File file)构造器,则默认append为false,即使用覆盖文件的方式进行写入操作
*        如果调用FileWriter(File file,boolean append)构造器,true代表向文件末尾追加内容,false代表使用文件覆盖的方式进行写入操作
*/
//1.创建一个File对象--用于指定需要写入的文件
File file=new File("E:/文档/temp/java1.md");
//2.将此文件对象File放入FileWriter中
FileWriter fw=new FileWriter(file,true);
//调用write()方法写入数据
fw.write("## IO流");
//4.关闭写入流
fw.close();

字符流实现文件复制

File readFile=new File("E:/文档/temp/java.txt");
File writeFile=new File("E:/文档/temp/java1.md");
FileReader fr= null;
FileWriter fw= null;
try {
    fr = new FileReader(readFile);
    fw = new FileWriter(writeFile,true);
    char[] cbuf=new char[5];
    int len = 0;
    //方式一:推荐这种方式,减少对文件的频繁操作
    while((len=fr.read(cbuf))!=-1){
        String str=new String(cbuf,0,len);
        System.out.print(str);
        fw.write(cbuf,0,len);
    }
    //方式二:这种方式会频繁对文件进行操作
    //            while((len=fr.read(cbuf))!=-1){
    //                for (int i = 0; i < len; i++) {
    //                    fw.write(cbuf[i]);
    fw.flush();
    //                }
    //            }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        fw.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    try {
        fr.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

字节流实现文件的读取

FileInputStream fis= null;
try {
    File srcPath = new File("E:/Documents/集合框架.png");
    fis = new FileInputStream(srcPath);
    byte[] bbufe=new byte[1024];
    int len = 0;
    while((len=fis.read(bbufe))!=-1){
        for(int i=0;i<len;i++){
            System.out.println((char)bbufe[i]);
        }
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        fis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

}

字节流是实现文件的写入

FileOutputStream fos= null;
try {
    File destPath = new File("E:/Documents/java.txt");
    fos = new FileOutputStream(destPath);
    byte[] b=new byte[]{'a','b','c'};
    fos.write(b);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        fos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

字节流实现文件的复制

FileInputStream fis= null;
FileOutputStream fos= null;
try {
    File srcPath = new File("E:/Documents/集合框架.png");
    File destPath = new File("E:/Documents/集合框架2.png");
    fis = new FileInputStream(srcPath);
    fos = new FileOutputStream(destPath);
    byte[] bbufe=new byte[1024];
    int len = 0;
    while((len=fis.read(bbufe))!=-1){
        fos.write(bbufe,0,len);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        fis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    try {
        fos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2.4.缓冲流

缓冲流:是为了提高数据的读写速度而产生的,JavaAPI提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部的缓冲区数组,使用8192个字节的缓冲区

private static int DEFAULT_BUFFER_SIZE = 8192;

缓冲流需要套在相应的节点流上

  • BufferedInputStream和BufferedOutputStream
  • BufferedReader和BufferedWriter

当读取数据时,会一次性读取8192个(8KB),存在缓冲区中,直到缓冲区存满了,才会重新从文件中读取下一个块,可以使用flush()方法将未存满的缓冲区内容强制写入输出流

在关闭流的过程中,只需要关闭缓冲流的流连接,节点流的连接也会自动关闭,不需要再次关闭–当然关闭也不错

Java-IO流详解_第1张图片
代码测试:

long start = System.currentTimeMillis();
BufferedInputStream bis=null;
BufferedOutputStream bos=null;
try {
    //创建文件对象
    File srcPath = new File("D:/BaiduNetdiskDownload/1.mp4");
    File destPath = new File("D:/BaiduNetdiskDownload/2.mp4");
    //创建读取和写入的流对象
    FileInputStream fis = new FileInputStream(srcPath);
    FileOutputStream fos = new FileOutputStream(destPath);
    //创建处理流对象
    bis=new BufferedInputStream(fis);
    bos=new BufferedOutputStream(fos);
    byte[] bbufe=new byte[1024];
    int len = 0;
    while((len=bis.read(bbufe))!=-1){
        bos.write(bbufe,0,len);
    }
    System.out.println("文件传输成功");
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        bis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    try {
        bos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
long end = System.currentTimeMillis();
System.out.println("文件传输速度:"+(end - start));//1849--约快了3~4倍

2.5.转换流

转换流用于在字节流和字符流之间相互转换

InputStreamReader:将InputStream转换为Reader

OutputSreamWriter:将OutputSream转换为Writer

字节流中的数据都是字符时,转成字符流操作更高效

使用转换流的目的是为了处理文件乱码,实现编码和解码的功能

常见的编码表
ASCII:美国标准信息交换码。用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表。用一个字节的8位表示。
GB2312:中国的中文编码表。最多两个字节编码所有字符
GBK(ANSI):中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
BIG5:繁体中文字符集

字节流输入流转换为字符流输入流

//将InputStream转换为Reader
File file = new File("E:\\Documents\\java.txt");
FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//默认使用IDEA默认编码
char[] cbuf=new char[20];//FileInputStream原本时需要用byte数组承接的,但是转换流使用后只能用char承接,因为read方法没有byte[]参数
int len=0;
while((len=isr.read(cbuf)) != -1){
    for (int i = 0; i < len; i++) {
        System.out.print(cbuf[i]);
    }
}
isr.close();

字节流输出流转换为字符流输出流

File file = new File("E:\\Documents\\java.txt");
FileOutputStream fos =new FileOutputStream(file);
OutputStreamWriter osw=new OutputStreamWriter(fos,"UTF-8");
osw.write("中国加油!");
osw.close();

转换流实现复制文件

public void conversionFile() throws IOException {
    File src = new File("E:\\Documents\\java.txt");
    File dest = new File("E:\\Documents\\java1.txt");
    FileInputStream fis=new FileInputStream(src);
    FileOutputStream fos=new FileOutputStream(dest);
    InputStreamReader isr=new InputStreamReader(fis,"UTF-8");
    OutputStreamWriter osw=new OutputStreamWriter(fos,"GBK");
    char[] cbuf=new char[20];
    int len=0;
    while((len=isr.read(cbuf))!=-1){
        osw.write(cbuf,0,len);
        osw.flush();
    }
    isr.close();
    osw.close();
}

2.6其他流

标准输入输出流

System.in\System.out\System.err

InputStreamReader inputStreamReader = new InputStreamReader(System.in);
BufferedReader br=new BufferedReader(inputStreamReader);
while(true){
    System.out.println("请输入:");
    String data=br.readLine();
    if ("e".equalsIgnoreCase(data)||"exit".equalsIgnoreCase(data)) {
        System.out.println("程序结束");
        break;
    }
    String s = data.toUpperCase();
    System.out.println(s);
}
br.close();
}

数据流

DataOutputStream dos=new DataOutputStream(new FileOutputStream("data.txt"));
dos.writeUTF("李白");
dos.flush();
dos.writeInt(15);
dos.flush();
dos.writeBoolean(true);
dos.flush();
dos.close();

DataInputStream dis=new DataInputStream(new FileInputStream("data.txt"));
//java.io.EOFException
//        boolean b = dis.readBoolean();
//        int i = dis.readInt();
//        String s = dis.readUTF();
//李白3841false
//        String s = dis.readUTF();
//        boolean b = dis.readBoolean();
//        int i = dis.readInt();
String s = dis.readUTF();
int i = dis.readInt();
boolean b = dis.readBoolean();
System.out.println(s+i+b);
dis.close();

对象流

ObjectInputStream和ObjectOutputStream

作用:

​ 将Java中的对象写入到数据源中或把对象从数据源中还原出来

序列化:

​ 使用ObjectOutputStream类保存基本类型数据或对象的机制叫序列化

反序列化:

​ 使用ObjectInputStream类读取基本类型数据或对象的机制叫反序列化

注意:ObjectOutputStream和ObjectInputStream不能序列化static和stransient修饰的成员变量

对象序列化:对象的序列化机制允许把内存中的java对象转换成平台无关的二进制流,支持保存到磁盘或传输到其他的网络节点,再通过程序获取二进制流就可以恢复原来的java对象

为了让类实现可序列化,该类必须实现以下两个接口其中的一个

  • Serializable

    • 该接口是一个标记接口,无需实现任何方法,只是标注该类的实例为可序列化的
  • Externalizable

    • 该接口提供了两个方法,由程序员确定存储和恢复的对象数据

    • @Override
      public void writeExternal(ObjectOutput out) throws IOException {
          out.writeObject(new StringBuffer(name).reverse());
          out.writeInt(age);
          out.writeObject(p);
      }
      
      @Override
      public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
          this.name=((StringBuffer)in.readObject()).reverse().toString();
          this.age=in.readInt();
          this.p=(Person) in.readObject();
      }
      
@Test
/**
 * @Description: 对象流--主要是对对象的序列化处理--将对象写入磁盘文件,然后读取文件中的对象信息
 * @Param: 
 * @return: void
 */ 
public void objectStreamTest(){
    ObjectOutputStream oos = null;
    try {
        //1.创建文件对象--需要将对象写入哪个文件
        File file = new File("E:\\Documents\\java.txt");
        //2.创建节点流--输出流
        FileOutputStream fos=new FileOutputStream(file);
        //3.创建处理流-对象输出流
        oos = new ObjectOutputStream(fos);
        //4.创建需要写入的类对象
        Person p=new Person("李白",25);
        //5.写入到文件
        oos.writeObject(p);//当文件未序列化时报错:com.carl.javaadvanced.ioclass.Person
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            //关闭流
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //获取文件中的对象
    ObjectInputStream ois= null;
    try {
        //1.创建文件对象--需要从哪个文件读取对象
        File dest=new File("E:\\Documents\\java.txt");
        //2.创建节点流--输入流
        FileInputStream fis=new FileInputStream(dest);
        //3.创建处理流--对象输入流
        ois = new ObjectInputStream(fis);
        //4.将获取的对象信息使用类对象承接--这里会失去对象记忆,一并认为是Object
        Person p=(Person) ois.readObject();
        System.out.println(p.toString());
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            //关闭流
            ois.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
/**
 * @Description: 对象流--当一个类中包含另一个类的引用的序列化问题测试
 *      1.如果该类包含其他可序列化对象的引用,自己是不可序列化对象,是不支持序列化到文件中的
 *      2.如果该类包含其他不可序列化对象的引用,即使自己是可序列化的对象,也是不支持序列化的
 *      3.下述例子中Teachar和Person都必须是可序列化对象,才能满足写入磁盘文件或网络传输的需求--因此在开发中所有的javaBean都需要实现Serializable接口
 * @Param:
 * @return: void
 */
@Test
public void objectStreamTest2(){
    ObjectOutputStream oos = null;
    try {
        //1.创建文件对象--需要将对象写入哪个文件
        File file = new File("E:\\Documents\\java.txt");
        //2.创建节点流--输出流
        FileOutputStream fos=new FileOutputStream(file);
        //3.创建处理流-对象输出流
        oos = new ObjectOutputStream(fos);
        //4.创建需要写入的类对象
        Teacher t=new Teacher("李白",25,new Person("老师",30));
        //5.写入到文件
        oos.writeObject(t);//当文件未序列化时报错:com.carl.javaadvanced.ioclass.Person
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            //关闭流
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //获取文件中的对象
    ObjectInputStream ois= null;
    try {
        //1.创建文件对象--需要从哪个文件读取对象
        File dest=new File("E:\\Documents\\java.txt");
        //2.创建节点流--输入流
        FileInputStream fis=new FileInputStream(dest);
        //3.创建处理流--对象输入流
        ois = new ObjectInputStream(fis);
        //4.将获取的对象信息使用类对象承接--这里会失去对象记忆,一并认为是Object
        Teacher t=(Teacher) ois.readObject();
        System.out.println(t.toString());
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            //关闭流
            ois.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
/**
 * @Description: 对象流--当一个类创建两个对象都使用同一个其他对象问题测试
 *     1.Java序列化机制--序列化算法
 *          所有保存到磁盘中的对象都有一个序列化编号
 *          当程序试图序列化一个对象时,程序会先检查该对象是否已经被序列化,当该对象从未被序列化,系统才会将该对象转换成字节序列并输出
 *          如果某对象已经被序列化,程序将直接输出一个序列化编号,而不是再次重新序列化该对象
 *     2.该结果表示:
 *          序列化机制会在准备序列化时检测文件中是否已经存在该对象的序列化,如果存在则不会再次序列化
 *          两次序列化的地址值一致,表示并没有重新序列化
 *          当文件中已经存在该对象的序列化编号,修改该对象的成员变量值并不会引起序列化文件内容发生改变,依旧是反馈编号(只有在初始调用write()方法序列化时会发生对象转字节存储,后面再修改成员对象信息的操作都是无效操作,再次使用write()方法会记录一个访问顺序,便于在读取的时候按访问顺序读取)
 *          对象一旦被序列化,即序列化编号一旦确定,则不能修改后重新写到相同的文件中进行覆盖,报错:
 *          java.io.InvalidClassException: com.carl.javaadvanced.ioclass.Person; local class incompatible: stream classdesc serialVersionUID = -3758171511954800602, local class serialVersionUID = -3758171511954800601
 * @Param:
 * @return: void
 */
@Test
public void objectStreamTest3(){
    ObjectOutputStream oos = null;
    try {
        //1.创建文件对象--需要将对象写入哪个文件
        File file = new File("E:\\Documents\\teacher1.txt");
        //2.创建节点流--输出流
        FileOutputStream fos=new FileOutputStream(file,true);
        //3.创建处理流-对象输出流
        oos = new ObjectOutputStream(fos);
        //4.创建需要写入的类对象
        Person p = new Person("徒弟", 18);

        Teacher t1=new Teacher("李白",25,p);
        Teacher t2=new Teacher("杜甫",30,p);
        //5.写入到文件
        oos.writeObject(t1);//当文件未序列化时报错:com.carl.javaadvanced.ioclass.Person
        oos.writeObject(t2);
        oos.writeObject(p);
        p.setAge(15);
        oos.writeObject(p);
        oos.writeObject(t2);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            //关闭流
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //获取文件中的对象
    ObjectInputStream ois= null;
    try {
        //1.创建文件对象--需要从哪个文件读取对象
        File dest=new File("E:\\Documents\\teacher1.txt");
        //2.创建节点流--输入流
        FileInputStream fis=new FileInputStream(dest);
        //3.创建处理流--对象输入流
        ois = new ObjectInputStream(fis);
        //4.将获取的对象信息使用类对象承接--这里会失去对象记忆,一并认为是Object
        Teacher t1=(Teacher) ois.readObject();
        Teacher t2=(Teacher) ois.readObject();
        Person p=(Person) ois.readObject();
        Teacher t3=(Teacher) ois.readObject();
        System.out.println(t1.toString()+">>>>\n"+t2.toString()+">>>>\n"+p.toString()+">>>>\n"+t3.toString());
        System.out.println(t2==t3);//true
        System.out.println(t1.getP()==t2.getP());//true
        System.out.println(p == t1.getP());//true

    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            //关闭流
            ois.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

序列化机制的过滤功能:

在Java9中为ObjectOutputStream增加了两个方法

setObjectInputFilter():用于为对象输入流设置过滤器,当反序列化时,过滤器会自动调用checkInput()方法,用于检查序列化数据是否有效
getObjectInputFilter():获取输入流的过滤器状态
checkInput()方法检查序列化数据时有三种返回值:
	Status.REJECTED:拒绝恢复
	Status.ALLOWED:允许恢复
	Status.UNDECIDED:未决定状态,程序继续执行检查

自定义序列化:

transient:被该关键字修饰的成员变量将无法进行序列化--这样就可以控制某些成员变量不需要序列化
	缺点:成员变量被该关键字修饰后将完全被隔离在序列化机制之外,无法获取
另一种自定义序列化机制:
在需要序列化的类中实现这三种方法:
	private void writeObject(ObjectOutputStream out):负责写入特定类的实例状态
	private void readObject(ObjectInputStrean in):负责从流中读取并恢复对象的实例变量
	private void readObjectNoData():用于正确的初始化反序列化的对象
通过这三种方法可以控制类中哪些实例变量需要序列化,哪些不需要序列化,以及序列化流不完整的情况下进行初始化
彻底的自定义机制:
ANY-ACCESS-MODIFIER Object writeReplace():将对象转换为其他对象进行序列化-手动调用
ANY-ACCESS-MODIFIER Object readResolve():保护性复制整个对象-在readObject()之后被调用--一般用于序列化单例类、枚举类,该方法的返回值会覆盖原来反序列的对象,而原来readObject()反序列化的对象将会被立即丢弃
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
    out.writeObject(new StringBuffer(name).reverse());
    out.writeInt(age);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    this.name=new String(((StringBuffer)in.readObject()).reverse());
    this.age=in.readInt();
}
//这个方法实现后,在调用readObject()方法时必须使用该返回值类型接收
private Object writeReplace() throws ObjectStreamException{
    ArrayList<Object> list=new ArrayList<>();
    list.add(name);
    list.add(age);
    return list;
}

3.Java虚拟机读写其他进程的数据

使用Runtime对象的exec()方法可以运行平台上的其他程序,该方法产生一个Process对象,Process对象代表的就是java程序启动的子进程

Process提供了三个方法:
InputStream getErrorStream();获取子进程的错误流
InputStream getInputStream();获取子进程的输入流
OutputStream getOutputStream();获取子进程的输出流
public void childProcess(){
    Process p= null;
    BufferedReader br=null;
    try {
        p = Runtime.getRuntime().exec("javac");
        br=new BufferedReader(new InputStreamReader(p.getErrorStream(),"GBK"));
        String buff=null;
        while((buff=br.readLine())!=null){
            System.out.println(buff);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.RandomAccessFile(随机访问)

RandomAccessFile是java输入/输出流中功能最丰富的文件内容访问类,它既可以读取文件内容,也可以向文件写数据。

特点:支持随机访问方式访问文件,程序可以直接跳转到文件的任意位置进行数据的读写操作

使用场景:

  1. 只需要访问文件的部分内容
  2. 在指定位置追加内容

缺点:只能操作文件

构造器:

public RandomAccessFile(File file, String mode);
//使用String参数的构造器本质上还是使用的File参数的构造器--套娃
public RandomAccessFile(String name, String mode);
需要特别说明mode参数:该参数指定了RandomAccessFile对象的访问模式,共四个参数
    1. "r":以只读方式打开指定文件。如果试图对该RandomAccessFile执行写入操作,将抛出IOException
    2. "rw":以读、写方式打开指定文件,如果该文件不存在,则尝试创建该文件
    3. "rws":以读、写方式打开指定文件,同步文件内容和元数据的更新
    4. "rwd":以读、写方式打开指定文件,同步文件内容的更新
        
********************************************        
write()写数据时,"rw"模式不会将数据立即写入到磁盘中,而是在关闭close之前flush到磁盘中,"rwd"会将数据立即写入到磁盘中,如果数据写入出现异常,"rwd"会保存异常前写入的数据,"rw"则全部丢失

常用方法:

long getFilePointer();返回文件记录指针的当前位置
void seek(long pos);将文件记录指针定位到pos位置
三个重载的read方法与InputStream一样
三个重载的write方法与OutputSream一样
还特别提供了readXxx()WriteXxx()来完成输入和输出

5.NIO

内存映射文件方式处理输入和输出

NIO(new IO):相关包:
	java.nio:主要包含各种与Buffer相关的类
	java.nio.channels:主要包含与Channel和Selector相关的类
	java.nio.charset:主要包含与字符集相关的类
	java.nio.channels.spi:主要包含与Channel相关的服务提供者编程接口
	java.nio.charset.spi:主要包含与字符集相关的服务提供者编程接口
Channel(通道)和Buffer(缓冲)是新IO中的两个核心对象
	Channel是所有数据的传输通道,Channel提供了一个map()方法,通过该map()方法可以直接将“一块数据”映射到内存中
	Buffer是一个容器--数组,发送到Channel中的所有对象都必须首先放到Buffer中,从Channel中读取的数据也会先放到Buffer中
Charset类:用于将Unicode字符映射成为字节序列以及逆映射操作
Selector类:支持非阻塞式输入/输出

5.1Buffer

1.容量(capacity):缓冲区的容量,表示该Buffer的最大数据容量,创建后不可改变,不能为负值
2.界限(limit):第一个不应该被读出或写入的缓冲区位置索引,位于limit后的数据既不可被读也不可被写
3.位置(position):用于指明下一个可以被读出或写入的缓冲区位置索引,索引从0开始,即如果从Channel中读了两个数据后(0,1),position指向的索引应该是2(第三个即将读取数据的位置)
position可以自己设置,即设置从索引为mark处读取数据

Java-IO流详解_第2张图片

/**
 * XxxBuffer方法:
 *      put():向Buffer中放入一些数据--一般不使用,都是从Channel中获取数据
 *      get():向Buffer中取出数据
 *      flip():当Buffer装入数据结束后,调用该方法可以设置limit为当前位置,避免后续数据的添加--为输出数据做准备
 *      clear():对界限、位置进行初始化,limit置为capacity,position设置为0,为下一次取数做准备
 *      int capacity():返回Buffer的容量大小
 *      boolean hasRemaining():判断当前位置和界限之间是否还有元素可供处理
 *      int limit():返回Buffer的界限(limit)的位置
 *      Buffer mark():设置Buffer的标记(mark)位置,只能在0-position之间做标记
 *      int position():返回Buffer中的position值
 *      Buffer position(int newPs):设置Buffer的position,并返回position被修改后的Buffer对象
 *      int remaining():返回当前位置和界限之间的元素个数
 *      Buffer reset():将位置转到mark所在的位置
 *      Buffer rewind():将位置设置为0,取消设置的mark
 * @Param: 
 * @return: void
 */ 
public void BufferTest(){
    //创建一个CharBuffer缓冲区,设置容量为20
    CharBuffer buff= CharBuffer.allocate(20);
    //测试方法:
    //获取当前容量、界限、位置
    System.out.println("初始值:"+buff.capacity()+"\n"+
            buff.limit()+"\n"+
            buff.position());//20、20、0
    buff.put('1');
    buff.put('2');
    buff.put('3');
    buff.position(1).mark();//标记位置索引处
    buff.rewind();//将position设置为0,并将mark清除,此时再调用reset()将会报错java.nio.InvalidMarkException
    buff.mark().reset();//将position转移到标记处
    buff.put("abc");
    buff.put("java");
    //abcjavafalse
    //设置界限值
    buff.limit(buff.position());
    System.out.println("添加数据后:"+buff.capacity()+"\n"+
            buff.limit()+"\n"+
            buff.position());//20、7、7
    //初始化容量、界限、位置
    int position = buff.position();
    buff.position(0);
    System.out.println("修改后:"+buff.capacity()+"\n"+
            buff.limit()+"\n"+
            buff.position());//20、7、0
    //遍历Buffer数组的数据
    for (int i = 0; i < position; i++) {
        System.out.print(buff.get());
    }
    System.out.println();
    //hasRemaining判断是否可继续添加元素,position >= limit返回false,position < limit返回true
    System.out.println(buff.remaining());//0
    System.out.println(buff.hasRemaining());//false
    buff.limit(15);
    System.out.println(buff.hasRemaining());//true

    System.out.println(buff.position());//7
    System.out.println("remaining="+buff.remaining());//8 还可以添加8个元素
    buff.clear();
    System.out.println("clear后:"+buff.capacity()+"\n"+
            buff.limit()+"\n"+
            buff.position());//20、20、0
}

Buffer的缺点:

1. XxxBuffer使用allocate()方法创建的Buffer对象是普通的Buffer--创建在Heap上的一块连续区域--间接缓冲区
2. ByteBuffer还有一个allocateDirect()方法创建的Buffer是直接Buffer--创建在物理内存上开辟空间--直接缓冲区
	间接缓冲区:易于管理,垃圾回收器可回收,但是空间有限,读写文件速度较慢(从磁盘读到内存)
	直接缓冲区:空间较大,读写速度快(从磁盘读到磁盘的速度),但是不受垃圾回收器的管理,创建和销毁都极耗性能
3. 直接Buffer的创建成本高于间接Buffer,所以直接Buffer只用于生存期长的Buffer。
4. 直接Buffer只有ByteBuffer才能创建,因此如果需要其他的类型,只能使用创建好的ByteBuffer转型为其他类型

5.2Channel

Channel可以直接将指定的文件的部分或全部直接映射为Buffer--映射
程序不能直接访问Channel中的数据(读取、写入都不行),必须通过Buffer进行承载后从Buffer中操作这些数据
1.Channel不能使用构造器来创建,只能通过字节流InputStream,OutputStream(节点流)来调用getChannel()方法来创建
2.不同的节点流调用getChannel()方法创建的Channel对象不一样。如:FileInputStream/FileOutputStream->FileChannel
								PipedInputStream/PipedOutputStream->Pip.SinkChannel/Pip.SourceChannel
3.Channel常用的三个方法:
		* MappedByteBuffer map(FileChannel.MapMode mode,long position,long size):将Channel对应的部分或全部的数据映射成ByteBuffer
				参数:mode:映射模式-三种:READ_ONLY(只读)、PRIVATE(私有(写时复制))、READ_WRITE(读写)
					 position:Buffer的初始化位置
					 size:Buffer的容量
		* read():用于读取Buffer中的数据
		* write():用于将数据写入Buffer

FileInputStream、FileOutputStream创建Channel进行文件复制

FileChannel inChannel = null;
FileChannel outChannel = null;
try {
    //1.创建文件对象--指定读取和写入的文件
    File src=new File("E:\\Documents\\java.txt");
    File dest=new File("E:\\Documents\\java1.txt");
    //2.使用FileInputStream进行文件读取、FileOutputStream进行文件写入
    //不一样的是采用管道的方式--这里就需要getChannel()创建Channel对象
    inChannel = new FileInputStream(src).getChannel();//只能读
    outChannel = new FileOutputStream(dest).getChannel();//只能写
    //3.将管道数据通过map()方法传递给MappedByteBuffer对象进行缓冲承载
    MappedByteBuffer buffer=inChannel.map(FileChannel.MapMode.READ_ONLY, 0, src.length());
    //4.将获取的内容buffer交给Channel写回到指定文件java1.txt中
    outChannel.write(buffer);
    //将文件内容打印到控制台
    //1.初始化position和limit
    buffer.clear();
    //2.设置输出编码格式
    Charset charset=Charset.forName("UTF-8");
    //3.将ByteBuffer转换成字符集的Buffer
    CharsetDecoder decoder=charset.newDecoder();
    CharBuffer cb=decoder.decode(buffer);
    //4.输出字符集buffer
    System.out.println(cb);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        inChannel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    try {
        outChannel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
使用RandomAccessFile创建Channel对象
	1.管道的写数据的方式是追加--但是重新执行程序就是覆盖--这种情况需要修改position的位置
	2.源文件的随机访问对象创建的管道必须可读,目标文件的随机访问对象创建的管道必须可写
	3.源文件的随机访问对象创建的管道使用map()方法生成buffer后,目标文件的随机访问对象创建的管道使用write()方法写出buffer
	4.最后一定要关闭流

RandomAccessFile创建Channel对象进行文件复制

FileChannel channel = null;
FileChannel channel1 = null;
try {
    File srcPath=new File("E:\\Documents\\java.txt");
    File destPath=new File("E:\\Documents\\java1.txt");
    channel = new RandomAccessFile(srcPath,"r").getChannel();
    channel1 = new RandomAccessFile(destPath,"rw").getChannel();
    ByteBuffer map = channel.map(FileChannel.MapMode.READ_ONLY, 0, srcPath.length());
    channel1.position(destPath.length());
    channel1.write(map);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        channel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

使用固定容量的Buffer获取Channel读取的数据

File src=new File("E:/Documents/java.txt");
FileChannel channel = new FileInputStream(src).getChannel();
ByteBuffer bf=ByteBuffer.allocate(256);
int len=0;
while ((len=channel.read(bf))!=-1) {
    //limit设置为position,避免操作空白区域。如果覆盖到指定位置后,后续还有内容也不可读取,这样避免覆盖不完全出现错误数据
    bf.flip();
    System.out.println(bf);
    Charset charset=Charset.forName("UTF-8");
    CharsetDecoder decoder = charset.newDecoder();
    CharBuffer cb = decoder.decode(bf);
    System.out.println(cb);
    //重置buffer的参数,内容依旧是采用覆盖的方式,clear不会修改Buffer中的内容
    bf.clear();
}

5.3字符集和Charset

由于计算机的文件、数据、图片文件底层都是二进制存储的(全部都是字节码)

编码:将明文的字符序列转换成计算机理解的二进制序列称为编码

解码:将二进制序列转换成明文字符串称为解码
Java-IO流详解_第3张图片

java默认使用Unicode字符集,当从操作系统中读取数据到java程序容易出现乱码

当A程序使用A字符集进行数据编码(二进制)存储到硬盘,B程序采用B字符集进行数据解码,解码的二进制数据转换后的字符与A字符集转换后的字符不一致就出现了乱码的情况。

JDK1…4提供了Charset来处理字节序列和字符序列之间的转换关系

  • 该类包含了用于创建编码器和解码器的方法

  • 获取Charset所支持的字符集的方法availableCharsets()

常用方法:
forName(String charsetName):创建Charset对应字符集的对象实例
newDecoder():通过Charset对象获取对应的解码器
newEncoder():通过Charset对象获取对应的编码器

CharBuffer encode(ByteBuffer bb):将ByteBuffer中的字节序列转换为字符序列
ByteBuffer decode(CharBuffer cb):将CharBuffer中的字符序列转换为字节序列
ByteBuffer encode(String str):将String中的字符序列转换为字节序列
//      SortedMap stringCharsetSortedMap = Charset.availableCharsets();
//      stringCharsetSortedMap.forEach((key,value)-> System.out.println(key+"<->"+value));
        Charset charset = Charset.forName("UTF-8");
        ByteBuffer bb = charset.encode("中文字符");
        System.out.println(bb);//java.nio.HeapByteBuffer[pos=0 lim=12 cap=19]
        //编码解码方式一:
        CharBuffer decode1 = charset.decode(bb);
        System.out.println(decode1);//中文字符
        ByteBuffer encode1 = charset.encode(decode1);
        System.out.println(encode1);//java.nio.HeapByteBuffer[pos=0 lim=12 cap=19]
        //编码解码方式二:
        CharsetDecoder decoder = charset.newDecoder();
        CharsetEncoder encoder = charset.newEncoder();
        CharBuffer decode = decoder.decode(encode1);
        System.out.println(decode);//中文字符
        ByteBuffer encode = encoder.encode(decode);
        System.out.println(encode);//java.nio.HeapByteBuffer[pos=0 lim=12 cap=19]
    }

5.4文件锁

  • 文件锁是在多个运行的程序需要并发修改同一个文件时所必须的
  • 使用文件锁可以有效地阻止多个进程并发的修改同一个文件,但是并不是所有平台都提供了文件锁机制
  • 文件锁能控制文件的全部或部分字节的访问
  • 文件锁在不同的操作系统的差别较大

NIO中java提供了FileLock来支持文件锁定功能,在FileChannel中提供的lock()/tryLock()方法可以获取文件锁FileLock对象

  • lock(long position,long size,boolean shared):如果未获取文件锁,则会导致线程阻塞
  • tryLock(long position,long size,boolean shared):如果未获取文件锁直接返回null,获取返回该文件锁
    • 上述两个方法参数解析:
      • position:从文件的position位置开始
      • size:给长度为size的内容加锁
      • shared:true表示为共享锁:允许多个进程来读取该文件,但是其他进程获得该文件的排他锁;false表示该锁为排他锁,自己读取时其他线程不能获取锁

直接使用lock()或tryLock()方法获取的文件锁是排他锁,即shared默认值为false

FileLock fileLock = null;
try {
    FileOutputStream fileOutputStream = new FileOutputStream("E:/Documents/java.txt");
    FileChannel channel = fileOutputStream.getChannel();
    fileLock = channel.tryLock();//创建锁以后,其他程序将无法对该文件进行修改
    Thread.sleep(1000);
} catch (IOException e) {
    e.printStackTrace();
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    try {
        fileLock.release();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

虽然文件锁可以用于控制并发访问,但是还是推荐使用数据库来保存程序信息,而不是文件

注意:

1.对于部分平台,文件锁即使可以被获取,文件依旧是可以被其他线程操作的
2.对于部分平台,不支持同步地锁定一个文件并把它映射到内存中
3.文件锁是由java虚拟机持有的,如果两个java程序使用同一个java虚拟机,则不能对同一个文件进行加锁操作
4.对于部分平台,关闭FileChannel时,会释放java虚拟机在该文件上的所有锁,因此应该避免对同一个被锁定的文件打开多个FileChannel

5.5NIO工具类

NIO问题:

1.File类功能有限
2.File类不能利用特定文件系统的特性
3.File类的方法性能不高
4.File类大多数方法出错时不会提供异常信息

升级NIO.2:

提供了Path接口和Paths实现工具类
提供了Files工具类
public class Nio2Test {
    @Test
    public void pathsTest(){
        Path path = Paths.get("E:/Documents/java.txt");
        //path包含的路径数量
        System.out.println(path.getNameCount());//2=>(Document,java.txt)
        //获取根目录
        System.out.println(path.getRoot());//E:\
        //获取绝对路径
        System.out.println(path.toAbsolutePath());//E:\Documents\java.txt
        Path path1 = Paths.get("E:", "Documents", "java.txt");
        System.out.println(path1);//E:\Documents\java.txt
    }
    @Test
    public void File() throws IOException {
        //复制文件
        Files.copy(Paths.get("E:","Documents","java1.txt"), new FileOutputStream("E:/Documents/java2.txt"));
        //检查文件是否为隐藏文件
        System.out.println("Nio2Test.java:"+Files.isHidden(Paths.get("out.txt")));//false
        List<String> list = Files.readAllLines(Paths.get("E:/Documents/java2.txt"), Charset.forName("UTF-8"));
        System.out.println(list);
        //获取文件大小
        long size = Files.size(Paths.get("E:/Documents/java2.txt"));
        System.out.println(size);
        //写数据到文件中
        ArrayList<String> poem = new ArrayList<>();
        poem.add("今天搞完IO没得问题吧");
        poem.add("明天搞完网络编程第一章没得问题吧");
        poem.add("后天搞完网络编程第二章搞完IO没得问题吧");
        poem.add("大后天搞完网络编程第三章搞完IO没得问题吧");
        Path write = Files.write(Paths.get("E:/Documents/java2.txt"), poem, Charset.forName("UTF-8"));//覆盖
        System.out.println(write);
        //按行获取文件内容,使用Stream接口中的forEache方法遍历
        Files.lines(Paths.get("E:/Documents/java2.txt"),Charset.forName("UTF-8")).forEach(ele-> System.out.println(ele));
        //获取目录下文件,使用Stream接口中的forEache方法遍历
        Files.list(Paths.get("E:/Documents")).forEach(ele-> System.out.println(ele));
        //获取当前文件的根目录别名
        FileStore fileStore = Files.getFileStore(Paths.get("E:/Documents/java2.txt"));
        System.out.println(fileStore);
        //E盘总空间
        long totalSpace = fileStore.getTotalSpace();
        System.out.println(totalSpace);
        //E盘可用空间
        long unallocatedSpace = fileStore.getUnallocatedSpace();
        System.out.println(unallocatedSpace);
    }
}

你可能感兴趣的:(java基础,java,开发语言)