程序员程序员首页投稿首页投稿(暂停使用,暂停投稿)

Java复习之IO流(下)

2017-10-26  本文已影响196人  maoqitian

上一篇文章传送门:
Java复习之IO流(上)


上一篇文章中,我们在提到 IO 流的概念时说到 IO 流分为两大类,一个是字节流,我们已经在上一篇文章中做了复习,而本篇文章的 IO流复习就从另一大类字符流来开始。

什么是字符流

上一篇文章中我们说过字符流一般只能操作的是存文本数据,而字节流可以操作很多类型数据(文本,图片,音频,视频等)

FileReader 和FileWriter

上一篇文章中已经提到过字符流的父类是Reader 和Writer,但是他们都是接口,不能直接new出对象,当我们要操作文本中的字符,字符流中给我提供了两个类FileReader 和FileWriter,他们的操作和字节流的FileInputStream 和 FileOuputStream基本是一样的。


/**
 * 
 * @author 毛麒添
 * FileReader 读取字符 操作的是项目根目录中给的文件
 */

public class Demo_FileReader {

    public static void main(String[] args) {
        FileReader fr=null;
        try {
             fr=new FileReader("aaa.txt");
            int a;
            while((a=fr.read())!=-1){
                //FileReader。read()得到的是对应的码表值,将其强转就可以得到相应的字符
                //汉字字符对应两个字节一般以负数形势表现,底层读取判定是负数则一次读取两个字节,也就解释了为什么可以读取一个字符
                System.out.print((char)a);
            }
            fr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

/**
 * 
 * @author 毛麒添
 * 输出字符 操作的是项目根目录中给的文件
 */
public class Demo_FileWriter {

    public static void main(String[] args) {
        try {
            FileWriter fw=new FileWriter("bbb.txt");//如果要在末尾添加则,选择构造方法中有两个参数的,填入true
            fw.write("我像风一样自由!!!!!!");
            
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
    }

}

运行结果:

aaa.txt文件内容.png FileReader读取一个字符程序运行截图.png FileWrite输出字符运行结果.png
/**
 * @author 毛麒添
 * 字符流的拷贝
 */
public class CharIo_copy {

    public static void main(String[] args) {
        FileReader fr=null;
        FileWriter fw=null;
        
        try {
            fr=new FileReader("aaa.txt");
            fw=new FileWriter("ccc.txt");//不存在则会自动创建
            
            int a;
            while((a=fr.read())!= -1){
                fw.write(a);
            }
            //一定不能忘记关闭流,不关流,则输出文件中不会有内容,父类Writer中定义了缓存区,如果流不关闭,则输出内容还在缓冲区,不会出现输出字符
            fr.close();
            fw.close();
        } catch (IOException e) {
            
            e.printStackTrace();
        }
    }
}

运行结果:

字符流拷贝输出结果.png
/**
 * 
 * @author 毛麒添
 * 字符数组的拷贝
 */
public class Char_Arry_Copy {

    public static void main(String[] args) throws IOException {
        
        FileReader fr=new FileReader("aaa.txt");
        FileWriter fw=new FileWriter("ccc.txt");
        
        char[] arr=new char[1024];
        
        int len;
        
        while((len= fr.read(arr))!= -1){//将文件中的数据读取到字符数组中
            fw.write(arr, 0, len);      //将字符数组中的数据写入到文件中
        }
        
        fr.close();
        fw.close();

    }

}

/**
 * 
 * @author 毛麒添
 * 带缓冲区的字符流
 */
public class Char_Buffer {

    public static void main(String[] args) throws IOException {
        
        BufferedReader bf=new BufferedReader(new FileReader("aaa.txt"));
        BufferedWriter bw=new BufferedWriter(new FileWriter("ccc.txt"));
        
        int a;
        while((a= bf.read())!= -1){
            bw.write(a);
        }
        
        bf.close();
        bw.close();
    }

}
/**
 * 
 * @author 毛麒添
 * 将一个文本文档上的文本反转,第一行和倒数第一行交换,第二行和倒数第二行交换
 */
public class Reversal_text {

    public static void main(String[] args) throws IOException {
        //获取文本文档的字符输入输出流
        BufferedReader bf=new BufferedReader(new FileReader("aaa.txt"));
        BufferedWriter bw=new BufferedWriter(new FileWriter("ccc.txt")); 

        //创建一个String类型的List 保存读取的文本文档内容
        ArrayList<String> list=new ArrayList<String>();
        //将文本文档的内容保存到数组中
        String a;
        while((a=bf.readLine())!= null){//读取一行 
            list.add(a);
        }
        //反转遍历List写入文本文档中
        for(int i=list.size()-1;i>=0;i--){
            bw.write(list.get(i));
            bw.newLine();//写入一行换行
        }
        
        //关流
        bf.close();
        bw.close();
    }

}

运行结果:

aaa.txt文档中的内容.png ccc.txt文档中的内容.png

转换流

有时候我们会对不同码表的文件读取并写入,比如读取一个文本文件的码表是UTF-8,而写出文件的码表是GBK,如果直接读取,怎输出的文件内容必定会乱码,因为UTF-8 一个字符是三字节,而GBK一个字符代表两字节,每次只写入只输出连个字节,必然乱码 如下程序

/**
 * 
 * @author 毛麒添
 * 字符转换
 */
public class char_transformIO {
    public static void main(String[] args) throws IOException {
        FileReader fr=new FileReader("utf-8.txt");
        FileWriter fw=new FileWriter("gbk.txt");
        int a;
        while((a=fr.read()) !=-1){
            fw.write(a);
        }
        fr.close();
        fw.close();
    }
}

运行结果:

utf-8文件.png gbk文件.png
/**
 * 
 * @author 毛麒添
 * 字符转换
 */
public class char_transformIO {

    public static void main(String[] args) throws IOException {
        BufferedReader be=
                new BufferedReader(new InputStreamReader(new FileInputStream("utf-8.txt"),"UTF-8"));        
        BufferedWriter bw=
                new BufferedWriter(new OutputStreamWriter(new FileOutputStream("gbk.txt"),"gbk"));
                int a;
        while((a=be.read()) !=-1){
            bw.write(a);
        }
        be.close();
        bw.close(); 
    }
}

运行结果:

不乱码的gbk.txt文件.png
/**
 * 
 * @author 毛麒添
 * 获取一个文本上每个字符出现的次数,将结果写在times.txt上
 */
public class CharTest {

    public static void main(String[] args) throws IOException {
        //获取字符输入流
        BufferedReader br=new BufferedReader(new FileReader("times.txt")) ;
        //定义个双列集合 以键的形式存放文字中出现的字符 值为字符在文本中出现的次数
        TreeMap<Character, Integer> treeMap=new TreeMap<Character, Integer>();

        //读取aaa.txt中的字符 并存入集合中
        int a;
        while((a=br.read())!=-1){
            char b= (char) a;//强制类型转换
            //集合中没有该字符 值为1,有该字符 值加一
            treeMap.put(b,!treeMap.containsKey(b)? 1:treeMap.get(b)+1);
        }
        br.close();     
        //获取输出流对象
        BufferedWriter bw=new BufferedWriter(new FileWriter("times.txt")) ;
        //遍历集合,写入文件
        for (Character key : treeMap.keySet()) {
            //对特殊字符做特殊处理
            switch (key) {
            case '\t'://跳格
                bw.write("跳格出现的次数  ="+treeMap.get(key));
                break;
            case '\n'://换行  
                bw.write("换行出现的次数  ="+treeMap.get(key));
                break;
            case '\r'://回车  
                bw.write("回车出现的次数  ="+treeMap.get(key));
                break;
            default://直接写出字符
                bw.write(key+"="+treeMap.get(key));
                break;
            }
            bw.newLine();
        }
        bw.close(); 
    }
}

运行结果:aaa.txt可以查看上文,

times.txt中的内容.png
/**
 * 
 * @author 毛麒添
 * 当我们下载一个试用版软件,没有购买正版的时候,每执行一次就会提醒我们还有多少次使用机会用学过的IO流知识,模拟试用版软件,
 * 试用5次机会,执行一次就提示一次您还有几次机会,如果次数到了提示请购买正版
 */
public class Probation {

    public static void main(String[] args) throws IOException {
        //创建带缓冲的输入流对象,因为要使用readLine方法,可以保证数据的原样性
                BufferedReader br = new BufferedReader(new FileReader("config.txt"));
                //将读到的字符串转换为int数
                String line = br.readLine();//读取一行保证读取的是完整的字符串
                int times = Integer.parseInt(line); //将数字字符串转换为数字
                //对int数进行判断,如果大于0,就将其--写回去,如果不大于0,就提示请购买正版
                if(times > 0) {
                    //在if判断中要将--的结果打印,并将结果通过输出流写到文件上
                    System.out.println("软件开始试用,您还有" + times-- + "次机会");
                    FileWriter fw = new FileWriter("config.txt");
                    fw.write(times + "");
                    fw.close();
                }else {
                    System.out.println("您的试用次数已到,请购买正版");
                }
                //关流
                br.close();
    }
}

运行结果:

config.txt内容.png 模拟试用版软件截图1.png

程序运行5次后显示结果

模拟试用版软件截图2.png

到这里,IO流中的字符流和字节流基本上复习完了,但是IO流中不只有字节流和字符流,还有其他的一些不常用的,我们也来复习一些。


/**
 * 
 * @author 毛麒添
 * 读取两个文件中的数据整合写入到一个问价中
 */

public class SequenceIo {
    public static void main(String[] args) throws IOException {
        FileInputStream fis1=new FileInputStream("a.txt");
        FileInputStream fis2=new FileInputStream("b.txt");
        FileOutputStream fos=new FileOutputStream("c.txt"); 
        int a;
        while((a=fis1.read())!= -1){
            fos.write(a);
        }
        int b;
        while((b=fis2.read())!= -1){
            fos.write(b);
        }
        fis1.close();
        fis2.close();
        fos.close();
    }
}

在例子中我们可以看到,读取流的操作重复了两次,代码复用性差,如果使用序列流,则可以简化操作,序列流的构造方法可以传入两个输入流(SequenceInputStream(InputStream is1, InputStream is2)),把上面的例子改写为

/**
 * 
 * @author 毛麒添
 * 读取两个文件中的数据整合写入到一个问价中
 */
public class SequenceIo {
    public static void main(String[] args) throws IOException { 
        SequenceInputStream sqi=new SequenceInputStream(new FileInputStream("a.txt"), new FileInputStream("b.txt"));
        FileOutputStream fos=new FileOutputStream("c.txt");
        int a;
        while((a=sqi.read())!= -1){
            fos.write(a);
        }
        sqi.close();
        fos.close();
    }
}
/**
 * @author 毛麒添
 * 使用序列流整合多个输入流对象输出到一起
 */
public class SequenceEnum {
    public static void main(String[] args) throws IOException { 
        //创建vector集合对象
        Vector<FileInputStream> ve= new Vector<FileInputStream>();
        ve.add(new FileInputStream("a.txt"));
        ve.add(new FileInputStream("b.txt"));
        ve.add(new FileInputStream("d.txt"));
        Enumeration<FileInputStream> elements = ve.elements();
        SequenceInputStream sq=new SequenceInputStream(elements);
        FileOutputStream fos=new FileOutputStream("c,txt");    
        int a;
        while((a =sq.read())!= -1){
            fos.write(a);
        }    
        sq.close();
        fos.close();
    }
}

/**
 * 
 * @author 毛麒添
 * 定义一个文件输入流,调用read(byte[] b)方法,将a.txt文件中的内容打印出来(byte数组大小限制为5)
 */
public class ByteArrayIO {
    public static void main(String[] args) throws IOException {
        FileInputStream fis=new FileInputStream("a.txt");
        
        ByteArrayOutputStream bos=new ByteArrayOutputStream();
        
        byte[] arr=new byte[5];
        int len;
        while((len=fis.read(arr))!=-1){//将文件上的数据读到字节数组中
            bos.write(arr,0,len);//将字节数组的数据写到内存缓冲区中
        }
        System.out.println(bos);
        fis.close();//内存输出流只是对内存的操作,并没有建立流管道,所以不需要关流
    }
}
/**
 * 
 * @author 毛麒添
 * 对象操作流
 */

public class ObjectIO {

    public static void main(String[] args) throws IOException, IOException, ClassNotFoundException {
        //将对象存储在集合中写出
        Person p1=new Person("mao",24);
        Person p2=new Person("huang",23);
        Person p3=new Person("luo",23);
        ArrayList<Person> list = new ArrayList<>();
        list.add(p1);
        list.add(p2);
        list.add(p3);
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("File.txt"));
        oos.writeObject(list);
        System.out.println("存档保存成功");
        
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("File.txt"));
        ArrayList<Person> list1 = (ArrayList<Person>)ois.readObject();  //泛型在运行期会被擦除,索引运行期相当于没有泛型
        System.out.println("存档读取成功,读取到的存档");                                                                //想去掉黄色可以加注解                    @SuppressWarnings("unchecked")
        for (Person person : list1) {
            System.out.println(person);
        }
        ois.close();
    }
}

/**
 * 
 * @author 毛麒添
 * 玩家存档对象
 */
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    public Person(String name,int age){
        this.age=age;
        this.name=name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}

运行结果:

对象操作流运行结果.png
PrintStream ps = System.out;
            ps.println(97); //其实底层用的是Integer.toString(x),将x转换为数字字符串打印
            ps.println(new Person("张三", 23));
            Person p = null;
            ps.println(p);//如果是null,就返回null,如果不是null,就调用对象的toString()

//自动刷出: PrintWriter(OutputStream out, boolean autoFlush, String encoding) 
PrintWriter pw = new PrintWriter(new FileOutputStream("g.txt"), true);
            pw.write(97);
            pw.print("大家好");
            pw.println("你好");   //自动刷出,只针对的是println方法有效
        //pw.close();因为不是建立管道对硬盘操作,所以可以不必使用关流的方法
/**
 * 
 * @author 毛麒添
 * 修改标准输入输出流拷贝图片
 */
public class PrintIO {
    public static void main(String[] args) throws IOException {
    
        System.setIn(new FileInputStream("a.jpg"));
        System.setOut(new PrintStream("copy.jpg"));
        
        InputStream is = System.in; //获取标准输入流                       
        PrintStream ps = System.out;//获取标准输出流
        
        int len;
        byte[] arr = new byte[1024 * 8];
        
        while((len = is.read(arr)) != -1) {
            ps.write(arr, 0, len);
        }
        is.close();
        ps.close();
    }
}
 方式一:
//BufferedReader的readLine方法读取一行,实现键盘录入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line=br.readLine();

 方式二(常用方式):
Scanner sc=new Scanner(System.in);
String nextLine = sc.nextLine();
JDK API 1.6文档截图.png
/**
 * @author 毛麒添
 * java 实现多下载  
 */
public class MuchTreadDown {
    private static int threadCount=3;//开启线程数
    private static int blockSize=0;//每个线程下载的大小
    private static int runningThreadCount=0;//当前运行的线程数
    //本地服务器地址 Tomcat搭建
    private static String path="http://172.30.163.13:8080/NewsServer/XXX.exe";

    public static void main(String[] args) {
        
        try{
        //访问服务器获取资源 
        URL url = new URL(path);
        HttpURLConnection connection=(HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setConnectTimeout(10*1000);
        int code = connection.getResponseCode();
        if(code== 200){
            //1.获取资源的大小
            int filelength = connection.getContentLength();
            //2.在本地创建一个与本地资源文件同样大小的文件(占位)
            RandomAccessFile randomAccessFile=new RandomAccessFile(new File(getFileName(path)),"rw");
            randomAccessFile.setLength(filelength);
            
            //3.分配每个线程下载文件的开始位置和结束位置
            blockSize=filelength/threadCount;//计算出每个线程理论下载大小
            
            for(int threadID=0;threadID<threadCount;threadID++){
                int startIndex=threadID*blockSize;//计算每个线程下载开始的位置
                int endIndex=(threadID+1)*blockSize-1;//计算每个线程下载结束的位置
                
                //如果是最后一个线程,结束位置需要单独计算
                if(threadID==threadCount-1){
                    endIndex=filelength-1;
                }
                //4.开启线程执行下载
                new DownLoadThread(threadID, startIndex, endIndex).start();
            }   
        }
        
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    //下载文件的线程逻辑
     public static class DownLoadThread extends Thread{
         
        private int threadID;
        private int startIndex; //开始位置
        private int endIndex; //结束位置
        private int lastPostion;//网络中断最后下载的位置
        //构造方法传值
        public DownLoadThread(int threadID,int startIndex,int endIndex){
             this.threadID=threadID;
             this.startIndex=startIndex;
             this.endIndex=endIndex;
         }
        public void run() {
            synchronized (DownLoadThread.class) {
                runningThreadCount=runningThreadCount+1;//开启一个线程,当前线程数加1
            }
            //分段请求网络连接,分段保存文件到本地
            try {
                
                URL url = new URL(path);
                HttpURLConnection connection=(HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                connection.setConnectTimeout(10*1000);
                
                System.out.println("理论上 线程:"+threadID+".开始位置"+startIndex+".结束位置"+endIndex);
                
                //读取上次下载结束的位置.本次从这个位置直接下载
                File file2 = new File(threadID+".txt");
                if(file2.exists()){
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file2)));
                    //读取这一行的字符串
                    String lastPostion_str=bufferedReader.readLine();
                    //将字符串转换成 int类型
                     lastPostion=Integer.parseInt(lastPostion_str);//读取文件上次下载的位置
                    
                    //设置分段下载的的头信息 Range:做分段请求用的头(断点续传的开始)
                    connection.setRequestProperty("Range","bytes:"+lastPostion+"-"+endIndex);//bytes:0-500 请求服务资源0-500字节之间的信息
                    
                    System.out.println("实际上 线程:"+threadID+".开始位置"+lastPostion+".结束位置"+endIndex);
                    bufferedReader.close();
                }else{
                    
                    lastPostion=startIndex;
                    //设置分段下载的的头信息 Range:做分段请求用的头(默认的开始)
                    connection.setRequestProperty("Range","bytes:"+lastPostion+"-"+endIndex);//bytes:0-500 请求服务资源0-500字节之间的信息
                    
                    System.out.println("实际上 线程:"+threadID+".开始位置"+lastPostion+".结束位置"+endIndex);
                }
                
                
                if(connection.getResponseCode()==206){//200请求全部资源成功,206请求部分资源成功
                    //获取请求成功的流
                    InputStream inputStream = connection.getInputStream();
                    
                    //将请求成功的流写入之前占用的文件中
                    RandomAccessFile randomAccessFile = new RandomAccessFile(new File(getFileName(path)),"rw");
                    randomAccessFile.seek(lastPostion);//设置随机文件从哪个文件开始写入
                    //将流写入文件
                    byte[] buffer=new byte[1024*10];
                    int length=-1;
                    int total=0;//记录本次下载的位置
                    while((length=inputStream.read(buffer))!=-1){
                        randomAccessFile.write(buffer, 0, length);
                        
                        total=total+length;
                        //去保存当前线程下载的位置,保存到文件中
                        int currentThreadPostion=lastPostion+total;//计算出当前线程本次下载的位置
                        //创建随机文件保存当前线程下载的位置
                        File file=new File(threadID+".txt");
                        //将文件直接保存到硬盘中,防止出错
                        RandomAccessFile randomAccessFile2 = new RandomAccessFile(file,"rwd");
                        randomAccessFile2.write(String.valueOf(currentThreadPostion).getBytes());
                        randomAccessFile2.close();
                        
                    }
                    randomAccessFile.close();
                    inputStream.close();
                    
                    System.out.println("线程:"+threadID+"下载完毕");
                    
                    //当前线程下载结束,删除存放下载位置的文件
                    synchronized (DownLoadThread.class) {
                        runningThreadCount=runningThreadCount-1;//标志着一个线程下载结束(刚开始已下载完,所以runningThreadCount会等于开启线程的总数)
                        if(runningThreadCount==0){
                            for (int i = 0; i < threadCount; i++) {
                                File file=new File(i+".txt");
                                file.delete();
                            }
                        }   
                    }
                }
                
            } catch (Exception e) {
                e.printStackTrace();
            }
            
            super.run();
        }
     }
     
     //截取文件的名称
     public static String getFileName(String path){
         return path.substring(path.lastIndexOf("/"));//获取文件的名称
     } 
}

最后

到这里,IO流的复习就结束了,如果你看到这里,我相信你会对IO流的各个知识点有一个新的认识。这里套用一句人们常说的话,起房子地基打的牢,房子才能起得高,写代码也一样,基础知识就是我们的地基,只有基础打牢,我们才能给写出更好的程序。最后的最后,文章中如果有错误,请大家给我提出来,大家一起学习进步,如果觉得我的文章给予你帮助,也请给我一个喜欢。

本系列文章:
Java复习之集合框架
Java复习之IO流(上)
Java复习之IO流(下)
Java 复习之多线程

上一篇下一篇

猜你喜欢

热点阅读