JAVASE IO流
前言
本文以图文并茂的形式重点记录了这一周学习Java中IO操作的心得,并配以大量练习代码。Java的IO流无外乎就是输入流和输出流,所以基础部分还是比较简单的。
简述
IO:用于处理设备上的数据的技术。设备:内存、硬盘、光盘。java中所涉及的功能对象都存储到java.io包中。
流:系统资源,windows系统本身就可以操作设备。各种语言只是使用了系统平台上的这个资源。并对外提供了各种语言自己的操作功能,这些功能最终调用的是系统资源。使用完资源一定要记住:释放。(也就是说IO一定要写finally!)
IO流也进行分类:
1:输入流(读)和输出流(写)。
2:因为处理的数据不同,分为字节流和字符流。
输入流和输出流相对于内存设备而言。
将外设中的数据读取到内存中:输入-->读
将内存的数写入到外设中:输出-->写
注意:流的操作只有两种:读和写。
字节流:处理字节数据的流对象。设备上的数据无论是图片或者dvd,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。
字符流:因为字符每个国家都不一样,所以涉及到了字符编码问题,那么GBK编码的中文用unicode编码解析是有问题的,所以需要获取中文字节数据的同时+ 指定的编码表才可以解析正确数据。为了方便于文字的解析,所以将字节流和编码表封装成对象,这个对象就是字符流。只要操作字符数据,优先考虑使用字符流体系。
流的体系因为功能不同,但是有共性内容,不断抽取,形成继承体系。该体系一共有四个基类,而且都是抽象类。
字节流:InputStream OutputStream
字符流:Reader Writer
在这四个系统中,它们的子类,都有一个共性特点:子类名后缀都是父类名,前缀名都是这个子类的功能名称。
File类
java.io.File类将文件系统中的文件和文件夹封装成了对象。提供了更多的属性和行为可以对这些文件和文件夹进行操作。这些是流对象办不到的,因为流只操作数据。设备上的数据(0、1串)最常见的存储表现形式是文件file。先学习一下文件的基本操作。
查阅API,描述文件或者文件夹(目录路径名)的类是File类。常用方法需要查表。
首先我们来演示一下File构造函数的使用。
- package ustc.lichunchun.file.demo;
- import java.io.File;
- /*
- * File对象创建过程、字段信息。
- *
- * 注意:File文件对象只能操作文件或者文件夹的属性,
- * 例如文件或文件夹的创建、删除、获取文件属性(大小、所在目录等),
- * 但我们最终建立文件的目的,是往文件里面存数据,File对象是做不了这个的。
- * 这时,我们就要用到IO流。
- */
- public class FileDemo {
- //File类已经提供了相应字段。
- //private static final String FILE_SEPARATOR = System.getProperty("file.separator");
- public static void main(String[] args) {
- //将某一个文件或者文件夹封装成了File对象。可以封装存在的文件或目录,也可以封装不存在的文件或目录。
- //注意,这里File对象封装的实际上是1.txt,至于d:\\只是作为全路径目录。
- //再比如,new File("abc\\a\\b\\c");实际上封装的是c文件夹以及其全路径目录。
- File file = new File("d:\\1.txt");
- //File(String parent, String child);这样可以将目录和文件名分开。
- File file1 = new File("d:\\", "1.txt");
- File dir = new File("d:\\");
- File file2 = new File(dir, "1.txt");
- //File f = new File("d:"+System.getProperty("file.separator")+"abc"+System.getProperty("file.separator")+"1.txt");
- //File f = new File("d:"+FILE_SEPARATOR+"abc"+FILE_SEPARATOR+"1.txt");
- File f = new File("d:"+File.separator+"abc"+File.separator+"1.txt");
- System.out.println(f);
- }
- }
File类常见方法:
1:创建。
boolean createNewFile():在指定目录下创建文件,如果该文件已存在,则不创建。而对操作文件的输出流而言,比如FileOutputStream,输出流对象已建立,就会创建文件,如果文件已存在,会覆盖。除非续写。
boolean mkdir():创建此抽象路径名指定的目录。
boolean mkdirs():创建多级目录。
2:删除。
boolean delete():删除此抽象路径名表示的文件或目录。
void deleteOnExit():在虚拟机退出时删除。
注意:在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹用delete删除。
window的删除动作,是从里往外删。注意:java删除文件不走回收站。要慎用。
3:获取。
long length():获取文件大小。
String getName():返回由此抽象路径名表示的文件或目录的名称。
String getPath():将此抽象路径名转换为一个路径名字符串。
String getAbsolutePath():返回此抽象路径名的绝对路径名字符串。
String getParent():返回此抽象路径名父目录的抽象路径名,如果此路径名没有指定父目录,则返回 null。
long lastModified():返回此抽象路径名表示的文件最后一次被修改的时间。
File.pathSeparator:返回当前系统默认的路径分隔符,windows默认为 “;”。
File.Separator:返回当前系统默认的目录分隔符,windows默认为 “\”。
4:判断。
boolean exists():判断文件或者文件夹是否存在。
boolean isDirectory():测试此抽象路径名表示的文件是否是一个目录。
boolean isFile():测试此抽象路径名表示的文件是否是一个标准文件。
boolean isHidden():测试此抽象路径名指定的文件是否是一个隐藏文件。
boolean isAbsolute():测试此抽象路径名是否为绝对路径名。
5:重命名。
boolean renameTo(File dest):可以实现移动的效果。剪切+重命名。
String[] list():列出指定目录下的当前的文件和文件夹的名称。包含隐藏文件。如果调用list方法的File 对象中封装的是一个文件,那么list方法返回数组为null。如果封装的对象不存在也会返回null。只有封装的对象存在并且是文件夹时,这个方法才有效。
注意:list(FilenameFilter filter)和listFiles(FileFilter filter)往往是和过滤器实例参数一起使用。
代码示例1:
- package ustc.lichunchun.file.demo;
- import java.io.File;
- public class FileMethodDemo {
- public static void main(String[] args) {
- /*
- * File类,常见方法。
- * 1.名字。获取名称。
- * String getName();
- * 2.大小。获取大小。
- * long length();
- * 3.类型。获取类型。
- * 没有,因为类型可以自定义。
- * 4.获取所在目录。
- * String getParent();
- */
- File file = new File("d:\\abc\\1.txt");
- String file_name = file.getName();
- System.out.println(file_name);//1.txt-->File对象封装的是1.txt所在路径(而且不是绝对路径)。
- long len = file.length();
- System.out.println(len);//0
- System.out.println(file.getParent());//d:\abc
- }
- }
- package ustc.lichunchun.file.demo;
- import java.io.File;
- import java.io.IOException;
- import java.text.DateFormat;
- import java.util.Date;
- public class FileMethodTest {
- public static void main(String[] args) throws IOException {
- /*
- * File方法 练习:
- *
- * 1.获取文件的绝对路径。
- * String getAbsolutePath();
- *
- * 2.获取文件的路径。
- * String getPath();
- *
- * 3.获取文件最后一次修改的时间。要求是x年x月x日。时间。
- * long lastModified();
- *
- * 4.文件是否是隐藏的。
- * boolean isHidden();
- *
- * 5.发现File对象封装的文件或者文件夹是可以存在的也可以是不存在的。
- * 那么不存在的可否用file的功能创建呢?
- * 创建功能。
- * boolean createNewFile();
- *
- * 删除功能。
- * boolean delete();
- *
- * 6.一个File对象封装成的文件或者文件夹到底是否存在呢?
- * 判断存在功能。
- * boolean exists();
- *
- * 7.getFreeSpace()方法是什么意思?用Demo验证。getTotalSpace()、getUsableSpace()
- * 指定分区中:未分配、总共、已分配字节数。用处:迅雷看看缓存到本地,会先判断哪个盘符剩余空间比较大。
- *
- * 8.列出可用的文件系统根。
- * file[] listRoots();
- *
- */
- //methodDemo1();
- //文件创建与删除
- File file = new File("1.txt");
- //methodDemo2(file);
- //文件夹创建与删除
- File file1 = new File("abc\\a\\b\\c");
- //methodDemo3(file1);
- //揭秘
- //methodDemo4();
- File file2 = new File("d:\\");
- //System.out.println(file2.getFreeSpace());//128448581632
- listRootsDemo();
- }
- public static void methodDemo1() {
- File file = new File("abc\\1.txt");
- String path = file.getAbsolutePath();//E:\JavaSE_code\day21e\abc\1.txt-->获取文件对象的绝对路径。即使封装的是相对的,获取到的也是绝对的,这时就是所在项目的绝对路径下。
- String path1 = file.getPath();//abc\1.txt-->获取的是file对象中的封装的路径。封装的是什么,获取到的就是什么。
- System.out.println("AbsolutePath = "+path);
- System.out.println("Path = "+path1);
- File file1 = new File("E:\\JavaSE_code\\day21e\\IO流_1.txt");
- long time = file1.lastModified();
- Date date = new Date(time);
- String str_date = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG).format(date);
- System.out.println(time);//1436701878494
- System.out.println(str_date);//2015年7月12日 下午07时51分18秒
- boolean b = file.isHidden();
- System.out.println("isHidden():" + b);//false
- }
- public static void methodDemo2(File file) throws IOException {
- //1.演示文件的创建。
- boolean b = file.createNewFile();//如果文件存在,则不创建,返回false;不存在,就创建,创建成功,返回true。
- System.out.println(b);
- //2.文件的删除。
- boolean b1 = file.delete();
- System.out.println(b1);
- //3.判断文件是否存在
- System.out.println(file.exists());
- }
- public static void methodDemo3(File file) {
- //1.演示文件夹的创建。
- //boolean b = file.mkdir();//只能创建单级目录:new File("abc")
- boolean b = file.mkdirs();//创建多级目录:new File("abc\\a\\b\\c"),或者创建单级目录:new File("abc")
- System.out.println(b);
- System.out.println("exists: "+file.exists());
- //2.演示文件夹的删除。
- boolean b1 = file.delete();//删除文件夹时,必须保证该文件夹没有内容。有内容,必须先把内容删除后,才可以删除当前文件夹。
- System.out.println("delete: "+b1);
- }
- public static void methodDemo4() throws IOException {
- //揭秘:你用什么方法创建的,它就是什么。
- //千万不要被表面现象所迷惑: abc.txt有可能是文件夹,abc有可能是文件。
- //是不是文件,不能主观判断,得用isFile()、isDirectory()判断。
- //而且在用is之前,要先判断是否存在!
- File file1 = new File("abc");
- System.out.println("file: "+file1.isFile());//false
- System.out.println("directory: "+file1.isDirectory());//false,如果一个东西不存在的情况下,不可能是文件或者文件夹。
- System.out.println("--------------");
- File file2 = new File("abc");
- boolean b2 = file2.mkdirs();
- System.out.println("mkdirs: "+b2);//true
- System.out.println("file: "+file2.isFile());//false
- System.out.println("directory: "+file2.isDirectory());//true
- System.out.println("--------------");
- File file3 = new File("edf");
- boolean b3 = file3.createNewFile();
- System.out.println("createNewFile: "+b3);//true
- System.out.println("file: "+file3.isFile());//true,文件不一定非要有扩展名
- System.out.println("directory: "+file3.isDirectory());//false
- System.out.println("--------------");
- File file4 = new File("abc.txt");
- boolean b4 = file4.mkdirs();
- System.out.println("mkdirs: "+b4);//true
- System.out.println("file: "+file4.isFile());//false
- System.out.println("directory: "+file4.isDirectory());//true,文件夹也可以包含"."
- }
- public static void listRootsDemo() {
- File[] files = File.listRoots();
- for(File file : files)
- System.out.println(file);
- }
- }
- package ustc.lichunchun.file.demo;
- import java.io.File;
- public class FileMethodTest2 {
- public static void main(String[] args) {
- /*
- * 9.获取指定文件夹中的所有文件和文件夹的名称。
- */
- File dir = new File("d:\\");
- String[] names = dir.list();//列出当前目录下的所有文件和文件夹名称,包含隐藏文件。
- //list()局限:只获取名称。
- //如果目录存在但是没有内容,会返回一个数组,但是长度为0。
- if(names != null){
- for (String name : names) {
- System.out.println(name);
- }
- }
- System.out.println("-------------------------");
- File[] files = dir.listFiles();//获取当前目录下的所有文件和文件夹的File对象,更为常用。
- for(File f : files){
- System.out.println(f.getName()+"......"+f.length());
- }
- }
- }
接下来我要介绍的是文件名过滤器和文件过滤器,它们的用途是为了获取指定目录下的指定类型文件。所采用到的设计模式属于策略设计模式,目的就是为了降低容器和过滤条件之间的耦合性。
1.文件名过滤器:FilenameFilter
它的底层源码如下:
- public String[] list(FilenameFilter filter) {
- String names[] = list();
- if ((names == null) || (filter == null)) {
- return names;
- }
- List<String> v = new ArrayList<>();
- for (int i = 0 ; i < names.length ; i++) {
- if (filter.accept(this, names[i])) {
- v.add(names[i]);
- }
- }
- return v.toArray(new String[v.size()]);
- }
- package ustc.lichunchun.filter;
- import java.io.File;
- import java.io.FilenameFilter;
- /*
- * 根据文件名称的后缀名进行过滤的过滤器。
- */
- public class FilterBySuffix implements FilenameFilter {
- private String suffix;
- public FilterBySuffix(String suffix) {
- super();
- this.suffix = suffix;
- }
- /**
- * @param name 被遍历目录dir中的文件夹或者文件的名称。
- */
- @Override
- public boolean accept(File dir, String name) {
- return name.endsWith(suffix);
- }
- }
- package ustc.lichunchun.file.demo;
- import java.io.File;
- import ustc.lichunchun.filter.FilterBySuffix;
- public class FilenameFilterDemo {
- public static void main(String[] args) {
- /*
- * 10.能不能只获取指定目录下的.java文件呢?
- * 文件名过滤器:list(FilenameFilter filter);
- */
- /*
- File dir = new File("d:\\");
- String[] names = dir.list();
- for(String name : names){
- if(name.endsWith(".java"))//-->耦合性太强。
- System.out.println(name);
- }
- */
- //文件名过滤器:让容器和过滤条件分离,降低耦合性。
- //类似于比较器,都属于策略设计模式。不要面对具体的过滤或者排序动作,我只面对接口。
- File dir = new File("d:\\");
- //传入一个过滤器。
- String[] names = dir.list(new FilterBySuffix(".java"));
- for(String name : names){
- System.out.println(name);
- }
- }
- }
- package ustc.lichunchun.filter;
- import java.io.File;
- import java.io.FilenameFilter;
- public class FilterByContain implements FilenameFilter {
- private String content;
- public FilterByContain(String content) {
- super();
- this.content = content;
- }
- @Override
- public boolean accept(File dir, String name) {
- return name.contains(content);
- }
- }
- package ustc.lichunchun.file.demo;
- import java.io.File;
- import java.io.FilenameFilter;
- import ustc.lichunchun.filter.FilterByContain;
- import ustc.lichunchun.filter.FilterBySuffix;
- public class FilenameFilterDemo2 {
- public static void main(String[] args) {
- //需求:不是获取指定后缀名的文件,而是获取文件名中包含指定字段的文件。
- File dir = new File("d:\\");
- FilenameFilter filter = new FilterBySuffix(".java");//过滤后缀名的过滤器。
- filter = new FilterByContain("Demo");//过滤内容的过滤器。
- String[] names = dir.list(filter);
- for(String name : names){
- System.out.println(name);
- }
- }
- }
2.文件过滤器:FileFilter
文件过滤器其实更为常用。因为过滤器中pathname.getName().endsWith(".java")可以实现同样的文件名过滤操作。
我再用文件过滤器的方法,实现上面文件名过滤器所示例的,过滤指定类型文件的过滤器:
- package ustc.lichunchun.filter;
- import java.io.File;
- import java.io.FileFilter;
- public class FilterBySuffix2 implements FileFilter {
- private String suffix;
- public FilterBySuffix2(String suffix) {
- super();
- this.suffix = suffix;
- }
- @Override
- public boolean accept(File pathname) {
- return pathname.getName().endsWith(suffix);
- }
- }
- package ustc.lichunchun.filter;
- import java.io.File;
- import java.io.FileFilter;
- public class FilterByFile implements FileFilter {
- @Override
- public boolean accept(File pathname) {
- return pathname.isFile();//文件过滤器。只筛选出文件,不要文件夹。
- }
- }
- package ustc.lichunchun.filter;
- import java.io.File;
- import java.io.FileFilter;
- public class FilterByDirectory implements FileFilter {
- @Override
- public boolean accept(File pathname) {
- return pathname.isDirectory();
- }
- }
- package ustc.lichunchun.file.demo;
- import java.io.File;
- import java.io.FileFilter;
- import ustc.lichunchun.filter.FilterByDirectory;
- import ustc.lichunchun.filter.FilterByFile;
- import ustc.lichunchun.filter.FilterBySuffix2;
- public class FileFilterDemo {
- public static void main(String[] args) {
- File dir = new File("d:\\");
- FileFilter filter = new FilterByFile();//过滤出当前目录下所有文件
- filter = new FilterByDirectory();//过滤出当前目录下所有文件夹
- filter = new FilterBySuffix2(".java");//过滤出当前目录下所有以指定后缀名结尾的文件和文件夹
- File[] files = dir.listFiles(filter);
- for(File file : files){
- System.out.println(file);
- }
- System.out.println("-------------------------");
- }
- }
(1)接口NumberFilter:表示过滤器接口。接口NumberFilter的抽象方法boolean is(int n)供使用者实现以设置筛选条件。
(2)类NumberArray:表示一组整数的对象(可用int型数组作为其成员来存放一组整数)。类NumberArray的方法print(0输出满足过滤器NumberFilter设置的筛选条件的部分整数。类NumberArray的方法setFilter(NumberFilter nf)用于设置过滤器。
(3)类Filter:表示具体的过滤器,实现接口NumberFilter,实现is()方法用于设置筛选条件,这里要求过滤出偶数。
- package ustc.lichunchun.filter;
- public class Test {
- public static void main(String[] args) {
- int[] array = {1,2,3,4,5,6,7,8,9};
- NumberFilter nf = new Filter();
- NumberArray na = new NumberArray(array, nf);
- na.print();
- }
- }
- class NumberArray {
- private int[] array;
- public NumberArray(int[] array, NumberFilter nf) {
- super();
- this.array = array;
- this.nf = nf;
- }
- private NumberFilter nf;
- public void print(){
- for(int i : array){
- if(nf.is(i)){
- System.out.print(i+ " ");
- }
- }
- System.out.println();
- }
- public void setFilter(NumberFilter nf){
- this.nf = nf;
- }
- }
- class Filter implements NumberFilter {
- @Override
- public boolean is(int n) {
- if(n % 2 == 0)
- return true;
- return false;
- }
- }
- interface NumberFilter {
- boolean is(int n);
- }
递归及其IO应用
说完了上面的过滤器以后,我们已经可以实现过滤出指定目录下的特定类型文件了。但是现在我又有一个需求:遍历指定目录下的内容(包含子目录中的内容)。这该如何处理呢?再解决这个问题之前,我们下来简单回顾一下算法课中所学的递归思想。
递归:就是函数自身调用自身。
什么时候用递归呢?当一个功能被重复使用,而每一次使用该功能时的参数不确定,都由上次的功能元素结果来确定。简单说:功能内部又用到该功能,但是传递的参数值不确定。(每次功能参与运算的未知内容不确定)。
递归的注意事项:
1:一定要定义递归的条件。
2:递归的次数不要过多。容易出现 StackOverflowError 栈内存溢出错误。
其实递归就是在栈内存中不断的加载同一个函数。
递归示例:
- package ustc.lichunchun.recursion;
- public class RecursionDemo {
- public static void main(String[] args) {
- /*
- * 递归使用时,一定要定义条件。
- * 注意:递归次数过多,会出现栈内存溢出。
- */
- //show();
- int sum = getSum(3);
- System.out.println("sum = "+sum);//3+((3-1)+(3-1-1))
- int sum1 = getSum(999999);//java.lang.StackOverflowError
- }
- public static int getSum(int num){
- if(num == 1)
- return 1;
- return num + getSum(num - 1);
- }
- /*这也是递归,并且会溢出。
- public static void show(){
- method();
- }
- public static void method(){
- show();
- }
- */
- }
- package ustc.lichunchun.file.test;
- import java.io.File;
- public class GetAllFilesTest {
- public static void main(String[] args) {
- /*
- * 遍历指定目录下的内容(包含子目录中的内容)
- *
- * 递归:函数自身调用自身,不断进栈。函数内部又使用到了该函数功能。
- * 什么时候使用呢?
- * 功能被重复使用,但是每次该功能使用参与运算的数据不同时,可以考虑递归方式解决。
- *
- */
- File dir = new File("d:\\JavaSE_code");
- getAllFiles(dir);
- }
- public static void getAllFiles(File dir){
- System.out.println("dir: "+dir);
- //1.获取该目录的文件对象数组
- File[] files = dir.listFiles();
- //2.对数组进行遍历
- if(files != null){//windows一些文件夹是不可以被java访问到的。
- for(File file : files){
- if(file.isDirectory()){
- getAllFiles(file);
- }else{
- System.out.println("file: "+file);
- }
- }
- }
- }
- }
- package ustc.lichunchun.file.test;
- import java.io.File;
- public class DeleteDirTest {
- public static void main(String[] args) {
- /*
- * 基于递归,做一个练习:删除一个带内容的文件夹。必须从里往外删。
- */
- File dir = new File("d:\\JavaSE_code");
- //System.out.println(dir.delete());//false,有内容的文件夹不能直接删
- deleteDir(dir);
- }
- public static void deleteDir(File dir){
- //1.列出当前目录下的文件以及文件夹。
- File[] files = dir.listFiles();
- //2.对该数组进行遍历。
- for(File file : files){
- //3/判断是否有目录。如果有,继续使用该功能遍历,递归!如果不是文件夹,直接删除。
- if(file.isDirectory()){
- deleteDir(file);
- }else{
- System.out.println(file + ":" +file.delete());
- }
- }
- //4.删除不含文件了的文件夹
- System.out.println(dir + ":" +dir.delete());
- }
- }
File类综合练习
获取一个想要的指定文件的集合。获取JavaSE_code下(包含子目录)的所有的.java的文件对象。并存储到集合中。
思路:
1.既然包含子目录,就需要递归。
2.在递归的过程中,需要过滤器。
3.凡是满足条件的,都添加到集合中。
- package ustc.lichunchun.test;
- import java.io.File;
- import java.io.FileFilter;
- import java.util.ArrayList;
- import java.util.List;
- import ustc.lichunchun.filter.FilterBySuffix2;
- public class Test {
- public static void main(String[] args) {
- /*
- * 需求:获取一个想要的指定文件的集合。获取JavaSE_code下(包含子目录)的所有的.java的文件对象。并存储到集合中。
- *
- * 思路:
- * 1.既然包含子目录,就需要递归。
- * 2.在递归的过程中,需要过滤器。
- * 3.凡是满足条件的,都添加到集合中。
- */
- File dir = new File("e:\\JavaSE_code");
- List<File> list = fileList(dir, ".java");
- for(File file : list){
- System.out.println(file);
- }
- }
- /**
- * 定义一个获取指定过滤器条件的文件的集合。
- */
- public static List<File> fileList(File dir, String suffix){
- //1.定义集合
- List<File> list = new ArrayList<File>();
- //2.定义过滤器。
- FileFilter filter = new FilterBySuffix2(suffix);
- /*匿名内部类也可以,不过不建议这么做。
- FileFilter filter = new FileFilter(){
- @Override
- public boolean accept(File pathname) {
- return pathname.getName().endsWith(suffix);
- }
- };*/
- getFileList(dir, list, filter);
- return list;
- }
- /**
- * 对指定目录进行递归。
- *
- * 多级目录下,都要用到相同的集合和过滤器,那么不要在递归方法中定义,而是不断地进行传递。
- *
- * @param dir 需要遍历的目录
- * @param list 用于存储符合条件的File对象
- * @param filter 接收指定的过滤器
- */
- public static void getFileList(File dir, List<File> list, FileFilter filter){
- //1.通过ListFiles方法,获取dir当前下的所有的文件和文件夹对象。
- File[] files = dir.listFiles();
- //2.遍历该数组。
- for(File file : files){
- //3.判断是否是文件夹。如果是,递归。如果不是,那就是文件,就需要对文件进行过滤。
- if (file.isDirectory()){
- getFileList(file, list, filter);
- }else{
- //4.通过过滤器对文件进行过滤。
- if(filter.accept(file)){
- list.add(file);
- }
- }
- }
- }
- }
其中所用到的文件过滤器上面有,这里就不单独再列出来了。
字节流File文件对象只能操作文件或者文件夹的属性,例如文件或文件夹的创建、删除、获取文件属性(大小、所在目录等),我们最终建立文件的目的,是往文件里面存数据,File对象是做不了这个的。这时,我们就要用到IO流。
InputStream:是表示字节输入流的所有类的超类。
|--FileInputStream:从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。
用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。
|--FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。
|--BufferedInputStream:该类实现缓冲的输入流。
|--DataInputStreamStream:操作基本数据类型值的流。
|--ObjectInputStream:对象的序列化。
|--PipedInputStream:管道输出流是管道的发送端。
|--SequenceInputStream:序列流。
|--ByteArrayInputStream:操作内存数组。关闭动作无效。
|--System.in:键盘录入。
OutputStream:此抽象类是表示输出字节流的所有类的超类。
|--FileoutputStream:文件输出流是用于将数据写入File或FileDescriptor的输出流。
注意处理IO异常。续写和换行。
|--FilterOutputStream:此类是过滤输出流的所有类的超类。
|--BufferedOutputStream:该类实现缓冲的输出流。
|--PrintStream:字节打印流,保证数值的表现形式不变,实现自动刷新和换行。
|--DataOutputStream:操作基本数据类型值的流。
|--ObjectOutputStream:对象的反序列化。
|--PipedOutputStream:管道输入流应该连接到管道输出流。
|--ByteArrayOutputStream:操作内存数组。关闭动作无效。
|--System.out:控制台打印到屏幕上。
FileOutputStream
将数据写入到文件中,使用字节输出流:FileOutputStream。
在演示字节输出流之前,有以下三点需要注意:
1.输出流所关联的目的地,如果不存在,会自动创建。如果存在,则替换并覆盖。(这与File对象,如果存在、创建失败有所区别)
2.底层流资源使用完以后一定要记得释放资源。也即IO一定要写finally。
3.一定要在释放资源前先判断输出流对象是否为空。因为try中创建输出流对象失败,则fos依然是null,但是空指针没法调用close()函数释放资源,这回导致抛出NullPointerException异常。
下面演示一下创建字节输出流对象、调用输出流的写功能的代码。
- package ustc.lichunchun.bytestream;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- public class FileOutputStreamDemo {
- public static void main(String[] args) throws IOException {
- /*
- * 将数据写入到文件中。
- * 使用字节输出流。
- * FileOutputStream。
- */
- File dir = new File("tempfile");
- if(!dir.exists()){
- dir.mkdir();
- }
- //1.创建字节输出流对象。用于操作文件,在对象初始化时,必须明确数据存储的目的地。
- //输出流所关联的目的地,如果不存在,会自动创建。如果存在,则替换并覆盖。(这与File对象,如果存在、创建失败有所区别)
- FileOutputStream fos = new FileOutputStream("tempfile\\fos.txt");
- //2.调用输出流的写功能。
- //String str = "abcde";
- //byte[] buf = str.getBytes();
- fos.write("abcde".getBytes());
- //3.释放资源。
- fos.close();
- }
- }
- package ustc.lichunchun.bytestream;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- public class IOExceptionDemo {
- /*
- * IO异常的处理方式:IO一定要写finally!
- */
- public static void main(String[] args) {
- File dir = new File("tempfile");
- if(!dir.exists()){
- dir.mkdir();
- }
- FileOutputStream fos = null;//如果try中内容失败,fos还是null,所以finally要先判断。
- try {
- fos = new FileOutputStream("tempfile\\fos.txt");
- fos.write("abcdefg".getBytes());
- } catch (IOException e) {
- System.out.println(e.toString() + "---");
- } finally {
- if (fos != null) {// 一定要在释放资源前先判断!
- try {
- fos.close();
- } catch (IOException e) {
- throw new RuntimeException("关闭失败" + e);//不要把异常抛给main函数、并让主函数声明、虚拟机处理!
- }
- }
- }
- }
- }
- package ustc.lichunchun.bytestream;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- public class NewlineDemo {
- private static final String LINE_SEPARATOR = System.getProperty("line.separator");
- public static void main(String[] args) {
- /*
- * 续写和换行。
- *
- * Linux换行符是"\n"
- * Windows换行符是"\r\n"
- * System.getProperty("line.separator")
- */
- File dir = new File("tempfile");
- if(!dir.exists()){
- dir.mkdir();
- }
- FileOutputStream fos = null;
- try{
- fos = new FileOutputStream("tempfile\\fos.txt",true);//传入true实现续写。
- String str = LINE_SEPARATOR + "abc";
- fos.write(str.getBytes());
- }catch(IOException e){
- System.out.println(e.toString()+"--");
- }finally{
- if(fos != null){
- try{
- fos.close();
- }catch(IOException e){
- throw new RuntimeException(""+e);
- }
- }
- }
- }
- }
那如何将已有文件的数据读取出来呢?既然是读,使用InputStream,而且是要操作文件,FileInpuStream。
为了确保文件一定在读之前是存在的,可以先将字符串路径封装成File对象。
下面演示创建文件字节读取流对象、逐个读取并打印文本文件中的字节。
- package ustc.lichunchun.bytestream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- public class FileInputStreamDemo {
- public static void main(String[] args) throws IOException {
- /*
- * 将已有文件的数据读取出来。
- * 既然是读,使用InputStream
- * 而且是要操作文件,FileInpuStream。
- */
- //为了确保文件一定在读之前是存在的,将字符串路径封装成File对象。
- File file = new File("tempfile\\fos.txt");
- if(!file.exists()){
- throw new RuntimeException("要读取的文件不存在");
- }
- //创建文件字节读取流对象时,必须明确与之关联的数据源。
- FileInputStream fis = new FileInputStream(file);
- //调用读取流对象的读取方法。read();
- /*
- int by1 = fis.read();
- System.out.println("by1 = "+by1);//97
- int by2 = fis.read();
- System.out.println("by2 = "+by2);//98
- int by3 = fis.read();
- System.out.println("by3 = "+by3);//99
- int by4 = fis.read();
- System.out.println("by4 = "+by4);//-1
- int by5 = fis.read();
- System.out.println("by5 = "+by5);//-1
- */
- int by = 0;
- while((by = fis.read()) != -1){
- System.out.println(by);
- }
- //关闭资源。
- fis.close();
- }
- }
所以我建议使用下面的这第二种字节流读取方式:创建一个缓冲区字节数组,大小自定义,
然后调用FileInputStream的read(byte[])方法,这样一来,效率会提升不少。
建议这里介绍的三种方法中,选择此种方法。当然,最好是用BufferedInputStream,这我稍后便会阐述。
- package ustc.lichunchun.bytestream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- public class FileInputStreamDemo2 {
- private static final int DEFAULT_SIZE = 1024*1024*2;//2MB 缓冲区
- public static void main(String[] args) {
- //演示第二种读取方式。read(byte[]); --> 第二种方式较好!
- File file = new File("tempfile\\fos.txt");
- if(!file.exists()){
- throw new RuntimeException("要读取的文件不存在");
- }
- FileInputStream fis = null;
- try{
- fis = new FileInputStream(file);//流与文件关联
- //创建一个缓冲区字节数组。
- byte[] buf = new byte[DEFAULT_SIZE];//缓冲区大小一般设置为1024的整数倍。
- //调用read(byte[])方法
- /*
- int len = fis.read(buf);//len记录的是往字节数组里存储的字节个数
- System.out.println(len + "..." + new String(buf,0,len));//2...ab
- int len1 = fis.read(buf);
- System.out.println(len1 + "..." + new String(buf,0,len1));//1...c
- int len2 = fis.read(buf);
- System.out.println(len2 + "..." + new String(buf));//-1...cb
- */
- int len = 0;
- while((len = fis.read(buf)) != -1){
- System.out.println(new String(buf,0,len));
- }
- }catch(IOException e){
- //一般将异常信息写入到日志文件中,进行记录。
- }finally{
- if(fis != null){
- try{
- fis.close();
- }catch(IOException e){
- //一般可以throw new RuntimeException异常。或者将异常信息写入到日志文件中,进行记录。
- }
- }
- }
- }
- }
- package ustc.lichunchun.bytestream;
- import java.io.FileInputStream;
- import java.io.IOException;
- public class FileInputStreamDemo3 {
- public static void main(String[] args) throws IOException {
- FileInputStream fis = new FileInputStream("tempfile\\fos.txt");
- System.out.println(fis.available());//可以获取与之关联文件的字节数。可以理解为file.length();
- byte[] buf = new byte[fis.available()];//创建了一个和文件大小一样的缓冲区,刚刚好。不建议用。
- fis.read(buf);
- String s = new String(buf);
- System.out.println(s);
- fis.close();
- }
- }
思路:
读取源数据,将数据写到目的中。用到了流,操作设备上的数据。
读,用到输入流;写,用到输出流。而且操作的还是文件。需要用到字节流中操作文件的流对象。
- package ustc.lichunchun.copy;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- public class CopyTextTest {
- public static void main(String[] args) throws IOException {
- /*
- * 需求:复制一个文件。
- * 思路:
- * 读取源数据,将数据写到目的中。
- * 用到了流,操作设备上的数据。
- * 读,用到输入流;写,用到输出流。
- * 而且操作的还是文件。需要用到字节流中操作文件的流对象。
- */
- copyText();
- }
- public static void copyText() throws IOException {
- //1.创建一个输入流和源数据相关联。
- FileInputStream fis = new FileInputStream("复制文本文件图解.bmp");
- //2.创建一个输出流,并通过输出流创建一个目的。
- FileOutputStream fos = new FileOutputStream("tempfile\\io_copy.bmp");
- //读一个,写一个。-->这种方式非常不好,效率太低,千万别用此方法。
- int by = 0;
- while((by = fis.read()) != -1){
- fos.write(by);
- }
- fos.close();
- fis.close();
- }
- }
- package ustc.lichunchun.copy;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- public class CopyTextByBufTest {
- public static void main(String[] args) {
- copyTextByBuf();
- }
- public static void copyTextByBuf() {
- FileInputStream fis = null;
- FileOutputStream fos = null;
- try{
- fis = new FileInputStream("tempfile\\fos.txt");
- fos = new FileOutputStream("tempfile\\copy_fos.txt");
- //创建缓冲区
- byte[] buf = new byte[1024];//1KB,这就是缓冲区.
- //定义记录字符个数的变量
- int len = 0;
- //循环读写
- while((len = fis.read(buf)) != -1){
- fos.write(buf, 0, len);
- }
- }catch(IOException e){
- //异常日志。
- }finally{
- if(fos != null){
- try {
- fos.close();
- } catch (IOException e) {
- //异常日志。
- }
- }
- if(fis != null){
- try {
- fis.close();
- } catch (IOException e) {
- //异常日志。
- }
- }
- }
- }
- }
前面在FileInputStream小节中,我已经介绍了利用自定义缓冲区实现高效的字节流读取。
java也考虑到了这一点,在底层将缓冲区封装成了对象,实际上就是在一个类中封装了数组,
对流所操作的数据进行缓存。缓冲区的作用就是为了提高操作数据的效率。
这样可以避免频繁的在硬盘上寻道操作。缓冲区创建时,必须有被缓冲的流对象与之相关联。
原理图解:
需求:利用缓冲区完成复制图片的例子。
- package ustc.lichunchun.bytestream.buffer;
- import java.io.BufferedInputStream;
- import java.io.BufferedOutputStream;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- public class CopyPicByBufferDemo {
- public static void main(String[] args) throws IOException {
- /*
- * java将缓冲区封装成了对象,实际上就是在一个类中封装了一个数组,对流所操作的数据进行缓存。
- * 缓冲区的作用就是为了提高操作数据的效率。这样可以避免频繁的在硬盘上寻道操作。
- * 缓冲区创建时,必须有被缓冲的流对象。
- *
- * 利用缓冲区完成复制图片的例子。
- */
- copyPicByBuffer();
- }
- public static void copyPicByBuffer() throws IOException {
- //演示缓冲区。
- //1.创建具体的流对象。
- FileInputStream fis = new FileInputStream("tempfile\\1.jpg");
- //2.让缓冲区与指定流相关联。
- //对流中的数据进行缓冲。位于内存中的缓冲区的数据读写速度远远大于硬盘上的读写。
- BufferedInputStream bufis = new BufferedInputStream(fis);//缓冲区默认读8MB字节
- FileOutputStream fos = new FileOutputStream("tempfile\\copy_1.jpg");
- BufferedOutputStream bufos = new BufferedOutputStream(fos);
- byte[] buf = new byte[1024];
- int len = 0;
- while((len = bufis.read(buf)) != -1){
- bufos.write(buf, 0, len);//使用缓冲区的写入方法将数据先写入到缓冲区中。
- bufos.flush();//将缓冲区的数据刷新到底层目的地中。(即使不写,缓冲区满了,java也会自动刷新 )
- }
- //关闭缓冲区,其实关闭的就是被缓冲的流对象。
- bufos.close();
- bufis.close();
- }
- }
在计算机中,无论是文本,还是图片、mp3、视频等,所有数据最终都是以字节形式存在。
字节流:能操作以字节为单位的文件的流对象。所以字节流能操作计算机上的一切数据。
- package ustc.lichunchun.readcn;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- public class ReadCNDemo {
- public static void main(String[] args) throws IOException {
- /*
- * 字节流操作中文数据。
- *
- * String --> String.getbyte() --> byte[] --> FileOutputStream.write(byte[])
- * 这里我们使用的是String类的getBytes()方法按平台默认字符集,编码字符串为字节数组,
- * 用以将中文字符串按字节流输出到硬盘文件中,并以字节形式存储。
- *
- * byte[] --> new String(byte[]) --> String --> System.out.println()
- * 为了在控制台能够打印出中文字符,而不是逐个字节打印出int值,
- * 我们又使用String类的String(byte[] bytes,int offset,int length)构造函数按平台默认字符集,解码字节数组为字符串。
- *
- * FileInputStream --> read() --> FileOutputStream --> write()
- * 如果只是将中文文本文件拷贝一份,或者复制其他类型的媒体文件诸如图片音视频等,我们则不必关心各种类型文件所使用的各种编码方式,
- * 只需要通过输入字节流逐个读取硬盘上文件的字节,然后在通过输出字节流输出到相应目的地文件中即可,相应的特定软件会自行解码并打开的。
- *
- * BufferedXxxStream --> 为了高效。
- *
- * 在计算机中,无论是文本,还是图片、mp3、视频等,所有数据最终都是以字节形式存在。
- * 字节流:能操作以字节为单位的文件的流对象。
- * 所以字节流能操作计算机上的一切数据。
- * 而字符流只能操作以字符为单位的文本数据。
- *
- * 注意:
- * windows简体中文系统下,默认中文编码表是GBK,其中编码中文时,是两个字节对应一个中文字符。
- */
- //writeCNText();
- readCNText();
- }
- public static void writeCNText() throws IOException {
- FileOutputStream fos = new FileOutputStream("tempfile\\cn.txt");
- fos.write("你A好".getBytes());//按照默认编码表GBK编码,将String编码为byte[]。(System.getProperty("file.encoding")获取)
- //getBytes():使用平台的默认字符集将此 String编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
- fos.close();
- }
- public static void readCNText() throws IOException {
- FileInputStream fis = new FileInputStream("tempfile\\cn.txt");
- /*
- //这里对中文文本文件逐个字节读取,并打印到控制台,肯定是不行的,编码方式决定了不可以这样。
- int by = fis.read();
- System.out.println(by);//196
- int by1 = fis.read();
- System.out.println(by1);//227
- int by2 = fis.read();
- System.out.println(by2);//65
- int by3 = fis.read();
- System.out.println(by3);//186
- int by4 = fis.read();
- System.out.println(by4);//195
- int by5 = fis.read();
- System.out.println(by5);//-1
- */
- //读取中文,按照字节形式。但是一个中文GBK码表中是两个字节。
- //而字节流的read方法一次只读一个字节。如何可以在控制台获取到一个中文呢?
- //别读一个就操作,多读一些存起来,再操作。存到字节数组中,将字节数组转成字符串就哦了。
- //因为String类有一个构造函数可以通过使用平台的默认字符集解码指定的 byte数组,构造一个新的 String。
- byte[] buf = new byte[1024];
- int len = 0;
- len = fis.read(buf);
- String s = new String(buf,0,len);//将字节数组转成字符串,而且是按照默认的编码表(GBK)进行解码。
- System.out.println(s);
- /*
- //字节是硬盘文件存储基本单位,所以复制文件问题中我不需关心媒体或者中文字符到底几个字节,
- //我就逐个字节往另一个相同类型文件里存,至于不同编码表,那是打开特定文件的软件的问题,与字节流操作无关。
- FileOutputStream fos = new FileOutputStream("tempfile\\copy_cn.txt");
- int by = 0;
- while((by = fis.read())!=-1){
- fos.write(by);
- }
- //字节流引发的小问题:
- //用字节流读取中文太费劲,如果中英文穿插,中文2字节,英文1字节,
- //如果每次只读一个中文字符new byte[2],然后我就转成字符串打印,
- //"你"字符可以打印出来,但是A和"好"字符的前一半连在一起出来,就出错了。
- //也许你会说,我可以加一个判断中英文编码的条件,但是这样做太麻烦。
- //像上面我们做的那种打印中文文本的程序,需要读取字节数据,再自己调用String构造函数解码,也太麻烦,并且如何解码也不知道。
- //所以我们就考虑用新的流:字符流。
- byte[] buf = new byte[2];
- int len = 0;
- while((len = fis.read(buf)) != -1){
- System.out.println(new String(buf,0,len));
- }
- */
- fis.close();
- }
- }
正如上面这段代码最后一段的注释中所说,对于纯文本数据的文件,用字节流操作中英文数据时由于编码问题,不同语言的字符对应的字节数不同,会显得比较繁琐。这时我们就该考虑字符流了。但是在这之前,我先把调研到的编码相关背景知识总结分享一下。
编码表
在计算机中,无论是文本,还是图片、mp3、视频等,所有数据最终都是以字节形式存在。字节流:能操作以字节为单位的文件的流对象。所以字节流能操作计算机上的一切数据。文本数据、媒体数据等,什么数据字节流都可以搞定。而字符流只能操作以字符为单位的文本数据。
主要的编码表有:ASCII、ISO8859-1、GB2312、GBK、GB18030、Unicode、UTF-8(标识头信息)等。如果想要获取到当前系统使用的默认编码表,可以使用如下代码获取系统平台默认字符集:System.getProperty("file.encoding")--> GBK。
ASCII:美国标准信息交换码,0打头,用一个字节的后7位表示,读一个字节。
ISO8859-1:欧洲码表,用一个字节的8位表示,1打头代表拉丁文,读一个字节。
GB2312:中文编码表,两个字节都是1打头,读两个字节
GBK:扩展版中文编码表,第一个字节1打头,读两个字节10101011 01010110 打头都是1,读到1打头,就读两个字节,再去查GBK表。
Unicode:国际标准码,重新编排码表,统一规定所有文字都是两个字节,Java语言使用的就是unicode。弊端:能用一个字节表示的,都用两个字节装,浪费空间。
UTF-8:对Unicode优化,能用一个字节表示的就不用两个字节,最多用三个字节来表示一个字符。读完相应若干字节后,再去查表。01111010 单字节字符,0打头,读完单字节就去查表;11001010 10101010 两个字节字符,110打头第一个字节,10打头第二个字节,读完两个字节再去查表;11100101 10101001 10010001三个字节字符,1110打头第一个字节,10打头第二、三个字节,读完三个字节再去查表。
字符流为什么不能复制图片?
字符流读完字节数据后,并没有直接往目的地里面写,而是去查表了。但是读到的媒体性字节数据,都有自身千变万化的编码方式,在码表里没有对应内容。那么它就会在表的未知字符区域随便找一些数据写到目的地中,这时,目的数据和源数据就不一致,就不能图片编辑器解析为图片。所以,字符流不能用来操作媒体文件。
编解码详述
编码:字符串 --> 字节数组。(默认都是按照windows系统本地的编码GBK存储的)
解码:字节数组 --> 字符串。
代码示例:
- package ustc.lichunchun.encode;
- import java.io.IOException;
- import java.io.UnsupportedEncodingException;
- public class EncodeDemo {
- public static void main(String[] args) throws IOException {
- /*
- * 字符串-->字节数组:编码。
- * 字节数组-->字符串:解码。
- *
- * 字符串的编码,默认都是按照windows系统本地的编码GBK存储的。
- * 获取平台默认字符集:System.getProperty("file.encoding");
- * (注:字符类型char,在java底层是以unicode码完成的,作为中间转换码,可以忽略不用考虑。)
- *
- * 你好:
- * GBK: -60 -29 -70 -61 一个中文两个字节,每个字节打头的都是1,所以都是负数。
- * UTF-8: -28 -67 -96 -27 -91 -67
- *
- * 1.如果你编码错了,解不出来。
- * 2.如果编对了,解错了,有可能还有救。
- * 再次按错误的码表编码,获取原字节,然后再选择另一种码表解码即可。
- *
- * 应用:tomcat服务器提供的Web服务使用的是iso8859-1在服务器端编解码。
- */
- test1();//编解码示例
- //test2();//有时有救
- //test3();//有时没救
- //test4();//移动联通问题
- }
- public static void test1() throws UnsupportedEncodingException {
- String str = "你好";
- //编码。
- //byte[] buf = str.getBytes();//使用平台默认字符集gbk编码
- //byte[] buf = str.getBytes("gbk");
- byte[] buf = str.getBytes("utf-8");
- //printBytes(buf);
- //解码。
- //String s1 = new String(buf);//使用平台默认字符集gbk解码
- //String s1 = new String(buf, "gbk");
- String s1 = new String(buf, "utf-8");
- System.out.println("s1 = "+s1);
- }
- public static void test2() throws IOException {
- String str = "你好";
- byte[] buf = str.getBytes("gbk");//11000100 11100011 10111010 11000011
- String s1 = new String(buf, "iso8859-1");//iso都是单字节,所以没有改变原字节。
- System.out.println("s1 = "+s1);//????
- byte[] buf2 = s1.getBytes("iso8859-1");//获取原字节:11000100 11100011 10111010 11000011
- String s2 = new String(buf2,"gbk");
- System.out.println("s2 = "+s2);//你好
- }
- public static void test3() throws IOException {
- String str = "你好";//"嘻嘻"、"谢谢"
- byte[] buf = str.getBytes("gbk");
- String s1 = new String(buf, "utf-8");//没有识别出来。以?替代。
- byte[] buf1 = s1.getBytes("gbk");
- System.out.println("s1 = "+s1);//???、????、ππ
- byte[] buf2 = s1.getBytes("utf-8");//获取原字节。只有"谢谢"可以得到。
- printBytes(buf2);//-17 -65 -67 -17 -65 -67 -17 -65 -67
- //-17 -65 -67 -17 -65 -67 -17 -65 -67 -17 -65 -67
- //-48 -69 -48 -69
- String s2 = new String(buf2,"gbk");
- System.out.println("s2 = "+s2);
- /*
- 见API文档关于utf-8修改版的编码规范。
- 你好:
- gbk编码:11000100 11100011 10111010 11000011
- utf-8解码:???
- utf-8编码:11101111 10111111 10111101 11101111 10111111 10111101 11101111 10111111 10111101
- 嘻嘻:
- gbk编码:11001110 11111011 11001110 11111011
- utf-8解码:????
- utf-8编码:11101111 10111111 10111101 11101111 10111111 10111101 11101111 10111111 10111101 11101111 10111111 10111101
- 谢谢:
- gbk编码:11010000 10111011 11010000 10111011
- utf-8解码:ππ
- utf-8编码:11010000 10111011 11010000 10111011
- 所以最后再对utf-8编码后的字节,用gbk解码,"你好"、"嘻嘻"会出现乱码。
- */
- }
- public static void test4() throws IOException {
- String str = "联通";
- byte[] buf = str.getBytes("gbk");
- for(byte b : buf){
- System.out.println(Integer.toBinaryString(b&255));
- }
- /*
- 11000001
- 10101010
- 11001101
- 10101000
- 问题:
- 在桌面上创建两个txt文本文件,1.txt写上"移动",2.txt写上"联通"。
- 发现再次打开1.txt完好,2.txt出现乱码。这是为什么呢?
- 回答:
- 记事本保存时,会按照windows平台默认GBK编码文本文件并保存。(ANSI也是本地默认字符集的意思)
- 这可以通过2.txt文本文件大小为4个字节得知。所以编码没有问题。
- 但是通过上面的小程序可以发现,"联通"的二进制码形式上符合utf-8的编码风格规范,
- 所以记事本再次打开时,会按照utf-8的格式解码这个文本文件,所以会出现乱码。
- */
- }
- public static void printBytes(byte[] buf) {
- for(byte b : buf){
- System.out.print(b+" ");
- }
- System.out.println();
- }
- }
上述代码示例中的test2()函数,我们有如下的处理过程:
这种原理在实际中,也有应用,比如tomcat服务器端,就是有这样的转换:
练习:按照字节数截取一个字符串。"abc你好"如果截取到半个中文,舍弃。比如截取4字节,abc,截取5个字节abc你。定义功能实现。(友情提示:GB2312编码的一个中文是两个字节,而且两个字节的最高位都是1,也就是说是一个负数。)
思路:
1.提示告诉我中文两个字节都是负数。
2.判断截取的最后一个字节是否是负数。如果不是,直接截取;如果是,就往回判断,前一个是否是负数。并记录住负数的个数。如果连续的负数有奇数个,舍弃最后一个字节;如果连续的负数是偶数个,不舍弃。
- package ustc.lichunchun.encode;
- import java.io.IOException;
- public class Test {
- public static void main(String[] args) throws IOException {
- /*
- * 按照字节数截取一个字符串。"abc你好"如果截取到半个中文,舍弃。比如截取4字节,abc,截取5个字节abc你。
- * 定义功能实现。
- * 友情提示:GB2312编码的一个中文是两个字节,而且两个字节的最高位都是1,也就是说是一个负数。
- *
- * 思路:
- * 1.提示告诉我中文两个字节都是负数。
- * 2.判断截取的最后一个字节是否是负数。
- * 如果不是,直接截取。
- * 如果是,就往回判断,前一个是否是负数。并记录住负数的个数。如果连续的负数有奇数个,舍弃最后一个字节。
- * 如果连续的负数是偶数个,不舍弃。
- *
- * 拓展1:GBK扩容后,中文既有负数,又有正数。它只保证第一个字节是负数,第二个字节不保证。此程序依然适用。
- * 拓展2:如果将字符串编码成utf-8格式,咋办?这时,一个中文3个字节。
- */
- //字符串转成字节数组。
- String str = "a琲bc你好d琲e";
- byte[] buf = str.getBytes("utf-8");
- for(int i = 1; i <= buf.length; i++){
- String temp = cutStringByCount2(str,i);
- System.out.println("截取"+i+"个字节是:"+temp);
- }
- }
- public static String cutStringByCount1(String str, int len) throws IOException {
- //1.将字符串转成字符数组。因为要判断截取的字节是否是负数,所以要先有字节。
- byte[] bytes = str.getBytes("gbk");
- //2.定义计数器,记录住负数的个数。
- int count = 0;
- //3.对字节数组进行遍历。应该从截取长度的最后一个字节开始判断,并往回判断。
- for(int x = len-1; x >= 0; x--){
- //4.在遍历过程中,只要满足负数就进行计数。只要不是负数,直接结束遍历。
- if(bytes[x]<0){
- count++;
- }else{
- break;
- }
- }
- //5.对遍历后,计数器的值进行判断,奇数,就舍弃最后字节并将字节数组转成字符串。
- //偶数,不舍弃,将字节数组转成字符串。
- if(count%2 == 0)
- return new String(bytes, 0, len);
- else
- return new String(bytes, 0, len-1);
- }
- public static String cutStringByCount2(String str, int len) throws IOException {
- byte[] bytes = str.getBytes("utf-8");
- int count = 0;
- for(int x = len-1; x >= 0; x--){
- if(bytes[x] < 0){
- count++;
- }else{
- break;
- }
- }
- if(count%3 ==0)
- return new String(bytes, 0, len, "utf-8");
- else if(count%3 == 1)
- return new String(bytes, 0, len-1, "utf-8");
- else
- return new String(bytes, 0, len-2, "utf-8");
- }
- }
字符流
字符流只能用来操作字符数据--文本。但是由于字符流封装了编码部分,所以操作字符比较方便。字符流=字节流+编码表。
Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[],int,int)和 close()。
|--BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
|--LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int)和 getLineNumber(),
它们可分别用于设置和获取当前行号。
|--InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset读取字节并将其解码为字符。
它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
|--FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。
要自己指定这些值,可以先在 FileInputStream上构造一个InputStreamReader。
|--CharArrayReader:没有调用系统流,只是操作内存中的数组。
|--StringReader:没有调用系统流,只是操作内存中的数组。
Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[],int,int)、flush()和 close()。
|--BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
|--OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset将要写入流中的字符编码成字节。
它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
|--FileWriter:用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。
要自己指定这些值,可以先在 FileOutputStream上构造一个OutputStreamWriter。
|--PrintWriter:字符打印流,保证数值的表现形式不变,可以对字节流、字符流自动刷新并换行。
|--CharArrayWriter:没有调用系统流,只是操作内存中的数组。
|--StringWriter:没有调用系统流,只是操作内存中的数组。
注意:只要是输出字符流,都有查表后的缓冲区。所以它的write()方法后面一定要跟随flush()刷新操作!(即使没写但运行通过,那也是因为close()封装了flush方法)
转换流
通过编码表的演示,读者应该很容易发现针对纯文本文件,用字符流的方式处理更为方便。那就很自然的考虑到,我如何才能将字节流转换成字符流呢?下面我就来介绍一下字节和字符,二者之间转换的桥梁——转换流。
转换流:
字节-->字符:解码(看不懂的-->看得懂的):InputStreamReader-->字节通向字符的桥梁,将读到的若干字节进行解码。
字符-->字节:编码(看得懂的-->看不懂的):OutputStreamWriter-->字符通向字节的桥梁,将字符编码成若干字节。
记住:只要是字符输出流,都有查表后的缓冲区。所以它的write()方法后面一定要跟随flush()刷新操作!
缓冲的原因:
每次调用 write方法都会导致在给定字符(或字符集)上调用编码转换器,在写入底层输出流之前,得到的这些字节将在缓冲区中累积。中文最终变成字节才能出去,中文变成什么字节呢?识别中文的码表不止一个,没有指定,会按默认码表GBK。这意味着,会把中文先临时存储起来,去查相应的码表,再把查码表得到的字节存起来缓冲一下,再刷新写出去。之前学的字节流,是因为不用进行查码表、缓冲转换,字节该什么样就什么样,直接读写即可。现在需要拿着字符数据去查码表,把查到的字节数据存起来,再写出去。所以,有了缓冲区,就得做刷新。
转换流代码示例:
- package ustc.lichunchun.charstream;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.OutputStreamWriter;
- public class TransStreamDemo {
- public static void main(String[] args) throws IOException {
- /*
- * 需求:通过字符流读取中文数据。
- *
- * 字符流 = 字节流 + 编码表。
- *
- * Java内置的码表是Unicode编码。见encode包有关编码解码的代码示例。
- *
- * 转换流:
- * 字节-->字符:解码(看不懂的-->看得懂的):InputStreamReader
- * 字符-->字节:编码(看得懂的-->看不懂的):OutputStreamWriter
- */
- //字节通向字符的桥梁,将读到的若干字节进行解码。
- //readCnText();
- //字符通向字节的桥梁,将字符编码成若干字节。
- writeCNText();
- }
- public static void readCnText() throws IOException {
- //1.操作字节流的字符流对象,必须先有字节流。
- FileInputStream fis = new FileInputStream("tempfile\\cn.txt");
- //2.建立字节向字符的桥梁。(解码)
- InputStreamReader isr = new InputStreamReader(fis);
- int ch = isr.read();//注意这里read()读取的是单个字符,所以要强转(char)int,才可以打印在控制台
- System.out.println((char)ch);//(char)20320 = '你'
- int ch1 = isr.read();
- System.out.println((char)ch1);//(char)65 = 'A'
- int ch2 = isr.read();
- System.out.println((char)ch2);//(char)22909 = '好'
- int ch3 = isr.read();
- System.out.println(ch3);//(char)-1 = '?' , 虚拟机返回-1就代表到达文件末尾 了。
- isr.close();
- }
- public static void writeCNText() throws IOException {
- //1.创建字节流对象。
- FileOutputStream fos = new FileOutputStream("tempfile\\GBK.txt");
- //2.创建字符通向字节的桥梁。
- OutputStreamWriter osw = new OutputStreamWriter(fos);
- //3.使用osw的write方法直接写中文字符串。因为需要拿着字符数据去查表,所以写入字节数据前,都会存储到缓冲区中。
- osw.write("你A好");
- //4.需要刷新该字符流的缓冲区。将查表得到的字节数据写到fos流中,然后通过Windows底层资源写入到GBK.txt中。
- //osw.flush();
- //5.关闭此流前,会先刷新一下。
- osw.close();
- /*
- close()底层实现:
- void close(){
- flush();
- close();
- }
- */
- }
- }
flush()刷新完,流可以继续使用;
close()刷新完,流直接关闭,流结束了,无法再用。再用,会报Stream closed异常。
转换流的好处:可以指定编码表。
什么时候使用转换流呢?
1.操作文本数据。
2.需要指定编码表。
下面我们使用不同的编码表来演示转换流:
- package ustc.lichunchun.charstream;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.OutputStreamWriter;
- public class TransStreamDemo2 {
- public static void main(String[] args) throws IOException {
- /*
- * 使用不同的编码表演示转换流。
- *
- * 字节 -->字符: InputStreamReader
- * 字符 -->字节:OutputStreamWriter
- *
- */
- //writeText();
- readText();
- }
- public static void writeText() throws IOException {
- FileOutputStream fos = new FileOutputStream("tempfile\\u8.txt");
- OutputStreamWriter osw = new OutputStreamWriter(fos);//使用默认的编码表GBK。4字节。
- osw = new OutputStreamWriter(fos, "UTF-8");//6字节。
- osw.write("你好");//记住会有查表、缓冲区的动作
- osw.close();
- }
- public static void readText() throws IOException {
- FileInputStream fis = new FileInputStream("tempfile\\u8.txt");
- InputStreamReader isr = new InputStreamReader(fis);//默认码表GBK。
- isr = new InputStreamReader(fis, "UTF-8");
- int ch1 = isr.read();
- System.out.println("ch1 = "+(char)ch1);//浣--> 你
- int ch2 = isr.read();
- System.out.println("ch2 = "+(char)ch2);//犲--> 好
- int ch3 = isr.read();
- System.out.println("ch3 = "+ch3);//ソ--> -1
- isr.close();
- }
- }
二者是转换流的子类,专门用于操作文本文件的字符流对象。
FileWriter --> 用来写入字符文件的便捷类。
FileReader--> 用来读取字符文件的便捷类。
局限性:
1.只能操作纯文本文件。
2.只能使用默认编码。
由字符流的体系结构图我们可以清楚地看到,FileReader类继承自InputStreamReader转换流类。理由是:想要操作文本文件,必须要进行编码转换,而编码转换动作转换流都完成了,所以操作文件的流对象只要继承自转换流就可以读取一个字符了。但是子类有一个局限性,就是子类中使用的编码是固定的,是本机默认的编码表,对于简体中文版的系统默认码表是GBK。
- FileReader fr = new FileReader("a.txt");
- InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");
如果需要制定码表,必须用转换流。
转换流 = 字节流+编码表。
转换流的子类File = 字节流 + 默认编码表。
凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。
- package ustc.lichunchun.charstream;
- import java.io.FileReader;
- import java.io.FileWriter;
- import java.io.IOException;
- public class SubTransStreamDemo {
- public static void main(String[] args) throws IOException {
- /*
- * 转换流的子类,专门用于操作文本文件的字符流对象。
- * FileWriter --> 用来写入字符文件的便捷类。
- * FileReader --> 用来读取字符文件的便捷类。
- */
- //writeText();
- readText();
- }
- public static void writeText() throws IOException {
- //1.创建一个用于操作文件的字符输出流对象。
- FileWriter fw = new FileWriter("tempfile\\fw.txt");//内部使用了默认的码表。而且只能操作文件。
- //等效于:
- //FileOutputStream fos = new FileOutputStream("tempfile\\fw.txt");
- //OutputStreamWriter osw = new OutputStreamWriter(fos);
- fw.write("你好");//记住会查表、缓冲区.
- fw.close();
- }
- public static void readText() throws IOException {
- FileReader fr = new FileReader("tempfile\\fw.txt");
- //等效于:
- //FileInputStream fis = new FileInputStream("tempfile\\fw.txt");
- //InputStreamReader isr = new InputStreamReader(fis);
- int ch = 0;
- while((ch = fr.read()) != -1){
- System.out.println((char)ch);//发现一次读取一个字符比较慢,接下来我们考虑使用缓冲区读取字符数组。
- }
- fr.close();
- }
- }
练习:使用自定义字符流缓冲区,复制文本文件示例。
注意:
1.字节流使用的缓冲区是字节数组,字符流使用的缓冲区是字符数组。
2.仅为复制文件,建议使用通用类型的字节流;如果是纯文本文件,并且有多余操作,建议使用字符流。
- package ustc.lichunchun.charstream;
- import java.io.FileReader;
- import java.io.FileWriter;
- import java.io.IOException;
- public class SubTransStreamDemo2 {
- public static void main(String[] args) throws IOException {
- /*
- * 使用字符流缓冲区,复制文本文件示例。
- *
- * 字节流使用的缓冲区是字节数组,
- * 字符流使用的缓冲区是字符数组。
- *
- * 如果仅仅是为了复制,建议使用字节流。
- * 但是一旦涉及到中文字符的操作,用字符流较好,
- * 比如在while循环体内对读到的文本字符数据的"你"字替换为"我"字,字节流就办不到了。
- *
- */
- copyText();
- }
- public static void copyText() throws IOException {
- //1.明确数据源,定义字符读取流和数据源关联。
- FileReader fr = new FileReader("IO流_2.txt");
- //2.明确数据目的,定义字符输出流,创建存储数据的目的。
- FileWriter fw = new FileWriter("tempfile\\copy_2.txt");
- //3.创建自定义缓冲区。
- char[] buf = new char[1024];//char占两个字节,所以声明了一个2KB的数组。
- int len = 0;
- while((len = fr.read(buf)) != -1){
- fw.write(buf, 0, len);
- }
- fw.close();
- fr.close();
- }
- }
BufferedWriter、BufferedReader
BufferedXxx缓冲字符流存在的好处是:为了高效,并且有readLine()、newLine()方法。首先演示一下演示字符流的缓冲区读写操作。
- package ustc.lichunchun.charstream.buffer;
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.FileReader;
- import java.io.FileWriter;
- import java.io.IOException;
- public class CharStreamBufferDemo {
- public static void main(String[] args) throws IOException {
- /*
- * 演示字符流的缓冲区。
- * BufferedReader
- * BufferedWriter
- *
- * BufferedXxx缓冲字符流存在的好处是:
- * 为了高效,并且有readLine()、newLine()方法。
- */
- //writeTextByBuffered();
- readTextByBuffered();
- }
- public static void writeTextByBuffered() throws IOException {
- //1.明确目的。
- FileWriter fw = new FileWriter("tempfile\\bufw.txt");
- //2.创建缓冲区对象。明确要缓冲的流对象。
- BufferedWriter bufw = new BufferedWriter(fw);
- /*
- bufw.write("abc");//写入到缓冲区。
- bufw.write("\r\nhello");
- 换行的简便写法:newLine(),为什么给封装了呢?
- 因为文本文件相比于媒体文件最大的特点就是换行,文本体现的形式可以一行一行的体现,
- 其他类型文件不具备,所以把换行操作封装了。
- bufw.newLine();//System.getProperty("line_separator");
- */
- for(int x = 1; x <= 4; x++){
- bufw.write(x + "abc");
- bufw.newLine();
- bufw.flush();
- }
- bufw.close();
- }
- public static void readTextByBuffered() throws IOException {
- FileReader fr = new FileReader("tempfile\\bufw.txt");
- BufferedReader bufr = new BufferedReader(fr);
- //因为只有文本具备行的特性,所以又有了一个只针对文本的新方法:readLine();不包含终止符'\n'、'\r'。
- //问题:test.txt中有几个字符?答案:9 --> \r \n两个字符。
- /*
- String line1 = bufr.readLine();
- System.out.println(line1);//1abc
- String line2 = bufr.readLine();
- System.out.println(line2);//2abc
- String line3 = bufr.readLine();
- System.out.println(line3);//3abc
- String line4 = bufr.readLine();
- System.out.println(line4);//4abc
- String line5 = bufr.readLine();
- System.out.println(line5);//null-->和以前返回-1不一样了。
- */
- String line = null;
- while((line = bufr.readLine()) != null){
- System.out.println(line);
- }
- bufr.close();
- }
- }
思路:读取键盘录入,不建议使用Scanner类,因为Scanner = 流 + 正则表达式,它的方法都是按照某种规则在读取数据。System.in对应的是字节流对象,而用户通过键盘输入的是文本数据,所以转换成字符流比较合适,通过桥梁。一次读取键盘录入的一行文本,应该使用下面这两句。
- BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
- bufr.readLine();
练习1:建立指定文件的清单文件。将指定目录下的指定文件(包含子目录)的绝对路径写入到一个文件中,该文件就作为指定文件的清单文件。
其中所用到的文件过滤器,上面已经有提及并解决,这里只把过滤器代码贴出来,方便参阅。
- package ustc.lichunchun.test;
- import java.io.File;
- import java.io.FileFilter;
- public class FilterBySuffix implements FileFilter {
- private String suffix;
- public FilterBySuffix(String suffix) {
- super();
- this.suffix = suffix;
- }
- @Override
- public boolean accept(File pathname) {
- return pathname.getName().endsWith(suffix);
- }
- }
- package ustc.lichunchun.test;
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.File;
- import java.io.FileFilter;
- import java.io.FileReader;
- import java.io.FileWriter;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.List;
- public class Test {
- public static void main(String[] args) throws IOException {
- /*
- * 练习1:建立指定文件的清单文件。将指定目录下的指定文件(包含子目录)的绝对路径写入到一个文件中。
- * 该文件就作为指定文件的清单文件。
- * (并统计了目前.java文件的代码行数。)
- */
- File dir = new File("e:\\JavaSE_code");
- List<File> list = fileList(dir, ".java");
- //将集合中符合条件的文件对象的绝对路径写入到一个目标文件中。
- File destFile = new File("javalist.txt");
- write2File(list,destFile);
- System.out.println("目前Java代码量:"+countLines(destFile)+" 行!");
- }
- public static void getFileList(File dir, List<File> list, FileFilter filter){
- File[] files = dir.listFiles();
- for(File file : files){
- if(file.isDirectory()){
- getFileList(file, list, filter);
- }else{
- if(filter.accept(file)){
- list.add(file);
- }
- }
- }
- }
- public static List<File> fileList(File dir, String suffix){
- List<File> list = new ArrayList<File>();
- FileFilter filter = new FilterBySuffix(suffix);
- getFileList(dir, list, filter);
- return list;
- }
- public static void write2File(List<File> list, File destFile) throws IOException {
- //1.需要流对象,既然是写入字符,就用字符流缓冲区。
- BufferedWriter bufw =null;
- try{
- bufw = new BufferedWriter(new FileWriter(destFile));
- //2.遍历集合。
- for(File file : list){
- bufw.write(file.getAbsolutePath());
- bufw.newLine();
- bufw.flush();
- }
- }finally{
- if(bufw != null){
- try {
- bufw.close();
- } catch (IOException e) {
- throw new RuntimeException("关闭失败");
- }
- }
- }
- }
- //统计已经写了的代码行数。
- public static int countLines(File destFile) throws IOException {
- int count = 0;
- BufferedReader bufr = null;
- try{
- bufr = new BufferedReader(new FileReader(destFile));
- String filename = null;
- while((filename = bufr.readLine()) != null){
- count += countFileLines(filename);
- }
- }finally{
- if(bufr != null){
- try {
- bufr.close();
- } catch (Exception e) {
- throw new RuntimeException("关闭失败");
- }
- }
- }
- return count;
- }
- public static int countFileLines(String filename) throws IOException {
- int lineCount = 0;
- BufferedReader bufr = null;
- try{
- bufr = new BufferedReader(new FileReader(filename));
- String line = null;
- while((line = bufr.readLine()) != null){
- lineCount++;
- }
- }finally{
- if(bufr != null){
- try {
- bufr.close();
- } catch (Exception e) {
- throw new RuntimeException("子文件关闭失败");
- }
- }
- }
- return lineCount;
- }
- }
思路:
1.使用键盘录入技术。
2.操作的是学生信息,信息很多,需要将信息封装成学生对象。
3.总分由高到低,需要排序,需要对学生对象中的总分排序。需要将多个学生对象进行容器存储。哪个容器呢?TreeSet集合。
4.将容器中的学生对象的信息写入到文件中。
使用到的学生类如下:
- package ustc.lichunchun.test.domain;
- public class Student implements Comparable<Student>{
- private String name;
- private int ma,cn,en;
- private int sum;
- public Student() {
- super();
- }
- public String getName() {
- return name;
- }
- public Student(String name, int ma, int cn, int en) {
- super();
- this.name = name;
- this.ma = ma;
- this.cn = cn;
- this.en = en;
- this.sum = ma+cn+en;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getMa() {
- return ma;
- }
- public void setMa(int ma) {
- this.ma = ma;
- }
- public int getCn() {
- return cn;
- }
- public void setCn(int cn) {
- this.cn = cn;
- }
- public int getEn() {
- return en;
- }
- public void setEn(int en) {
- this.en = en;
- }
- public int getSum() {
- return sum;
- }
- public void setSum(int sum) {
- this.sum = sum;
- }
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + cn;
- result = prime * result + en;
- result = prime * result + ma;
- result = prime * result + ((name == null) ? 0 : name.hashCode());
- result = prime * result + sum;
- return result;
- }
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- Student other = (Student) obj;
- if (cn != other.cn)
- return false;
- if (en != other.en)
- return false;
- if (ma != other.ma)
- return false;
- if (name == null) {
- if (other.name != null)
- return false;
- } else if (!name.equals(other.name))
- return false;
- if (sum != other.sum)
- return false;
- return true;
- }
- @Override
- public int compareTo(Student o) {
- int temp = this.sum - o.sum;
- return temp==0?this.name.compareTo(o.name):temp;
- }
- }
1.对于读取键盘录入操作,一定要定义结束标记。(当然,实际开发中,不会要求键盘录入,数据都是网络获取的。)
2.关闭键盘录入须知:如果后面不再使用键盘录入,是可以关闭的;如果后面还要使用,就不要关闭,继续通过System.in就可以获取。因为System.in流是“一次性”的,一旦关闭,本次程序就无法再次使用。
3.由于TreeSet按照默认比较器定义大小从小到大比较,所以需要使用Collections工具类的reverseOrder()方法将比较器逆序。
- package ustc.lichunchun.test.tool;
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.File;
- import java.io.FileWriter;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.util.Comparator;
- import java.util.Set;
- import java.util.TreeSet;
- import ustc.lichunchun.test.domain.Student;
- public class GetInfoTool {
- /**
- * 获取所有学生对象集合,按照学生对象的自然排序。
- * @throws IOException
- */
- //重载。
- public static Set<Student> getStudent() throws IOException{
- return getStudent(null);
- }
- /**
- * 获取所有学生对象集合,按照指定的比较器排序。
- * @param comp
- * @return
- * @throws IOException
- */
- public static Set<Student> getStudent(Comparator<Student> comp) throws IOException{
- //创建一个容器存储学生对象。
- Set<Student> set = null;
- //如果比较器存在,就创建带有比较器的对象。
- if(comp != null){
- set = new TreeSet<Student>(comp);
- }else{
- set = new TreeSet<Student>();
- }
- //1.键盘输入。
- BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
- //2.获取键盘录入信息。
- String line = null;
- while((line = bufr.readLine())!=null){//readLine()是阻塞式方法,线程停在那没有动,等待键盘录入。
- //键盘录入要加结束标记。
- if("over".equals(line)){
- break;
- }
- //因为录入的数据是有规律的。可以通过指定的规则进行分割。
- String[] strs = line.split(",");
- //将数组中的元素封装成对象。
- Student stu = new Student(strs[0],Integer.parseInt(strs[1])
- ,Integer.parseInt(strs[2])
- ,Integer.parseInt(strs[3]));
- //将学生对象存储到集合中。
- set.add(stu);
- }
- //关闭键盘录入须知:如果后面不再使用键盘录入,是可以关闭的;如果后面还要使用,就不要关闭,继续通过System.in就可以获取。
- //因为System.in流是一次性的,一旦关闭,本次程序就无法再次使用。
- //bufr.close();
- return set;
- }
- /**
- * 将集合中的学生信息写入到文件中。
- * @param set
- * @param destFile
- * @throws IOException
- */
- public static void write2File(Set<Student> set, File destFile) throws IOException{
- BufferedWriter bufw = null;
- try{
- bufw = new BufferedWriter(new FileWriter(destFile));
- //遍历集合。
- for(Student stu : set){
- bufw.write(stu.getName()+"\t"+stu.getSum());
- bufw.newLine();
- bufw.flush();
- }
- }finally{
- if(bufw!=null){
- try {
- bufw.close();
- } catch (Exception e) {
- throw new RuntimeException("文件关闭失败");
- }
- }
- }
- }
- }
- package ustc.lichunchun.test;
- import java.io.File;
- import java.io.IOException;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.Set;
- import ustc.lichunchun.test.domain.Student;
- import ustc.lichunchun.test.tool.GetInfoTool;
- public class Test1 {
- public static void main(String[] args) throws IOException {
- /*
- * 练习2:键盘录入多名学生的信息,格式:姓名,数学成绩,语文成绩,英语成绩。
- * 按总分由高到低,将学生的信息进行排列到文件中。
- *
- * 思路:
- * 1.使用键盘录入技术。
- * 2.操作的是学生信息,信息很多,需要将信息封装成学生对象。
- * 3.总分由高到低,需要排序,需要对学生对象中的总分排序。需要将多个学生对象进行容器存储。
- * 哪个容器呢?TreeSet集合。
- * 4.将容器中的学生对象的信息写入到文件中。
- */
- //创建一个逆序的比较器。
- Comparator<Student> comp = Collections.reverseOrder();
- //使用操作学生信息的工具类。
- Set<Student> set = GetInfoTool.getStudent(comp);
- File destFile = new File("tempfile\\info.txt");
- GetInfoTool.write2File(set, destFile);
- }
- }
阶段性总结1:IO流四大体系脉络整理
File: IO技术用于操作设备上的数据,而数据最常见的体现方式是文件。
先了解了文件的操作。创建、删除、存在、隐藏、获取...。
需求:怎么操作文件中的数据呢?
使用IO流对象。而且文件数据都是字节存在。学习了可以操作文件的字节流。
InputStream
|--FileInputStream
OutputStream
|--FileoutputStream
为了提高操作效率。引入缓冲区。
InputStream
|--FileInputStream
|--FilterInputStream
|--BufferedInputStream
OutputStream
|--FileoutputStream
|--FilterOutputStream
|--BufferedOutputStream
发现,文件数据中,媒体文件字节流没问题,但是对于文本文件,比如想要操作文件中的中文数据时,字节流只能操作字节,需要我们字节解码成字符,麻烦。所以就到API找对象,就发现了字符流中有字节和字符的桥梁对象,传说中的转换流。
Reader
|--InputStreamReader:字节-->字符。
Writer
|-OutputStreamWriter:字符-->字节。
它们的出现解决了中文的编码转换问题。为了便捷操作字符文件。找到了转换流的子类,但是它有局限性,只能操作文件,而且是默认编码。如果不操作文件,而且编码不是默认的,需要使用转换流。
Reader
|--InputStreamReader:字节-->字符。
|--FileReader
Writer
|-OutputStreamWriter:字符-->字节。
|--FileWriter
为了提高字符流的操作效率。引入字符流的缓冲区。
Reader
|--InputStreamReader:字节-->字符。
|--FileReader
|--BufferedReader:readLine();
Writer
|--OutputStreamWriter:字符-->字节。
|--FileWriter
|--BufferedWriter:newLine();
通过前面这么长的讲述,不仅介绍了字节流、字符流,还分别利用了相应的缓冲区流对象进行高效读写。那么,java中IO流的缓冲区原理到底是怎样的呢?
练习:用户登录注册案例--IO版
代码基本和集合框架的那篇博客中提到的一致,只是用户登录注册的具体操作实现类,有所不同。在这里,我们的需求是,将已注册的用户名和密码信息持久化的存储到文件中。代码如下:
- package ustc.lichunchun.dao.impl;
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.FileReader;
- import java.io.FileWriter;
- import java.io.IOException;
- import ustc.lichunchun.dao.UserDao;
- import ustc.lichunchun.pojo.User;
- /**
- * 这是用户操作的具体实现类--(IO版)
- *
- * @author 李春春
- * @version V1.1
- */
- public class UserDaoImpl implements UserDao {
- //为了保证文件一加载就创建
- private static File file = new File("user.txt");
- //静态代码块,随着类的加载而加载,且只加载一次
- static {
- try {
- file.createNewFile();
- } catch (IOException e) {
- System.out.println("创建文件失败");
- // e.printStackTrace();
- }
- }
- @Override
- public boolean isLogin(String username, String password) {
- boolean flag = false;
- BufferedReader bufr = null;
- try{
- bufr = new BufferedReader(new FileReader(file));
- String line = null;
- while((line = bufr.readLine())!=null){
- //用户名=密码
- String[] datas = line.split("=");
- if(datas[0].equals(username) && datas[1].equals(password)){
- flag=true;
- break;
- }
- }
- }catch(FileNotFoundException e){
- System.out.println("用户登录找不到信息所在的文件");
- //e.printStackTrace();
- }catch(IOException e){
- System.out.println("用户登录失败");
- //e.printStackTrace();
- }finally{
- if(bufr!=null){
- try {
- bufr.close();
- } catch (IOException e) {
- System.out.println("用户登录释放资源失败");
- //e.printStackTrace();
- }
- }
- }
- return flag;
- }
- @Override
- public void regist(User user) {
- /*
- * 为了让注册的数据能够有一定的规则,我就自己定义了一个规则:用户名=密码
- */
- BufferedWriter bufw = null;
- try{
- bufw = new BufferedWriter(new FileWriter(file,true));
- bufw.write(user.getUsername()+"="+user.getPassword());
- bufw.newLine();
- bufw.flush();
- }catch(IOException e){
- System.out.println("用户注册失败");
- //e.printStackTrace();
- }finally{
- if(bufw!=null){
- try {
- bufw.close();
- } catch (IOException e) {
- System.out.println("用户注册释放资源失败");
- //e.printStackTrace();
- }
- }
- }
- }
- }