1.IO的介绍
IO,即in和out,也就是输入和输出,指应用程序和外部设备之间的数据传递,常见的外部设备包括文件、管道、网络连接。
Java 中是通过流处理IO 的,那么什么是流?
流(Stream),是一个抽象的概念,是指一连串的数据(字符或字节),是以先进先出的方式发送信息的通道。
当程序需要读取数据的时候,就会开启一个通向数据源的流,这个数据源可以是文件,内存,或是网络连接。类似的,当程序需要写入数据的时候,就会开启一个通向目的地的流。这时候你就可以想象数据好像在这其中“流”动一样。
一般来说关于流的特性有下面几点:
- 1 先进先出:最先写入输出流的数据最先被输入流读取到。
- 2 顺序存取:可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。(RandomAccessFile除外)
- 3 只读或只写:每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。
1.1 实例操作
1. FileInputStream、FileOutputStream(字节流)
字节流的方式效率较低,不建议使用
public class IOTest {
public static void main(String[] args) throws IOException {
File file = new File("D:/test.txt");
write(file);
System.out.println(read(file));
}
public static void write(File file) throws IOException {
OutputStream os = new FileOutputStream(file, true);
// 要写入的字符串
String string = "松下问童子,言师采药去。只在此山中,云深不知处。";
// 写入文件
os.write(string.getBytes());
// 关闭流
os.close();
}
public static String read(File file) throws IOException {
InputStream in = new FileInputStream(file);
// 一次性取多少个字节
byte[] bytes = new byte[1024];
// 用来接收读取的字节数组
StringBuilder sb = new StringBuilder();
// 读取到的字节数组长度,为-1时表示没有数据
int length = 0;
// 循环取数据
while ((length = in.read(bytes)) != -1) {
// 将读取的内容转换成字符串
sb.append(new String(bytes, 0, length));
}
// 关闭流
in.close();
return sb.toString();
}
}
2、BufferedInputStream、BufferedOutputStream(缓冲字节流)
缓冲字节流是为高效率而设计的,真正的读写操作还是靠FileOutputStream和FileInputStream,所以其构造方法入参是这两个类的对象也就不奇怪了。
public class IOTest {
public static void write(File file) throws IOException {
// 缓冲字节流,提高了效率
BufferedOutputStream bis = new BufferedOutputStream(new FileOutputStream(file, true));
// 要写入的字符串
String string = "松下问童子,言师采药去。只在此山中,云深不知处。";
// 写入文件
bis.write(string.getBytes());
// 关闭流
bis.close();
}
public static String read(File file) throws IOException {
BufferedInputStream fis = new BufferedInputStream(new FileInputStream(file));
// 一次性取多少个字节
byte[] bytes = new byte[1024];
// 用来接收读取的字节数组
StringBuilder sb = new StringBuilder();
// 读取到的字节数组长度,为-1时表示没有数据
int length = 0;
// 循环取数据
while ((length = fis.read(bytes)) != -1) {
// 将读取的内容转换成字符串
sb.append(new String(bytes, 0, length));
}
// 关闭流
fis.close();
return sb.toString();
}
}
3 InputStreamReader、OutputStreamWriter(字符流)
字符流适用于文本文件的读写,OutputStreamWriter类其实也是借助FileOutputStream类实现的,故其构造方法是FileOutputStream的对象
public class InputStreamReaderTest {
@Test
public void test1() throws IOException {
//参数2 指明了字符集 具体使用哪个字符集 取决于文件保存时使用的字符集
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("11.1.txt"));
char[] chars = new char[20];
int len;
while ((len = inputStreamReader.read(chars))!= -1){
String s = new String(chars,0,len);
System.out.println(s);
}
inputStreamReader.close();
}
/*
综合使用 InputStreamReader和OutputStreamWriter
*/
@Test
public void test2() throws IOException {
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("11.1.txt"),"utf-8");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("11.1_gbk.txt"), "gbk");
char[] chars = new char[20];
int len;
while ((len = inputStreamReader.read(chars))!=-1){
outputStreamWriter.write(chars,0,len);
}
outputStreamWriter.close();
inputStreamReader.close();
}
运行结果 :
4、字符流便捷类
Java提供了FileWriter和FileReader简化字符流的读写,new FileWriter等同于new OutputStreamWriter(new FileOutputStream(file, true))
注:FileWriter和FileReader在效率上不如BufferedReader和BufferedWriter
@Test
将day09下的hello.txt文件内容读入程序中 并输出到控制台
Reader说明点:
1.read()的理解:返回读入的一个字符。如果达到文件的末尾,返回-1
2.异常的处理:为了保证流资源一定可以执行关闭操作 需要使用try catch finally 来处理
3.读入的文件一定要存在 否则就会报异常
Writer说明:
1.输出操作,对应的FiLe可以不存在的。并不会报异常
2.FiLe对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件File对应的硬盘中的文件如果存在:
如果流使用的构造器是:FileWriter(file,false) / FiLeWriter(file): 覆盖原有文件
如果流使用的构造器是:FileWriter(file,true):true 是在原有文件上添加
public void testFiLeReaderFileWriter(){
FileInputStream fr = null;
FileOutputStream fw = null;
try {
//1.创建File类的对象,指明读入和写出的文件
// File file = new File("hello.txt");
// File file1 = new File("hello2.txt");
File file = new File("preview.jpg");
File file1 = new File("preview1.jpg");
//2.创建输入流和输出流的对象
fr = new FileInputStream(file);
fw = new FileOutputStream(file1);
//3.数据的读入和写出操作
byte [] bytes = new byte[5];
int len;//记录每次读入到chars数组中的字符个数
while ((len = fr.read(bytes))!=-1){
//每次写出len个字符
fw.write(bytes,0,len);
}
System.out.println("复制成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭流资源
if (fw != null)
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
if (fr != null)
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
5、BufferedReader、BufferedWriter(字符缓冲流)
可以用 long start = System.currentTimeMillis(); 方法来查看到Buffered缓冲流的效率明显的提高
public class BufferedTest {
/*
实现非文本文件的复制
*/
@Test
public void test1(){
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
//1.造文件
File file = new File("preview.jpg");
File file1 = new File("preview2.jpg");
//2.造流
//2.1造节点流
fileInputStream = new FileInputStream(file);
fileOutputStream = new FileOutputStream(file1);
//2.2造缓冲流
bufferedInputStream = new BufferedInputStream(fileInputStream);
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
//3.复制的细节 读取 写入
byte [] bytes = new byte[1024];
int len;
while ((len = bufferedInputStream.read(bytes))!= -1){
bufferedOutputStream.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源关闭
//要求:先关闭外层的流,再关闭内层的流
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedInputStream != null)
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
// //关闭外层流的同时,内层流也会自动关闭 所以可以省略关闭内层流
// fileOutputStream.close();
// fileInputStream.close();
}
}
2 IO流方法
2.1 字节流方法
字节输入流InputStream
主要方法:
-
read()
:从此输入流中读取一个数据字节。 -
read(byte[] b)
:从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。 -
read(byte[] b, int off, int len)
:从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。 -
close()
:==关闭此输入流并释放与该流关联的所有系统资源。
字节输出流OutputStream
主要方法: -
write(byte[] b)
:将 b.length 个字节从指定 byte 数组写入此文件输出流中。 -
write(byte[] b, int off, int len)
:将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。 -
write(int b)
:将指定字节写入此文件输出流。 -
close()
:关闭此输入流并释放与该流关联的所有系统资源。
2.2 字符流方法
字符输入流Reader主要方法:
-
read()
:读取单个字符。 -
read(char[] cbuf)
:将字符读入数组。 -
read(char[] cbuf, int off, int len)
: 将字符读入数组的某一部分。 -
read(CharBuffer target)
:试图将字符读入指定的字符缓冲区。 -
flush()
:刷新该流的缓冲。 -
close()
:关闭此流,但要先刷新它。
字符输出流Writer
主要方法: -
write(char[] cbuf)
:写入字符数组。 -
write(char[] cbuf, int off, int len)
:写入字符数组的某一部分。 -
write(int c)
:写入单个字符。 -
write(String str)
:写入字符串。 -
write(String str, int off, int len)
:写入字符串的某一部分。 -
flush()
:刷新该流的缓冲。 -
close()
:关闭此流,但要先刷新它。
另外,字符缓冲流还有两个独特的方法: -
BufferedWriter类newLine()
:写入一个行分隔符。这个方法会自动适配所在系统的行分隔符。 -
BufferedReader类readLine()
:读取一个文本行。
3 FileUtils工具类的使用
我们可以通过导入相关的jar包来使用FileUtiles工具类 这样的话直接用其方法就行了
简单的介绍:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
*文件操作工具类
*
* @author xiu
* @version 2017年7月31日 下午8:17:13
*/
public class FileUtil {
private FileUtil() {
}
/**
* 列出指定文件夹的内容
*
* @param dir 指定文件夹的路径
* @return 指定文件夹的内容
*/
public static String[] ls(String dir) {
File file = new File(dir);
if (file.exists()) {
return file.list();
}
return new String[0];
}
/**
* 在指定的位置创建指定的文件
*
* @param filePath 完整的文件路径
* @param mkdir 是否创建相关的文件夹
* @throws Exception
*/
public static void mkFile(String filePath, boolean mkdir) throws Exception {
File file = new File(filePath);
file.getParentFile().mkdirs();
file.createNewFile();
file = null;
}
/**
* 在指定的位置创建文件夹
*
* @param dirPath 文件夹路径
* @return 若创建成功,则返回True;反之,则返回False
*/
public static boolean mkDir(String dirPath) {
return new File(dirPath).mkdirs();
}
/**
* 写入文件(完全自定义文件格式和写入内容)
*
* @param filename 文件名(可包含路径)
* @param data 要写入的数据
* @param flag 是否追加(true追加,false不追加)
* @return boolean
*/
public static boolean write(String filename, String data, boolean flag) {
int i = filename.lastIndexOf('/');
File file = new File(filename.substring(0, i));
if (!file.exists()) {
file.mkdirs();
}
try {
FileWriter fw = new FileWriter(filename, flag);
fw.write(data);
fw.flush();
fw.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 删除某个文件或目录 2012-3-13
*
* @param filename
* 文件名或目录
* @return 0:删除成功,-1:指定文件夹不存在,-2:指定文件不存在
*/
public static int delete(String filename) {
File file = new File(filename);
if (file.isDirectory()) {
String[] dir = ls(filename);
for (int i = 0; i < dir.length; i++) {
new File(filename + "/" + dir[i]).delete();
if (file.isDirectory()) {
delete(filename + "/" + dir[i]);
}
}
if (file.delete()) {
return 0;
} else {
return -1;
}
} else if (file.isFile()) {
if (file.delete()) {
return 0;
} else {
return -2;
}
} else {
return -1;
}
}
/**
* 移动指定的文件(夹)到目标文件(夹)
* @param source 源文件(夹)
* @param target 目标文件(夹)
* @param isFolder 若为文件夹,则为True;反之为False
* @return
* @throws Exception
*/
public static boolean move(String source, String target, boolean isFolder)
throws Exception {
copy(source, target, isFolder);
if (isFolder) {
return delDir(source, true);
} else {
return delFile(source);
}
}
/**
* 删除指定的文件
*
* @param filePath 文件路径
*
* @return 若删除成功,则返回True;反之,则返回False
*
*/
public static boolean delFile(String filePath) {
return new File(filePath).delete();
}
/**
* 删除指定的文件夹
*
* @param dirPath 文件夹路径
* @param delFile 文件夹中是否包含文件
* @return 若删除成功,则返回True;反之,则返回False
*
*/
public static boolean delDir(String dirPath, boolean delFile) {
if (delFile) {
File file = new File(dirPath);
if (file.isFile()) {
return file.delete();
} else if (file.isDirectory()) {
if (file.listFiles().length == 0) {
return file.delete();
} else {
int zfiles = file.listFiles().length;
File[] delfile = file.listFiles();
for (int i = 0; i < zfiles; i++) {
if (delfile[i].isDirectory()) {
delDir(delfile[i].getAbsolutePath(), true);
}
delfile[i].delete();
}
return file.delete();
}
} else {
return false;
}
} else {
return new File(dirPath).delete();
}
}
/**
* 复制文件/文件夹 若要进行文件夹复制,请勿将目标文件夹置于源文件夹中
* @param source 源文件(夹)
* @param target 目标文件(夹)
* @param isFolder 若进行文件夹复制,则为True;反之为False
* @throws Exception
*/
public static void copy(String source, String target, boolean isFolder)
throws Exception {
if (isFolder) {
(new File(target)).mkdirs();
File a = new File(source);
String[] file = a.list();
File temp = null;
for (int i = 0; i < file.length; i++) {
if (source.endsWith(File.separator)) {
temp = new File(source + file[i]);
} else {
temp = new File(source + File.separator + file[i]);
}
if (temp.isFile()) {
FileInputStream input = new FileInputStream(temp);
FileOutputStream output = new FileOutputStream(target + "/" + (temp.getName()).toString());
byte[] b = new byte[1024];
int len;
while ((len = input.read(b)) != -1) {
output.write(b, 0, len);
}
output.flush();
output.close();
input.close();
}
if (temp.isDirectory()) {
copy(source + "/" + file[i], target + "/" + file[i], true);
}
}
} else {
int byteread = 0;
File oldfile = new File(source);
if (oldfile.exists()) {
InputStream inStream = new FileInputStream(source);
File file = new File(target);
file.getParentFile().mkdirs();
file.createNewFile();
FileOutputStream fs = new FileOutputStream(file);
byte[] buffer = new byte[1024];
while ((byteread = inStream.read(buffer)) != -1) {
fs.write(buffer, 0, byteread);
}
inStream.close();
fs.close();
}
}
}
/**
* 读取文件类容
*
* @param filename 文件名(可包含路径)
* @return 文件类容
*/
public static String read(String filename) {
try {
String text = null;
StringBuilder sb = new StringBuilder();
BufferedReader input = new BufferedReader(new FileReader(filename));
while ((text = input.readLine()) != null) {
sb.append(text);
sb.append("\n");
}
input.close();
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
/**
* 获取包下面所有类
* @param pack 文件夹路径
* @return
*/
public static Set<Class<?>> getClasses(String pack) {
// 第一个class类的集合
Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
// 是否循环迭代
boolean recursive = true;
// 获取包的名字 并进行替换
String packageName = pack;
String packageDirName = packageName.replace('.', '/');
// 定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
// 循环迭代下去
while (dirs.hasMoreElements()) {
// 获取下一个元素
URL url = dirs.nextElement();
// 得到协议的名称
String protocol = url.getProtocol();
// 如果是以文件的形式保存在服务器上
if ("file".equals(protocol)) {
//System.err.println("file类型的扫描");
// 获取包的物理路径
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
} else if ("jar".equals(protocol)) {
// 如果是jar包文件
// 定义一个JarFile
//System.err.println("jar类型的扫描");
JarFile jar;
try {
// 获取jar
jar = ((JarURLConnection) url.openConnection()).getJarFile();
// 从此jar包 得到一个枚举类
Enumeration<JarEntry> entries = jar.entries();
// 同样的进行循环迭代
while (entries.hasMoreElements()) {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/开头的
if (name.charAt(0) == '/') {
// 获取后面的字符串
name = name.substring(1);
}
// 如果前半部分和定义的包名相同
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
// 如果以"/"结尾 是一个包
if (idx != -1) {
// 获取包名 把"/"替换成"."
packageName = name.substring(0, idx).replace('/', '.');
}
// 如果可以迭代下去 并且是一个包
if ((idx != -1) || recursive) {
// 如果是一个.class文件 而且不是目录
if (name.endsWith(".class") && !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
String className = name.substring(packageName.length() + 1, name.length() - 6);
try {
// 添加到classes
classes.add(Class.forName(packageName + '.' + className));
} catch (ClassNotFoundException e) {
// log
// .error("添加用户自定义视图类错误
// 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}
} catch (IOException e) {
// log.error("在扫描用户定义视图时从jar包获取文件出错");
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return classes;
}
public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,
Set<Class<?>> classes) {
// 获取此包的目录 建立一个File
File dir = new File(packagePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
// log.warn("用户定义包名 " + packageName + " 下没有任何文件");
return;
}
// 如果存在 就获取包下的所有文件 包括目录
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
public boolean accept(File file) {
return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
}
});
// 循环所有文件
for (File file : dirfiles) {
// 如果是目录 则继续扫描
if (file.isDirectory()) {
findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
classes);
} else {
// 如果是java类文件 去掉后面的.class 只留下类名
String className = file.getName().substring(0, file.getName().length() - 6);
try {
// 添加到集合中去
// classes.add(Class.forName(packageName + '.' +
// className));
// 经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
classes.add(
Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
} catch (ClassNotFoundException e) {
// log.error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}