Java中的File类与IO
为了能够永久地保存程序中创建的数据,我们需要将他们存储到磁盘或其他永久存储设备的文件中。而对于文件的操作,我们只需要File类包含了获得一个文件/目录的属性,以及对文件/目录进行改名和删除的方法。
一、File类
1.1 File类构造函数与分隔符
绝对文件名( absolute file name) 是 由文件名和它的完整路径以及驱动器字母组成。 绝对文件名是依赖机器的。
相对文件名是相对于当前工作目录的。对于相对文件名而言,完整目录被忽略。
package FileOperation;
import java.io.File;
public class Demo02 {
public static void main(String[] args) {
/* 1.构造器,里面可以是文件也可以是文件夹
* 2.关联的是一个相对路径的文件
* 对于当前的IDEA/Eclipse而言 相对路径获取的文件都在项目根目录下
* 3.使用File类的exists()方法,来判断关联的该文件是否存在
*/
File f1 = new File("test.txt");
//f1与f2关联的路径相同,f2表示和此类同目录的test.txt文件
File f2 = new File("D:\\eclipse\\eclipse-workspace\\FileAndIOStream\\src\\test.txt");
//判断项目根目录下是否有test.txt文件
System.out.println(f1.exists());
System.out.println(f2.exists());
/*
* 1.关联一个绝对路径
* 2.如果此路径是Linux的文件路径 / 根目录
* f4对象关联的是根目录下的文件夹目录1下的目录2下的目录3
*/
File f3 = new File("D:\\a.txt");
System.out.println(f3.exists());
File f4 = new File("/1/2/3");
/*
*1.通过父文件夹的路径找子文件夹或文件
*/
File paFile = new File("D:\\eclipse\\eclipse-workspace\\FileAndIOStream\\src");
File f5 = new File(paFile, "test.txt");
System.out.println(f5.exists());
/*1.因为Linux/Unix/Windows/MAC这些操作系统中 路径分割符是不统一的
*2.当我们不确定程序所运行的系统环境 如果需要用到文件路径 路径分割符采用File自带的separator
*/
String parentParth = "D:\\eclipse\\eclipse-workspace\\FileAndIOStream\\src";
File f6 = new File(parentParth + File.separator+"test.txt");
System.out.println(f6.exists());
System.out.println(File.separator);
}
}
/*
我的项目根目录下没有test.txt,但是D盘有a.txt
运行结果:
false
false
true
false
false
\
*/
1.2 File类获取相关
package FileOperation;
import java.io.File;
import java.text.DateFormat;
import java.util.Date;
public class Demo01 {
public static void main(String[] args) {
File f1 = new File("Demo02.java");
//1.返回文件或文件夹的完整名称,包括路径
System.out.println(f1.getAbsolutePath());
//2.返回文件名,文件对象构造函数中写的是啥 这边打印的就是啥
System.out.println(f1.getPath());
//3. file.exists()判断文件或者文件夹是否存在
File f3 = new File("D:\\1");
boolean c = f3.exists();
if(c) {
System.out.println("存在");
}else {
System.err.println("不存在");
boolean d = f3.mkdirs();
if(d) {
System.out.println("目录创建成功!");
}else {
System.out.println("目录创建失败!");
}
}
//4.获取文件名 - 获取相对路径
System.out.println(f1.getName());
//5.获取文件大小 单位字节 b
System.out.println(f1.length());
//6.最后一次修改时间
System.out.println();
String time = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG).format(new Date(f1.lastModified()));
System.out.println(time);
//7.返回 File 对象代表的完整的目录和文件名。
System.out.println(f1.getParent());
File f2 = new File("D:\\eclipse\\eclipse-workspace\\FileAndIOStream\\src\\FileOperation\\Demo02.java");
System.out.println(f2.getParent());
}
}
1.3 File类其他方法
package FileOperation;
import java.io.File;
public class Demo05 {
public static void main(String[] args) {
File file = new File("test.txt");
System.out.println("Does it exist? "+ file.exists());
System.out.println("The file has " + file.length() + "bytes");
System.out.println("Can it be read? " + file.canRead());
System.out.println("Can it be written? " + file.canWrite());
System.out.println("Is it a directory? " + file.isDirectory());
System.out.println("Is it a file? " + file.isFile());
System.out.println("Is it absolute? " + file.isAbsolute());
System.out.println("Is it hidden? " + file.isHidden());
File file2 = new File("book.txt");
file.renameTo(file2);
System.out.println("NewName " + file2.getName());
}
}
/*
运行结果:
Does it exist? true
The file has 2108bytes
Can it be read? true
Can it be written? true
Is it a directory? false
Is it a file? true
Is it absolute? false
Is it hidden? false
NewName book.txt
*/
改名成功,依旧是原来的文件,但是因为改名需要关联一个新的File对象,所以需要再创建一个File。
1.4 文件和文件夹的创建
package FileOperation;
import java.io.File;
import java.io.IOException;
public class Demo03 {
public static void main(String[] args) throws IOException {
//E盘不存在 则报错 IOException
File f1 = new File("E:\\file01.txt");
System.out.println(f1.createNewFile());
}
}
package FileOperation;
import java.io.File;
import java.io.IOException;
public class Demo03 {
public static void main(String[] args) throws IOException {
//1.文件不存在 则创建 返回 true;文件已存在 则不创建 返回 false
File f1 = new File("test.txt");
System.out.println(f1.createNewFile());
//在上述创建文件的方法中,我们不知道文件是否已经存在,所以,在创建文件之前,我们需要判断文件是否存在
if(!f1.exists()) {
f1.createNewFile();
}
//一般而言 目录是没有后缀名 文件也可以没有后缀名
File f2 = new File("test");
if(!f2.exists()) {
f2.createNewFile();
}
//2.创建一个文件目录
/* 上面已经有一个同名没后缀名的文件 从系统角度而言 属于重名内容 不允许创建
* File dirc1 = new File("test");
* System.out.println(dirc1.mkdir());
*/
File dircFile1 = new File("project");
System.out.println(dircFile1.mkdir());
/*
* 对于系统而言 无论是文件还是目录 只要重名都不能再创建新的了
* 尽量创建文件时加上后缀名
* 尽量创建目录时不要有非法字符存在(后缀名)
*/
//3.创建多级文件目录
File dirc3 = new File("abc/def/ghi");
//mkdir一次只能创建一个一级目录
System.out.println(dirc3.mkdir());
//若果创建多级目录用mkdirs
System.out.println(dirc3.mkdirs());
}
}
1.5 文件和文件的删除
package FileOperation;
import java.io.File;
public class Demo04 {
public static void main(String[] args) {
File f1 = new File("test");
//删除一个目录 不走回收站
f1.delete();
//该目录中有多个子文件和其他子目录 不能直接删除
File f2 = new File("abc");
System.out.println(f2.delete());
//如果非得要删除的话 只能先将其内部的所有元素删除之后 再删除该目录
File f3 = new File("test.txt");
System.out.println(f3.delete());
//总而言之 删除文件 用delete
//delete只能删除单一的文件或单一的目录
//如果所删除的目录有内容 则不能删除 只能手动
}
}
1.6 子文件与文件过滤器
package FileOperation;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
public class FileFilterDemo {
public static void main(String[] args) {
//关联一个文件目录
File dirFile = new File("D:\\eclipse\\eclipse-workspace\\FileAndIOStream\\src\\FileOperation");
//查看该目录下的所有文件(包含隐藏文件)
MyFileNameFilter filter = new MyFileNameFilter();
//只能传关于名字的过滤
//list()返回的是该目录下第一层级的所有文件和目录的名字的字符串数组
String[] fileNames = dirFile.list();
//只要文件目录不为空,就遍历打印文件目录里的所有文件
if(fileNames != null) {
for (String name : fileNames) {
System.out.println(name);
}
}
MyFileFilter filter2 = new MyFileFilter();
//listFiles()返回的是该目录下第一层级的所有文件和目录的File对象的一维数组
File[] files = dirFile.listFiles(filter2);
//关于文件名 文件属性的过滤都行
if (files != null) {
for (File file : files) {
System.out.println(file.getAbsolutePath());
}
}
dirFile.list();
}
//继承FileFilter重写accept方法,过滤文件大小大于2000的文件
public static class MyFileFilter implements FileFilter{
@Override
public boolean accept(File file) {
// TODO Auto-generated method stub
return file.length() > 2000;
}
}
//继承FilenameFilter重写accpet方法,过滤文件类型
public static class MyFileNameFilter implements FilenameFilter{
@Override
public boolean accept(File dir, String name) {
// TODO Auto-generated method stub
return name.endsWith(".txt");
}
}
}
FilenameFilter过滤器中的accept方法接受两个参数,一个当前文件或文件夹所在的路径,一个是当 前文件或文件夹对象的名称。
FileFilter 过滤器中的accept方法接受一个参数,这个参数就当前文件或文件夹对象。
当我们需要过滤文件名称时就可以使用FilenameFilter这个过滤器,当我们想对当前文件或文件夹进行过滤,就可以使用FileFilter ,比如需要当前目录下的所有文件夹,就可以使用FileFilter过滤器。
思考:上述代码把需要过滤的文件名称给写死了,我们如何根据需求对文件进行过滤呐?
答:在创建我们自己定义的过滤器对象时,再明确具体的需要过滤的名称。添加一个私有的String类型的成员变量,显示初始化String,就可以按需过滤了。
1.7 文件夹内容遍历
一个目录中可能有多个文件或者文件夹,那么如果File中有功能获取到一个目录中的所有文件和文件夹,那么功能得到的结果要么是数组,要么是集合。
文件目录下的内容我们可以通过File类中的listFiles()方法获取,如果要获取目录下目录中的内容呐?如果一直进行同一操作的递归,子目录过多的话,会导致内存溢出,导致遍历无法完成。所以对于获取所有子目录中的内容的问题,有以下思路:
- 可以通过对每一个目录进行for循环,但是目录层级很多,for会死掉。
- 每遍历到一个目录,不对其进行遍历,而是先临时存储起来。 相当于把所有目录(无论同级不同级)都存储起来。
- 遍历容器时取到就是目录,然后对目录遍历即可。
- 从容器中取到目录遍历时发现内部还有目录,一样将这些目录存储到容器中。5. 只要不断的遍历这个容器就哦了。
上述思路还是有些问题:当目录特别多的时候,虽然避免递归导致栈溢出了,可是容器存放
在堆中,不停的给容器中添加目录,容器中的内容会一直增加下去,最后也会导致堆内存溢出,怎么办呢?
思考:对当前目录遍历时,把当前目录下的所有子目录存放在容器中,下次再遍历子目录时,当前目录已经没用了,就可以在容器中删除,同样我们取出容器中的子目录进行子子目录给容器中存放,那么当前的这个子目录也就没用了,这种结构正好是前面学习集合中讲过的队列结构。
package FileOperation;
import java.io.File;
import java.util.LinkedList;
public class printDir {
public static void main(String[] args) {
File file = new File("D:\\eclipse\\eclipse-workspace");
//printDirs1(file, 1);
printDirs(file);
}
//第一种方法
private static void printDirs(File dir) {
//创建一个队列
LinkedList<File> queueFiles = new LinkedList<File>();
//队列里存放的是文件目录 文件目录入队
queueFiles.offer(dir);
//文件看做一个二叉树 一层一层遍历 只要队列不为空,就对文件进行遍历
while(!queueFiles.isEmpty()) {
//出队一个文件,因为遍历完之后就要遍历下一个文件目录
File file = queueFiles.poll();
//打印文件目录的名字
System.out.println("【" + file.getName() + "】");
//返回目录文件下的所有对象,并存储到文件数组中
File[] files = file.listFiles();
//文件目录下要是有文件
if(files != null) {
//遍历并判断是文件还是文件目录
for (File file2 : files) {
if(file2.isFile()) {
//是文件,获取文件名
System.out.println(file2.getName());
}else {
//是文件目录入队
queueFiles.offer(file2);
}
}
}
}
}
//第二种方法
private static void printDirs1(File dir,int level) {
//打印这是第几层文件目录下的文件
System.out.println(getSpace(level) + "【" + dir.getName() + "】");
//获取文件目录下的文件和文件夹
File[] files = dir.listFiles();
//如果什么都没有,说明遍历完了,直接结束函数
if(files == null) {
return;
}
//遍历文件
for(File file: files) {
if(file.isFile()) {
System.out.println(getSpace(level + 1) + file.getName());
}else {
//如果文件目录下还有文件目录,调用函数对目录下的文件继续进行处理
printDirs1(file,level + 1);
}
}
}
//获取文件层数便于展示
private static String getSpace(int level) {
// TODO Auto-generated method stub
StringBuilder sb = new StringBuilder();
for (int i = 0; i < level; i++) {
sb.append("--");
}
return sb.toString();
}
}
上述代码使用到了**listFiles()**方法。
注意:在获取指定目录下的文件或者文件夹时必须满足下面两个条件:
- 指定的目录必须是存在的
- 指定的必须是目录。否则容易引发返回数组为null,出现NullPointerException
1.8 删除文件全部内容
package FileOperation;
import java.io.File;
public class Deletedir {
public static void main(String[] args) {
File file = new File("C:\\Users\\Desktop\\FileAndIOStream");
deleteDir(file);
}
private static void deleteDir(File dirFile) {
//获取文件目录下的所有文件
File[] files = dirFile.listFiles();
//如果文件目录下的文件为空,直接删除
if(files == null) {
dirFile.delete();
return;
}
//不为空则遍历文件目录下的文件
for (File file : files) {
if (file.isFile()) {
//如果是文件就删除
file.delete();
} else {
//是文件目录的话就重复这个方法
deleteDir(file);
}
}
dirFile.delete();
}
}
二、 字节流与字符流
IO–input output
一进一出 针对内存(程序)
从文件中读取输入到内存 输入流。
从内存把数据写入带文件 输出流。
流就是文件到内存的一个桥梁,数据传输的载体,是单向的。
-
按照方向
输入流 输出流
-
按照数据的类型
字节流 字符流
对于任何OS来说 文件最终都是以二进制的形式存储在硬盘里.
.txt .mp3 .avi .doc .jpeg 最终都是二进制文件.字节
.txt .java .py .cpp 这些文件,唯一存储的数据是字符
所以对于上述文件的操作,可以是字节流操作,字符流操作.
注意:字符流只能针对纯文本文件 .doc不算纯文本文件
字符流 = 字节流 + 编码表
IO它不一定非得和文件操作 但肯定和内存有关
2.1 字节流
了解了File类的相关操作之后,有了文件之后,我们可以对文件以及数据进行读写操作。
2.1.1 OutputStream与InputStream
1.OutputStream:一个抽象类,所有字节输出流的超类。输出流用于写入。
不能直接创建对象 ----通过创建子类对象来进行实例化,有以下子类:
-
ByteArrayOutputStream 字节数组(操作对象)
-
FileOutputStream 文件
-
ObjectOutputSream 对象
-
DataOutputStream 二进制数据
-
PipedOutputStream 管道
-
FilterOutputStream 过滤器
2.InputStream:一个抽象类,所有字节输入流的超类。输入流用于读取。
不能直接创建对象 ----通过创建子类对象来进行实例化,有以下子类:
- ByteArrayInputStream 字节数组
- FileInputStream 文件
- ObjectInputSream 对象
- DataInputStream 二进制数据
- PipedInputStream 管道
- FilterInputStream 过滤器
- …
Closeable 接口-- 定义关闭流的标准 close
FLushable接口 – 清空流中的内容 flush 清空
2.1.2 FileInputStream和FileOutputStream
FileOutputStream方法摘要:
常见功能:
- 输出流中定义都是写write方法。
- 操作字节数组write(byte[]),操作单个字节write(byte)。
常见功能:
- int read():读取一个字节并返回,没有字节返回-1.
- int read(byte[]): 读取一定量的字节数,并存储到字节数组中,返回读取到的字节数。
package IOoperation;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class OutputStreamDemo {
public static void main(String[] args) {
//读取图片
InputStream is = null;
//写入图片
OutputStream os = null;
try {
/*
* is = new FileInputStream(new File("C:\\Users\\Pictures\\Saved Pictures\\TATA.jpg"));
* os = new FileOutputStream("1.jpg");
* 使用read和write方法 分别进行读写操作
* int i = 0;
* read方法:从此输入流中读取一个数据字节。即一字节一字节读取
* while((i = is.read()) != -1) {
* write方法:将指定字节写入此文件输出流。
* os.write(i);
* }
*/
/*
* is = new FileInputStream("E:\\test.txt");
* os = new FileOutputStream("test.txt");
* 在使用byte类型的读写的方式 会存在 如果文件是字节类型
* 比如音频 视频 图片 -----文件完整性是能够有保证的
* 但是如果是文字 字符型的数据 那么数组当中会因为最后一次读取的时候
* Byte字节数不够1024个单位 数组当中会存有上次循环读取的数据
* 也会写入到文件中。
* byte[] bRet = new byte[1024];
* int i = 0;
* while((i = is.read(bRet)) != -1) {
* os.write(bRet);
* }
*/
//read,write方法中形参为数组时,需要注意写法,确定偏移量,以保证字符型数据文件的完整性
is = new FileInputStream("E:\\test.txt");
os = new FileOutputStream("test.txt");
byte[] bRet = new byte[1024];
int i = 0;
while((i = is.read(bRet)) != -1) {
os.write(bRet,0,i);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
try {
is.close();
os.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
注意:FileOutputStream(file)这样创建对象,会覆盖原有的文件。
那么我们在源文件中续写内容该如何操作呐?
通过查看API可以发现在FileOutputStream的构造函数中, 可以接受一个boolean类型的值,如果值true,就会在文件末位继续添加。
package IOoperation;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamDemo {
//换行
private static final String LINE_SEPERATOR = System.getProperty("line.separator");
public static void main(String[] args) {
FileOutputStream fos = null;
try {
File file = new File("test.txt");
fos = new FileOutputStream(file, true);
//追加内容到文件中
String str = LINE_SEPERATOR + "This is a test!";
//把字符转换为字节
fos.write(str.getBytes());
//一定要判断fos是否为null,只有不为null时,才可以关闭资源
if(fos != null) {
fos.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
2.1.3 IO中的异常如何处理?
IO流异常处理一般使用try catch
-
try中 我们创建对象,并且执行操作
-
catch中 我们打印异常
-
finally中 我们必须释放资源
注:
-
引用的声明一定要写在try的外面,否则作用域只在try中有效
-
如果创建对象的时候抛出异常,则引用指向的就是空
-
在执行finally的时候就会抛出空指针异常,为了避免这种状况我们加上一个判断条件。
2.1.4 ByteArrayInputStream和DataInputStream
- ByteArrayInputStrem ----主要用于处理缓冲的字节数据
- DataInputStream -----主要用于处理 处理缓冲二进制字节数据数组 还用于处理基本数据类型的数据
- Datainputstream— 实现了DataInput接口
- DataOutputStream-- 实现了DataOutput接口
package IOoperation;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
public class DataInputStreamTest {
public static void main(String[] args) {
//写入数据
ByteArrayOutputStream baos = new ByteArrayOutputStream();
/*因为DataOutputStream的构造方法中需要一个“基本数据输出流对象”
*所以在创建数据输出流之前,我们需要弄清楚,流之间的嵌套关系,并找到合适的输出流
*在API中找到了每次都需要和DataOutputStream配套使用的ByteArrayOutputStream
*/
DataOutputStream dos = new DataOutputStream(baos);
//流创建好之后,我们写入数据
try {
dos.writeDouble(3.14);
dos.writeBoolean(true);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//之后是读取数据
/*ByteArrayInputStream的构造方法中需要一个字节数组,而我们的输出流中并没有
* 通过查看api发现,ByteArrayOutputStream的方法中toByteArray()可以创建一个新分配的 byte 数组。
*/
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
DataInputStream dis = new DataInputStream(bais);
try {
//这里需要注意数据写入和读取的序列,先写小数,再写布尔类型值;读取时要一致
//如果读取时顺序颠倒,依旧能输出内容,但是内容会出现问题
System.out.println(dis.readBoolean());
System.out.println(dis.readDouble());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
2.1.4 BufferedInputStream和BufferedOutputStream
都叫做缓冲流。目的就是为了提高IO效率。缓冲大小8mb
-
FileInputStream而言
无论是read()一个字节一个字节读取,还是read(byte[])一堆字节一堆字节读取,它们都是直接从硬盘上读取数据的。
如果文件中有1000个字节的数据,每次对硬盘进行一次读取操作假设是1秒,
read() 需要 1000秒
read(byte[10]) 需要100秒 -
BufferedInputStream 自身也带一个缓冲区,并且
BufferedInputStream是其它InputStream(FileInputStream)的装饰类。
FileInputStream纯粹是从硬盘进行读取数据
,硬盘的计算速度跟不上内存的计算速度的。
如果一个一个读
或者一批一批(阙值)读
的话速度相对而言都是慢的
。
BufferedInputStream自身内部有一个缓冲区在内存中,假设缓冲区大小为500,对于文件中1000个字节的数据只需要两次IO就读到内存中了,耗时2秒
第1次 读取500个 .程序中再用byte[10]从内存缓冲区中进行读取 一次0.1秒 耗时5秒.
如果前500个数据被读取完毕之后 要进行填充,第2次 读取500个 程序中再用byte[10]从内存缓冲区中进行读取 一次0.1秒,耗时5秒总耗时12秒。 -
BufferedOutputStream 程序先将数据byte[10]写入到缓冲区中byte[500] 等到缓冲区满的时候
一次将缓冲区的内容写入到硬盘文件中1秒
阙值的注意:如果FileInputStream read(byte[500])
package IOoperation;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedInputStreamDemo {
public static void main(String[] args) {
//关联源文件
File source = new File("C:\\Users\\Videos\\海的女儿.mkv");
//关联目的文件
File copyFile = new File("copyvideo.mkv");
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//读取文件内容
fis = new FileInputStream(source);
//使用缓冲提高流读取文件速率
bis = new BufferedInputStream(fis);
//向目的文件写入文件内容,目的文件不存在,创建
fos = new FileOutputStream(copyFile);
bos = new BufferedOutputStream(fos);
//定义缓冲流每次读取的字节大小
byte[] buff = new byte[1024 * 1024];
int len = 0;
try {
while((len = bis.read(buff)) != -1) {
//保证数据的完整性
bos.write(buff, 0, len);
//属性缓存区,这样才能进入到目的文件
bos.flush();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//异常处理
if(bis != null) {
try {
//关流
bis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(bos != null) {
try {
bos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
可以自定义缓冲数组复制哦!自己定义一个数组缓冲区,读写时使用带数组的方法就可以了。
2.2 字符流
字符流 = 字节流 + 码表
字符串->二进制 编码
二进制->字符串 解码
package IOoperation;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class Demo01 {
public static void main(String[] args) throws UnsupportedEncodingException {
String s1 = "abc";
//默认编码
System.out.println(Arrays.toString(s1.getBytes()));
//以gbk的码表形式输出
System.out.println(Arrays.toString(s1.getBytes("gbk")));
//以utf-8的码表形式输出
System.out.println(Arrays.toString(s1.getBytes("utf-8")));
/*结果:
* [97, 98, 99]
[97, 98, 99]
[97, 98, 99]
*/
String s2 = "abc你好";
System.out.println(Arrays.toString(s2.getBytes()));
System.out.println(Arrays.toString(s2.getBytes("gbk")));
System.out.println(Arrays.toString(s2.getBytes("utf-8")));
/*
* [97, 98, 99, -60, -29, -70, -61]
[97, 98, 99, -60, -29, -70, -61]
[97, 98, 99, -28, -67, -96, -27, -91, -67]
*/
}
}
从上述例子可以看出:
在GBK当中,其实任何一个字符都是用两个字节存储的。也就是说“你好”在gbk当中是4字节,对应的结果 -60, -29, -70, -61。对于一个gbk当中的汉字来说,由两个字节组成 并且这两个字节不能分开对待,必须作为一个整体来操作。
为了避免用户将这种一对字节拆开操作,所以,结果是负数。
一定都是负数吗?不一定
负数是咋来的?比如一个汉字的两个字节如下
00000100 11000101
00000100 这个字节作为汉字的第一个字节 所以取负数即可
11111011 + 1 = 11111100
11000101 由于两个字节被拆开了,后面的这个字节是否是负数取决于第一位。
[97, 98, 99, -28, -67, -96, -27, -91, -67]
在UTF-8码表里 有可能1字节、2字节、3字节、4字节
英文,数字,符号 1字节
汉字 3字节
在字符串“你好”中 两个汉字 6个字节 - 6个数字
-28, -67, -96 -> 你
-27, -91, -67 -> 好
2.2.1 编码表
-
ascii: 一个字节中的7位就可以表示。对应的字节都是正数。0-xxxxxxx
-
iso8859-1:拉丁码表 latin,用了一个字节用的8位。1-xxxxxxx 负数。
-
GB2312:简体中文码表。6,7千的中文和符号。用两个字节表示。两个字节都是开头为1 两个 字节都是负数。
-
GBK:目前最常用的中文码表,2万多的中文和符号。用两个字节表示,一部分文字,第一个字节开头是1,第二字节开头是0。
-
unicode:国际标准码表:无论是什么文字,都用两个字节存储。Java中的char类型使用这个码表。char c = ‘a’;占两个字节。在Java中,字符串是按照系统默认码表来解析的。简体中文版字符串默认的码表是GBK。
-
UTF-8:基于unicode,一个字节就可以存储数据,不要用两个字节存储,而且这个码表更加的标准化,在每一个字节头加入了编码信息(api中查找)。
能识别中文的码表:GBK、UTF-8;正因为识别中文码表不唯一,涉及到了编码解码问题。
对于开发而言,常见的编码:GBK,UTF-8,ISO8859-1 。
package IOoperation;
import java.io.UnsupportedEncodingException;
public class Demo02 {
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "奇迹";
//对字符串进行编码 ---字节数组
byte[] buf1 = str.getBytes("utf-8");
for (byte b : buf1) {
System.out.print(b);
}
System.out.println();
//对字节数组解码 ---字符串
String s1 = new String(buf1, "utf-8");
String s2 = new String(buf1, "gbk");
System.out.println(s1);
System.out.println(s2);
/*
* 结果:
* -27-91-121-24-65-71
* 奇迹
* 濂囪抗
*/
}
}
由上述代码可以看出:
-
String----->byte[] 编码
-
byte[]----->String 解码
当使用getBytes对字符进行编码后,在解码的时候,指定的码表一定要和编码时的码表一致,否则会发生解码错误的现象,出现乱码。
package IOoperation;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo03 {
public static void main(String[] args) throws IOException {
File f1 = new File("temp.txt");
//默认值编码
FileOutputStream fos = new FileOutputStream(f1);
fos.write("今天星期四".getBytes());
//读取写入文件中的内容
FileInputStream fis = new FileInputStream(f1);
//使用一字节一字节读取。乱码
/*
* int b = 0;
* while((b = fis.read()) != -1) { //把字节转换成单个字符输出
* System.out.print((char) b); }
*/
//一批一批读取汉字,如果小缓冲大小设置的不到位的话 也会导致乱码问题
byte[] buf =new byte[2];
int len = 0;
while((len = fis.read(buf)) != -1) {
//保证数据完整性
System.out.print(new String(buf, 0, len));
}
fis.close();
fos.close();
}
}
2.2.2 Reader 和 Writer
通过上述程序,我们发现挡在读取拥有中文的文件时,使用的字节流在读取,那么我们读取到的都是一个一个字节。**把这些字节去查阅对应的编码表,就能够得到与之对应的字符。**通过查找API,我们发现了能够读取相应字符的功能流对象。
Reader:读取字符流的抽象超类。
与之相对的,有写字符的流对象。FileWriter中默认的是GBK
2.2.3 InputStreamReader 和 OutputStreamWriter
OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它的作用的就是,将字符串按照指定的编码表转成字节,在使用字节流将这些字节写出去。
OutputStreamWriter流对象,它到底如何把字符转成字节输出的呢?
其实在OutputStreamWriter流中维护自己的缓冲区,当我们调用OutputStreamWriter对象的write方法时,会拿着字符到指定的码表中进行查询,把查到的字符编码值转成字节数存放到OutputStreamWriter 缓冲区中。然后再调用刷新功能,或者关闭流,或者缓冲区存满后会把缓冲区中的字节数据使用字节流写到指定的文件中。
InputStreamReader 是字节流通向字符流的桥梁:它使用指定的charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
//从硬盘读取字节 将字节转为字符 转换流
public static void main(String[] args) throws IOException {
File f1 = new File("temp.txt");
FileInputStream fis = new FileInputStream(f1);
InputStreamReader isr = new InputStreamReader(fis,"gbk");
char[] cbuf = new char[100];
int len = 0;
while ((len = isr.read(cbuf)) != -1) {
System.out.println(new String(cbuf));
}
isr.close();
}
//将字符 转换为字节 然后写入到硬盘
public static void main(String[] args) throws IOException {
//赋值一个文本文件
File f1 = new File("笔记.txt");
File f2 = new File("笔记_copy.txt");
FileInputStream fis = new FileInputStream(f1);
FileOutputStream fos = new FileOutputStream(f2);
InputStreamReader isr = new InputStreamReader(fis);
OutputStreamWriter osw = new OutputStreamWriter(fos);
char[] cbuf = new char[100];
int len = 0;
while ((len = isr.read(cbuf)) != -1) {
osw.write(cbuf,0,len);
}
isr.close();
osw.close();
}
2.2.4 FileReader 和 FileWriter
继承关系:
Reader
-InputStreamRead
-FileReader 功能 = InputStreamRead + FileInputStream
Writer
-OutputStreamWriter
-FileWriter
- FileReader和FileWriter 本质上底层还是用字节流来操作。 但是自身又拥有转换的功能。所以相当于是:
FileReader 功能= InputStreamRead + FileInputStream
FileWriter 功能= OutputStreamWriter + FileOutputStream
FileReader FileWriter 不能再操作非文本文件了
其次 FileReader FileWriter 不能进行指定编码表,字符集。
package IOoperation;
import java.io.FileWriter;
import java.io.IOException;
public class Demo04 {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("temp.txt");
//这些文字都要先编码。都写入到了流的缓冲区中。
fw.write("今天肯德基疯狂周四!");
//fw.flush();
fw.close();
}
}
package IOoperation;
import java.io.FileReader;
import java.io.IOException;
public class Demo05 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("temp.txt");
int ch = 0;
while((ch = fr.read()) != -1){
//输出的字符对应的编码值
System.out.print(ch);
//输出字符本身
System.out.print((char) ch);
System.out.println();
}
}
}
/*
结果:
20170今
22825天
32943肯
24503德
22522基
30127疯
29378狂
21608周
22235四
65281!
*/
flush()和close()的区别
flush():将流中的缓冲区缓冲的数据刷新到目的地中,刷新后,流还可以继续使用。
close():关闭资源,但在关闭前会将缓冲区中的数据先刷新到目的地,否则丢失数据,然后在关闭流。流不可以使用。如果写入数据多,一定要一边写一边刷新,最后一次可以不刷新,由close完成刷新并关闭。
2.2.5 BufferedReader 和 BufferedWriter
//复制一个文本文件
FileReader fr = new FileReader("笔记.txt");
FileWriter fw = new FileWriter("笔记_copy.txt");
BufferedReader br = new BufferedReader(fr);
BufferedWriter bw = new BufferedWriter(fw);
String line = null;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
br.close();
bw.close();
}
2.3 字节流和字符流的转换
package IOoperation;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class BufferedReaderTest {
public static void main(String[] args) throws IOException {
//字节流与字符流的转换
//Reader和Writer;BufferedReader和BufferedWriter
//通常我们都以读取文件内容开始
/*
File file = new File("E:/test.txt");
FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis);
//FileReader fr = new FileReader(file);
//需要一个Reader类的对象
BufferedReader br = new BufferedReader(isr);
//需要一个Reader类的对象
BufferedWriter bWriter = new BufferedWriter(null);
*/
BufferedReader bReader = new BufferedReader(
new InputStreamReader(
new FileInputStream(
new File("E:/test.txt"))));
BufferedWriter bWriter = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(
new File("test.txt"))));
//写操作
String result = "";
StringBuffer sb = new StringBuffer();
while((result = bReader.readLine()) != null) {
//写入文件
bWriter.write(result);
bWriter.newLine();
//展示台打印
sb.append(result + "\n");
}
System.out.println(sb.toString());
bWriter.flush();
bReader.close();
bWriter.close();
}
}
2.4 流的操作规律
-
确定来源和目的
来源
:数据从哪里来 文件 网络 控制台 内存 InputStream Reader
目的
:数据区哪里 文件 网络 控制台 内存 OutputStream Writer -
确定字节流还是字符流
字符流
:纯文本数据 Reader
字节流
:非纯文本数据 InputStream -
明确数据所在的具体设备。
源设备
:
硬盘:文件 File开头。
内存:数组,字符串。
键盘:System.in;
网络:Socket
目的设备
:硬盘:文件 File开头。
内存:数组,字符串。
屏幕:System.out
网络:Socket -
确定是否需要额外功能?
转换吗?转换流。
InputStreamReader OutputStreamWriter
高效吗?缓冲区对象。
BufferedXXX