File类的一个对象,代表一个文件或一个文件目录
File类声明在java.io 包下
File类涉及到关于文件或目录的创建,删除,重命名,文件大小,修改时间,等方法,但是没有涉及到文件内容;如果需要修改,读取文件内容,必须使用io流
File类的对象常会作为参数传递到io流的构造器中,指明读取或写入的“终点”。
File有四个构造器,当我们new一个File时,它只是单纯的在内存层面,不会管是否存在这个文件,但我们print的时候,即:调用toString时,就是把这个路径输出。
有一点要注意,在main中使用 File(String pathname) 构造器时,这个Pathname时相对于当前工程下的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cUbajTbN-1603244215897)(/Users/luca/MarkText-img-Support/2020-07-03-11-09-18-image.png)]
//1. 最简单,直接把文件的路径的字符串输入 // File file = new File("hello.txt"); // System.out.println(file1); 打印结果为 :hello.txt // 还有一点要注意 public File(String pathname) { if (pathname == null) { throw new NullPointerException(); } this.path = fs.normalize(pathname); this.prefixLength = fs.prefixLength(this.path); } //2.File(上一级文件目录,这一文件目录或文件),这样的话这个File对象可能是一个目录 //File file2 = new File("/User/luca","Desktop"); //System.out.println(file2); 将输出:/User/luca/Desktop public File(String parent, String child) { if (child == null) { throw new NullPointerException(); } if (parent != null) { if (parent.isEmpty()) { this.path = fs.resolve(fs.getDefaultParent(), fs.normalize(child)); } else { this.path = fs.resolve(fs.normalize(parent), fs.normalize(child)); } } else { this.path = fs.normalize(child); } this.prefixLength = fs.prefixLength(this.path); } //3.File(File对象,路径) // File file3 = new File(file2,"test.txt"); // System.out.println(file3); 将输出:/User/luca/Desktop/test.txt public File(File parent, String child) { if (child == null) { throw new NullPointerException(); } if (parent != null) { if (parent.path.isEmpty()) { this.path = fs.resolve(fs.getDefaultParent(), fs.normalize(child)); } else { this.path = fs.resolve(parent.path, fs.normalize(child)); } } else { this.path = fs.normalize(child); } this.prefixLength = fs.prefixLength(this.path); } //4.这个先让他去 public File(URI uri)路径分隔符
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G6klZzs1-1603244215899)(/Users/luca/MarkText-img-Support/2020-07-03-10-58-20-image.png)]
public String getAbsolutePath(): 获取绝对路径
public String getPath() : 获取路径
public String getName() : 获取名称
public String getParent(): 获取上层文件目录路径。若无,返回null
public long length() : 获取文件长度(即: 字节数) 。不能获取目录的长度。
public long lastModified() : 获取最后一次的修改时间,毫秒值
public String[] list(): 获取指定目录下的所有文件或文件目录的名称数组,用于文件目录
public File[] listFiles(): 获取指定目录下的所有文件或者文件目录的File数组,用于文件目录
public boolean renameTo(File dest); 把文件重命名为指定的文件路径:file1.renameTo(file2); 如果要保证这个执行成功,这file1在硬盘上存在,而file2在硬盘上不存在。这时就会像复制一样,file1被复制到file2。
public boolean isDirectory(): 判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() : 判断是否存在
public boolean canRead() : 判断是和否可读
public boolean canWrite() : 判断是否可写
public boolean isHidden() : 判断是否隐藏
public boolean createNewFile() : 创建文件。若文件存在,则不创建,返回false
public boolean mkdir() : 创建文件目录。如果此文件目录存在,就不创建。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs() : 创建文件目录。如果上层文件目录不存在,一并创建
public boolean delete(): 删除文件或文件目录;java中的删除不会走回回收站。
按数据单位不同分为:
字节流(8 bit ) :适用于非文本文件(图片,视频,.doc,.ppt 等);
字符流(16 bit 也就是一个char) : 字符流适合来处理文本文件(.txt, .java, .c, .cpp等)
按照数据的流向不同分为:
输入流,
输出流
按照节点的角色不同分为:
节点流,可以从或向一个特定的地方(节点)读写数据,也就是直接作用在文件上的流称为节点流
处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。
这四个类是抽象的(static), 作为io流的四个基类,java的IO流共涉及到40多个类,它们都是从这四个基类中派生的。而且它们的子类中都有它们的名字
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L8FigRk6-1603244215901)(/Users/luca/MarkText-img-Support/io流体系.jpg)]
在这个表中的第二行,FileInputStream, FileOutputStream, FileReader, FileWriter 就是节点流,可以直接操作文件。而剩下的全部都是处理流,作用在已有的流之上。[标红是要重点关注的]
也就是我们最基本,能够直接操作文件的四个节点流:FileInputStream, FileOutputStream, FileReader, FileWriter。 它们都逃不出这四个步骤:
实例化一个File对象
提供一个合适的流
对流进行操作,比如读操作,写操作等
关闭流
不要忘记处理异常
read(char[]): FileReader 对象的read方法可以接受空参,也可以接受char[]作为参数,这个数组有多大就一次读多少个char,返回每次读入到这个数组中的字符个数。
我们也可以直接将路径字符串传入FileReader的构造器中,在这个构造器中它会自动帮我们创建File对象
char[] charbuffer = new char[10]; int len; while((len = fr.read(charbuffer)) != -1){ String str = new String(charbuffer,0,len); System.out.println(str); //for(int i = 0; i<len; i++){ //System.out.print(charbuffer[i]); //} }输出操作对应的文件可以不存在,如果不存在会自动创建
FileWriter的构造器可以接收两个参数,第一个是File类型,第二个是boolean,true代表在文件末尾添加,False表示直接覆盖掉。也可以省略,默认是False直接覆盖掉
写操作为FileWriter的writer方法。writer方法和read方法一样,是可以将char[] 作为参数,因此我们可以实现一边从这个文件读出到char[] 数组,在把char[] 中字符写到另外一个文件中。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wRikb9gN-1603244215904)(/Users/luca/MarkText-img-Support/2020-07-03-21-42-15-image.png)]
//例子 public static void testWrite() { FileWriter fw = null; char[] charbuffer = {'i','w','a','n','t','y'}; try { File file = new File("FileTest/src/com/luca/test.txt"); fw = new FileWriter(file,true); fw.write("i love you"); fw. } catch (IOException e) { e.printStackTrace(); } finally { if (fw != null) { try { fw.close(); } catch (IOException e) { e.printStackTrace(); } } } }上面两种都是字符流,只适合处理文本,如果要处理类型 图片,音频等非文本文件就要使用字节流
使用举例
//获取图片中的byte,并输出, utf-8中,中文占据3个byte public static void getDataFromImg() { FileInputStream fs = null; try { //Users/luca/Desktop/test.png File file = new File("FileTest/src/com/luca/test.txt"); fs = new FileInputStream(file); byte[] databuf = new byte[64]; int len; while ((len = fs.read(databuf))!= -1){ String str = new String(databuf,0,len); System.out.println(str); System.out.println(Arrays.toString(databuf)); } } catch (IOException e) { e.printStackTrace(); } finally { if (fs != null){ try { fs.close(); } catch (IOException e) { e.printStackTrace(); } } } }其实不管什么文件,物理存储都是二进制数据,我们对文件的读写其实都是对二进制数据进行读写,所以所谓的文件流其实是依靠转换流来实现的。
这也就是只有 字节流 ——> 字符流的原因。
FileReader 其实是 转换流 InputStreamReader 的直接子类,而 InputStreamReader 才继承与 Reader
public class FileReader extends InputStreamReader { public FileReader(String fileName) throws FileNotFoundException { super(new FileInputStream(fileName)); } public FileReader(File file) throws FileNotFoundException { super(new FileInputStream(file)); } }缓冲流是处理流的一种,它是在文件流的基础上进行包装。它的主要作用就是提高文件的读写效率。它们是:BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter.
以BufferedInputStream 为例
//BufferedInputStream bis = new BufferedInput(new FileInputStream(new File("test.png"))); File scr = new File("test.png"); FileInputStream fis = new FileInputStream(scr); BufferedInputStream bis = new BufferedInput(fis); //剩下的读取部分的代码都是一样的,可以用数组,也可以只用一个int型的变量 readData(); bis.close(); fis.close();注意,在关闭的时候,先关闭外层的流,再关闭内层的流。但是在关闭外层流的时候,内层流也会自动关闭,所以我们直接关闭外层流即可。
缓冲流有一个缓冲区,默认大小为8192的缓冲区,对于写的流来说,当缓冲区满了时,就会自动调用flush()刷新缓冲区,将缓冲区的数据写入文件。 也可以显示调用flush()来刷新缓冲区。
BufferedReader 有一个 readLine();以String型式返回读到的一行的内容,它不会包含文件的换行符;当读到EOF会返回null,
转换流也是一种处理流,它提供了字节流和字符流之间的转换。
Java提供了两个转换流:
InputStreamReader
将InputStream转换为Reader(将一个字节的输入流转化为一个字符的输入流)
是Reader的子类,属于字符流。
字节–>字符,对应一个解码的过程
OutputStreamWriter
将OutputStream转化成Writer(将一个字符的输出流转化为一个字节的输出流),
是Writer的子类,属于字符流
字符—>字节,对应一个编码的过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7UhGVWeQ-1603244215905)(/Users/luca/MarkText-img-Support/2020-07-07-09-02-39-image.png)]
当字节流中的数据都是字符时,转化为字符流处理的会更快
很多时候我们使用转换流在处理乱码问题。实现编码和解码的过程。比如我们想将一个文件有utf-8编码变成gbk编码就可以使用这个转换流
System.in和System.out分别代表了系统标准的输入和输出设备,属于字节流
默认输入设备是: 键盘,输出设备是: 显示器(控制台)
System.in的类型是InputStream
System.out的类型是PrintStream,其是OutputStream的子类, FilterOutputStream 的子类
重定向: 通过System类的setIn,setOut方法对默认设备进行改变。
public static void setln(InputStream in)
public static void setOut(PrintStream out)
例子
//从键盘读入字符串,一次读取一整行,转化为大写,然后继续读取,知道读取到'e'或'exit' //思路: System.in --> 转换流 --> BufferedReader.readLine() public static void test1(){ BufferedReader br = null; try { //用转化流直接将标准输入流包起来,将字节流转化为字符流,输入设备是键盘 InputStreamReader isr = new InputStreamReader(System.in); //想使用缓冲流中的readLine方法,所以创建一个缓冲流 br = new BufferedReader(isr); String str = ""; while(true){ String data = br.readLine(); if("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)){ System.out.println("程序结束"); break; } str +=(data.toUpperCase() + "\n"); } System.out.println(str); } catch (IOException e) { e.printStackTrace(); } finally { if(br != null){ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } }打印流: PrintStream和PrintWriter, 实现将基本数据类型的数据格式转化为字符串输出
提供了一系列重载的print)和println()方法,用于多种数据类型的输出
PrintStream和PrintWriter的输出不会抛出IOException异常
PrintStream和PrintWriter有自动fush功能
PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。
在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
System.out返回的是PrintStream的实例例子,修改System.out的默认输出设备
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-moedkiZY-1603244215907)(/Users/luca/MarkText-img-Support/2020-07-07-15-40-46-image.png)]
为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流。
作用:用于读写基本数据类型的变量或字符串
数据流有两个类: (用于读取和写出基本数据类型、String类的数据)
DatalnputStream 和 DataoOutputStream
分别“套接”在InputStream 和 OutputStream 子类的流上
Datalnputstream中的方法
boolean readBoolean();
byte readByte()
char readChar();
float readFloat()
double readDouble();
short readShort()
long readLong();
int readlnt()
String readUTF();
void readFully(byte[] b)
DataoOutputstream中的方法
上述的方法的read改为相应的write即可。 public static void main(String[] args) throws IOException { DataOutputStreamTest(); DataInputStreamTest(); } public static void DataOutputStreamTest() throws IOException { DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt")); dos.writeUTF("luca"); dos.flush(); dos.writeInt(123); dos.writeBoolean(true); dos.flush();//不写flush也行 dos.close(); } public static void DataInputStreamTest() throws IOException { DataInputStream dis = new DataInputStream(new FileInputStream("data.txt")); //读的顺序要与写入的顺序一致 String name = dis.readUTF(); int age = dis.readInt(); boolean ismale = dis.readBoolean(); System.out.println("name:" + name); System.out.println("age:" + age*3); System.out.println("name:" + ismale); System.out.println("sdad"); }ObjectlnputStream和OjbectOutputSsteam
用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
序列化: 用ObjectOutputStream类保存基本类型数据或对象的机制
反序列化: 用ObjectlnputStream类读取基本类型数据或对象的机制
ObjectOutputStream和ObjectlinputStream不能序列化static和transient修饰的成员变基. 其实transient这个关键字的作用就是在序列化对象的时候,被transient修饰的属性就不会被序列化
凡是Serializable接口的类都有一个表示序列化版本标识符的静态变量:private static final long serialVersionUID;
serialVersionUID用来表明类的不同版本间的兼容性。总而言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
如果类没有显示定义这个静态变量,它的值是Java运行时环境根据类的内部细节自动生成的。 若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议,显式声明。
简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)
对象序列化机制:允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上, 或通过网络将这个二进制流传输到另外一个节点。当其它程序获取了这种二进制流,就可以恢复成原来Java对象
例子
//序列化 public static void outputTest(){ ObjectOutputStream oop = null; try { oop = new ObjectOutputStream(new FileOutputStream("object.dat")); oop.writeObject(new String("luca")); } catch (IOException e) { e.printStackTrace(); } finally { if(oop != null){ try { oop.close(); } catch (IOException e) { e.printStackTrace(); } } } } // 反序列化 public static void inputTest(){ ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream("object.dat")); Object person = ois.readObject(); String str = (String) person; System.out.println(str); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } finally { if(ois != null){ try { ois.close(); } catch (IOException e) { e.printStackTrace(); } } } }包装对象是可序列化的要求:
继承serializable接口
有serialVersionUID(如果自己不提供,JVM会自动生成)
所有的属性都是可序列化的(默认情况下,所有的基本基本数据类型都是可序列化的,开发java的程序员已经帮我们写好了)
如果要将自己的类传入对象流中进行序列化就必须要让自定义的类继承serializable接口,同时提供一个唯一标示这个类的serialVersionUID;当然,上面也说了,也可以不写,java会自动根据类的内部细节自动生成一个serialVersionUID,但是一旦这个类的细节改变了,这个UID也会改变,这就会导致在类变化之前序列化的类无法反序列化。因为反序列化是要通过这个ID来找到这是什么类,把它还原成这个类,一旦ID找不到,JVM就不知道还原成什么类,所以为了这个ID保持不变,我们应该显示的声明这个UID。 只要这个UID不变,类的内部细节改变,反序列化也没有问题,也能找到是这个类。
//具体什么数字没有要求,这要是能唯一标示这个类就行 public static final long serialVersionUID = 12312234532423423L; public interface Serializable { }仔细一看,其实Serializable接口里面什么也没有,没有任何的抽象方法,类似于这样我们通常称之为标示接口。
RandomAccessFile 声明在java.io包下,直接继承于Object类,而不像其他的流都继承那四个抽象基类,
并且它实现了Datalnput、DataOutput这两个接口,也就意味着这个类既可以读也可以写,既可以作为一个输入流,也可以作为一个输出流
RandomAccessFile 类支持“随机访问"的方式,程序可以直接跳转到文件的任意位置来读、写文件
RandomAccessFile对象包含一个记录指针,用于标示当前读写的位置
RandomAccessFile 类对象可以自由移动记录指针:
long getFilePointer(): 获取文件记录指针的当前位置
void seek(long pos): 将文件记录指针定位到 pos 位置
创建 RandomAccessFile 类实例需要指定一个mode 参数,该参数指定 RandomAccessFile 的访问模式:
r:以只的读方式打开
rw: 打开以便读取和写入
rwd:打开以便读取和写入,同步文件内容的更新
rws:打开以便读取和写入,同步文件内容和元数据的更新
如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。 如果模式为rw读写。如果文件不存在则会去创建文件,如果存在则不会创建。
RandomAccessFile实现插入
public static void test3() throws IOException { File file = new File("data.txt"); //System.out.println(file.length()); RandomAccessFile raf = new RandomAccessFile(file, "rw"); //RandomAccessFile raf = new RandomAccessFile("data.txt", "rw"); //将角标为3后面的所有数据复制到builder中 StringBuilder builder = new StringBuilder((int)file.length()); raf.seek(3);//将指针定位到角标为3的位置 raf.write("wzy".getBytes()); byte[] buf = new byte[1024]; int len; while((len = raf.read(buf)) != -1){ builder.append(new String(buf,0,len)); } raf.seek(3); //将要插入的数据插入 raf.write("asdsady".getBytes()); //将builder中的数据再写入 raf.write(builder.toString().getBytes()); //System.out.println(file.length()); raf.close(); }RandomAccessFile在功能上与其他的流不同之处就是有一个指针,它可以深入到文件的某一个位置精确修改文件,而其他的流只能在尾部添加,要么就是全部覆盖。
我们可以用RandomAccessFile这个类,来实现一个多线程断点下载的功能,用过下载工具的朋友们都知道,下载前都会建立两个临时文件,一个是与被下载文件大小相同的空文件(比如下载1G的电影,先开辟处1G的空间出来),另一个是记录文件指针的位置文件,每次暂停的时候,都会保存上一次的指针,然后断点下载的时候,会继续从上一次的地方下载,从而实现断点下载或上传的功能,有兴趣的朋友们可以自己实现下。
我们之前讲的流大部分都是操作文件的,也就是说我们只介绍了一种节点流——文件流,以及基于它的处理流。但是还有几种节点流,它们并不是用来访问文件的,比如ByteArrayOutputStream 它也是节点流,它是用来访问数组的,而不是访文件的,所以它不需要传入File类 还有类似的访问管道的流,访问字符串的流它们都是节点流。
ASCII: 美国标准信息交换码。用一个字节的7位可以表示。
ISO8859-1: 拉丁码表。欧洲码表,用一个字节的8位表示。
GB2312: 中国的中文编码表。最多两个字节编码所有字符
GBK: 中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
Unicode: 国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
UTF-8: 变长的编码方式,可用1-4个字节来表示一个字符。
Unicode不完美,这里就有三个问题,一个是,我们已经知道,英文字母只用一个字节表示就够了,第二个问题是如何才能区别Unicode和ASCIl? 计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢? 第三个,如果和GBK等双字节编码方式一样,用最高位是1或0表示两个字节和一个字节,就少了很多值无法用于表示字符,不够表示所有字符。Unicode在很长一段时间内无法推广,直到互联网的出现。
面向传输的众多 UTF (UCS Transfer Format) 标准出现了,顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。
Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的Unicode纲码是UTF-8和UTF-16。
UTF-8:英:1;中:3
GB2312:英:1;中:2
GBK:英:1;中:2
GB18030:英:1;中:2
UTF-16:英:4;中:4
UTF-16LE:英:2;中:2
UTF-16BE:英:2;中:2
ISO-8859-1:英:1;中:1
java char的大小根据所用的编码不同而改变,假设idea使用了UTF-8,则char的长度为3个字节