读取文件路径名_第九讲:文件与IO

63c20d11c4d61e54d7d9a25655ce511d.png

一、File类的基本概念

1、File类的基本概念

File类:表示文件和目录径名的抽象表示形式。

File类可以实现文件的创建、删除、重命名、得到路径、创建时间等等、是唯一与文件本身有关的操作类。

package com.vince;import java.io.File;import java.io.IOException;import java.sql.Date;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.Arrays;/* * File类的使用 *  */public class FileDemo {  public static void main(String[] args) {    //File表示一个文件或目录    //"c:\\test\\vince.txt"    //"c:/test/vince.txt"    File f1=new File("c:"+File.separator+"test"+File.separator+"vince.txt");    if(!f1.exists()) {      try {        f1.createNewFile();//创建文件        System.out.println("文件创建成功");      } catch (IOException e) {        e.printStackTrace();      }    }    //f1.isFile():是否为文件    System.out.println("是否为文件夹"+f1.isDirectory());    File f2=new File("c:\\test\\my");    boolean b=f2.delete();    System.out.println(b);    String[] names=f2.list();//列出当前目录下的所有文件名    System.out.println(Arrays.toString(names));    File[] fs=f2.listFiles();    for(File f: fs) {      System.out.println("length="+f.length());      System.out.println("name="+f.getName());      System.out.println("相对路径"+f.getParent());      System.out.println("绝对路径"+f.getAbsolutePath());      System.out.println("是否为隐藏文件"+f.isHidden());      System.out.println("是否可读文件"+f.canRead());      Date date=new Date(f.lastModified());      DateFormat df=new SimpleDateFormat("HH:mm:ss");      System.out.println("文件最后修改的时间"+df.format(date));      System.out.println("------------------------");    }    //相对路径与绝对路径的区别    File f3=new File("temp.txt");    System.out.println(f3.getPath());    System.out.println(f3.getAbsolutePath());      File f4=new File("c:\\test\\dabin");      f4.mkdir();      f4.renameTo(new File("c:\\test\\yuduanhu"));      f4.mkdir();      //重命名与移动文件      f4.renameTo(new File("c\\yuduanhu"));  }}

2、File类的操作方法

public static finalString separarator表示路径分割符"\"
public Flie(String pathname)构造File类实例,要传入路径
public boolean createNewFile()创建新文件
public boolean deete()删除文件
public boolean isDirectory()判断给定的路径是否是文件夹
public boolean isFile()判断给定的路径是否是文件
public String[]list()列出文件夹中的文件
public File[] listFile()列出文件夹中所有的文件
public boolean mkdir()创建新的文件夹
public boolean renameTo(File dest为文件重命名
public long length返回文件大小
String getPath()路径名字符串
package com.vince;import java.io.File;/** * 在指定的目录中查找文件 *  *  * */public class FileDemo2 {  public static void main(String[] args) {    findFile(new File("c:\\"),".jpg");  }  //查找文件的方法    private static void findFile( File target,String ext) {      //如果文件是目录      if(target==null)return;      if(target.isDirectory()) {        File[] files=target.listFiles();        if(files!=null) {          for(File f:files) {            findFile(f,ext);//递归调用          }        }      }else {        //此处表示File是一个文件      String name=  target.getName().toLowerCase();      System.out.println(name);      if(name.endsWith(ext)) {        System.out.println(target.getAbsolutePath());      }      }    }}

二、字节流

1、IO流概述

IO流:输入输出流(lnput/Output)

流是一组有序的顺序,有起点和终点的字节集合,是对数据传输的总称或抽象,即数据在两设备间的传输称为流。流的本质是数据传输,根据数据传输特性将流抽象为各个类、方便更直观的进行数据操作。

IO流的分类

根据处理数据类型的不同分为:字符流和字节流,根据数据流向不同分为:输入流和输出流

2、字节输出流

OutputSteam类定义

public abatrct class OutputSteam extends Object implements Closeable,Flushable

此抽象类是表示输出字节流的所有类的超类,输出流接收输出字节并将这些字节发送到lnputStream类的一个接收器要向文件中输入,使用FlieOutputStream类

3、字节输入流

定义:public abatrct class lnputStrenam extends Object implements Closeable

此抽象类是表示字节流输入流所有类的超类,FilelnputStream从文件系统中的一个文件中获得输入字节

一、确定目录文件

二、构建一个文件输入流对象

三、表示每次读取的字节长度

四、把数据读入到数组中,并返回读取的字节数,当不等于-1时,表示读取到的数据等于-1表示文件已经读完

五、根据读取到的字节数    ,再转换成字符串的内容,添加StringBilder中

六、关闭输入流、打印内容(append 为true表示添加内容   \r\n表示换行

输出流操作原理;每次只会操作一个字节(从文件中读取或写入)

package com.vince;import java.io.*;/** * 字节输入输出流 * 输出流:超类OutputStream,对文件的输出流使用子类FileOutputStream * 输入流:超类InputStream,对文件的输入流使用子类FileInputStream * 字节操作流、默认每次执行写入操作会直接把数据写入文件 * 输入输出字节流操作原理,每次只会操作一个字节(从文件中读取或写入) */   public class  ByteStreamDemo{      public static  void in(){         //0、确定目标         File file=new File("c:\\test\\vince.txt");         //1、构建一个文件输入流对象         try {            InputStream in = new FileInputStream(file);            byte[] bytes = new byte[1024];            StringBuilder buf = new StringBuilder( );            int len = -1;//表示每次读取的字节长度            //把数据读入到数组中、并返回读取的字节数,当不等于-1时,表示读取到数据,等于-1表示文件已经读完了            while ((len = in.read(bytes)) != -1) {               //根据读取到的字节数组,再转换为字符串内容,添加到StringBilder中               buf.append(new String(bytes,0,len));            }            //打印内容            System.out.println( buf );            //关闭输入流            in.close();         } catch (FileNotFoundException e) {            e.printStackTrace( );         } catch (IOException e) {            e.printStackTrace( );         }      }      private  static  void  out(){         //0、确定文件目标文件         File file=new File("c:\\test\\vince.txt");         //1、构建一个文件输出流对象         try {            OutputStream out=new FileOutputStream(file,true);//append为true表示追加内容            //2、输出的内容            String info="你好阿!喝水吗?!\r\n";//\r\n表示换行              // String line=System.getProperty("line.separator");//获取换行符            //3、把内容写入到文件            out.write(info.getBytes());            //4、关闭流            out.close();            System.out.println( "write success." );         } catch (FileNotFoundException e) {            e.printStackTrace( );         } catch (IOException e) {            e.printStackTrace( );         }      }   public static void main(String[] args) {         //out();         in();   }}

三、字符流

Writer

写入字符流的抽象类,子类必须实现的方法仅有write(char[],int,int)、flush()和close().但是,多数字类将重写此处定义的一些方法,以提供高的效率和/或其它功能。与OutpuStream一样,对文件的操作使用:FileWriter类完成

Reader

用于读取字符流的抽象类,子类必须实现的方法有read(char[],int,int)和close().但是,多数子类将从此处定义的一些方法,以提供更高的效率/或其它功能。使用FileReader类进行实例化操作

字节流与字符流的区别

在所有的流操作里,字节永远是最基础的。任何基于字节的操作都是正确的,无论你是文本文件还是二进制的文件,如果确认流里面只有可打印的字符,包括英文的和各个国家的文字,也包括中文,那么可以考虑用字符流,由于编码不同,多字节的字符可能占用多个字节,比如:GBK汉字就占用2个字节,而UTF-8的汉字就占用3个字节。所以,字符流是根据指定的编码,将一个或多个字节转化为Java里面的unicode的字符,然后进行操作。字节操作一般使用Writer,Reader等,字节操作一般都是lnputStream,OutputStream以及各种包装类,比如:BufferedlnputStream和BufferedOutputStream等;

总结:如果你确认你要处理的流是可打印的字符,那么使用字符流会看上去简单一点。如果不确认,那么用字节流总是不会错。

如何选择使用字节流还是字符流:一般操作非文本文件时,使用字节流,操作文本文件时,建议使用字符流

字符流:

字符输出流:Writer,对文件的操作使用子类:FileWriter

字符输入流:Reader,对文件的操作使用子类:FileReader

每次操作的单位是一个字符:文件字符流会自动缓存,默认大小为1024字节,在缓存满后,或手动刷新缓存或关闭流时会把数据写入文件(字符流的内部实现还是字节流)

package com.vince;import java.io.*;/** * 字符流: * 字符输出流:Writer,对文件操作使用子类:FileWriter * 字符输出流:Reader,对文件的操作使用子类:FileReader * 每次操作的单位是一个字符 * 文件字符操作流会自带缓存,默认大小为1024字节,在缓存满后,或手动刷新缓存,或关闭流时会把数据写入文件 * 字节操作流、默认每次执行写入操作会直接把数据写入文件 * * 如何选择字节流或字符流? * 一般操作非文本文件时,使用字节流。操作文本文件时,建议使用字符流 * * 字符流的内部实现,其实还是字节流 */public class CharStreamDemo {    private  static  void  in(){        File file=new File("c:\\test\\vince.txt");        try {            Reader in=new FileReader(file);            char[] cs=new char[2];            int len=-1;            StringBuilder buf=new StringBuilder(  );            while ((len=in.read(cs))!=-1){                buf.append(new String( cs,0,len ));            }            in.close();            System.out.println( buf );        } catch (FileNotFoundException e) {            e.printStackTrace( );        } catch (IOException e) {            e.printStackTrace( );        }    }    private  static  void  out(){     File file=new File("c:\\test\\vince.txt");        try {            Writer out=new FileWriter(file,true);            out.write(",明天天气似乎不错!!");            out.close();        } catch (IOException e) {            e.printStackTrace( );        }    }    public static void main(String[] args) {     // out();        in();    }}
package com.vince;/** * 文件的复制; * 从一个输入流中读取数据,然后通过输出流写入目标位置 * 一边读一边写 * */import java.io.*;public class CopyFileDemo {    public static void main(String[] args) {      copy("c\\mm.jpg","c:\\test\\mm.jpg");      System.out.println( "start copy......" );      copy("c:\\mm.jpg","c:\\test\\mm.jpg");      System.out.println( "copy success." );    }    private  static  void  copy(String src,String target){        File srcFile=new File(src);        File targetFile=new File(target);        InputStream in=null;      OutputStream out=null;        try {           in=new FileInputStream(srcFile);            out=new FileOutputStream(targetFile);            byte[] bytes=new byte[1024];            int len=-1;            while ((len=in.read( bytes ))!=-1){             out.write(bytes,0,len);            }        } catch (FileNotFoundException e) {            e.printStackTrace( );        } catch (IOException e) {            e.printStackTrace( );        }finally {            if(in!=null) {                try {                    in.close();                    if(out!=null)out.close();                } catch (IOException e) {                    e.printStackTrace( );                }            }        }    }}

四、字节字符转换流

转换流,可以将一个字节流转换为字符流,也可以将一个字符流转换为字节流。

OutputStreamWriter:可以将输出的字符流转换为字节流的形式

lnputStreamReader:将输入的字节流转换为字符流输入形式

package com.vince;import java.io.*;import java.nio.charset.Charset;/** * 转换流 * 文件--->InputStream---->InputStreamReader------>程序 * 程序----->OutputStreamWriter-------->OutputStream-------->文件 * */public class ChangeStreamDemo {    private  static  void write(OutputStream out){        Writer writer=new OutputStreamWriter(out,Charset.defaultCharset());        try {            writer.write("开开心心敲代码\r\n");            writer.close();        } catch (IOException e) {            e.printStackTrace( );        }    }    private static void  in(InputStream in){        Reader reader=new InputStreamReader(in,Charset.defaultCharset());        char[] cs=new char[1024];        int len=-1;        while (true){            try {                if (!((len=reader.read( cs ))!=-1)) {                    System.out.println(new String(cs, 0, len));                }                reader.close();            } catch (IOException e) {                e.printStackTrace( );            }        }    }    public static void main(String[] args) throws FileNotFoundException {           // InputStream in=new FileInputStream("c\\test\\vince.txt");           // read(in);        OutputStream out=new FileOutputStream("c:\\test\\vince.txt");        write(out);    }}

五、缓冲流

首先要明确一个概念:

对文件和其它目标频繁的读写和操作,效率低,性能差。使用缓存流的好处是,能够高效的读写信息,原理是将数据先缓冲起来,然后一起写入或者读取出来。

BufferedlnputStream:为另一个输入流添加一些功能,创建BufferedlnputStream时,会创建一个内部缓冲区数组,用于缓冲数据。

BufferedOutputStream:通过设置这种缓冲流,应用程序就可以将各个字节写入底层输入流中,而不用针对每次字节写入调用底层系统

BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。

BufferedWriter:将文本写入字符输入流,缓冲各个字符,从而提供单个字符,数组和字符串的高效写入。

package com.vince;/** * 缓存的目的: * 解决在写入文件操作时,频繁的操作文件所带来的性能降低的问题 * BufferedOutputStream 内部默认的缓存大小是8KB,每次写入时存储到缓存中的byte数组中。当数组存满时,会把数组中的数据写入文件 * 并且缓存下标归零 * */import java.io.*;import java.nio.Buffer;public class BufferStreamDemo {    private static void  byteReader2() {        //private static void  byteReader() {       // private static void  byteWriter() {        File file = new File( "c://test//vince.txt");        try (BufferedInputStream bis=new BufferedInputStream(new FileInputStream(file))){          //InputStream in=new FileInputStream(file);            //构建一个字节缓冲流            //BufferedInputStream bos=new BufferedInputStream(in);            byte[] bytes=new byte[1024];            int len=-1;            while ((len=bis.read(bytes))!=-1){                System.out.println( new String( bytes,0,len ) );            }                    // bis.close();            //String info="敲代码使我快乐!!";            //bos.write(info.getBytes());           // bos.close();            //out.close();        } catch (FileNotFoundException e) {            e.printStackTrace( );        } catch (IOException e) {            e.printStackTrace( );        }    }    public static void main(String[] args) {     byteReader();    }}
** * 字符流: * 1、加入字符缓冲流,增强读取功能(readLine) * 2、更高效的读取数据 * FileReader:内部使用InputStreamReader(sun.nio.cs.StreamDecoder)解码过程,byte------>char,默认缓存大小是8K *BufferedReader:默认缓存大小也是8k,但也可以手动指定缓存大小,把数据进接读取到缓存中,减少每次转换过程,效率更高 *BufferedWriter: 同上 */import java.io.*;public class BufferStreamDemo2 {    private  static void charReader(){        File file=new File("c://test//vince.txt");        try {            Writer writer=new FileWriter(file);            BufferedWriter bw=new BufferedWriter(writer);            bw.write("国庆节也过完了!!");            bw.flush();            bw.close();           Reader reader=new FileReader(file);            //为字符流提供缓存,以达到高效读取的目的            BufferedReader br=new BufferedReader(reader);            char[] cs=new char[1024];            int len=-1;            while ((len=br.read(cs))!=-1){                System.out.println( new String( cs,0,len ) );            }        } catch (FileNotFoundException e) {            e.printStackTrace( );        } catch (IOException e) {            e.printStackTrace( );        }    }    public static void main(String[] args) {    }}

六、打印流

打印流的主要功能是用于输出,在整个IO包中打印流分为两种类型:

字节流打印:printStream

字符打印流:printWriter

打印流可以很方便的进行输入

package com.vince;import java.io.*;/** * 打印流:方便输出 * 字节打印流 *在字节输出时,可以增强输出功能 * 字符打印流 * */public class PrintStreamDemo {    private  static  void  charPrint(){        File file =new File("c:\\test\\vince.txt");        try {            Writer out=new FileWriter( file);            //加缓存            BufferedWriter bos=new BufferedWriter(out);            //增加打印功能            PrintWriter pw=new PrintWriter(bos);            pw.println( "国庆那么快就完了!!" );            pw.close();        } catch (IOException e) {            e.printStackTrace( );        }    }    private  static  void  bytePrint(){        File file =new File("c:\\test\\vince.txt");        try {          OutputStream  out = new FileOutputStream(file);          //加缓存            BufferedOutputStream bos=new BufferedOutputStream(out);            //增强打印功能            PrintStream ps=new PrintStream(bos);            ps.println( "国庆节也过完了!!" );            ps.close();        } catch (FileNotFoundException e) {            e.printStackTrace( );        }    }    public static void main(String[] args) {      //bytePrint();        charPrint();    }}

七、对象流

对象流的两个类:

objectOutputStream将Java对象的基本数据类型和图形写入OutputStream

bjectlnputStream:对以前使用bjectOutputStream写入的基本数据和对象进行反序例化。

序列化一组对象:

在序列化操作中,同时序列化多个对象,反序列化也必须按顺序操作,如果想要序列化一组对象该如何操作?

序列化一组对象可采用:对象数组的形式因为对象数组可以向Object进行转型操作。

transient关键字;

如果用transient声明一个实例变量,当对象储蓄时,它的值不需要维持。

package com.vince;import org.omg.CORBA.ULongSeqHelper;import java.io.Serializable;//如果一个类,创建的对象,需要被序列化,那么该类必须实现Serializable接口//Serializable是一个标记接口,没有任何定义,为了告诉JVM该类对象可以被序列化/** * 什么时候对象需要序列化呢? * 1、把对象存储到文件中(存储到物理介质) * 2、对象需要在网络上传输 * 3、如果对象没有实现Serializable接口,会报错误:java.io.NotSerializableException */public class Dog implements Serializable {    private String name;    private  int age;    private  String sex;    private transient int id;//在序列化中被忽略    public String getName() {        return name;    }    public void setSex(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public String getSex() {        return sex;    }    public void setName(String sex) {        this.sex = sex;    }       public Dog(String name,int age,String sex){        super();        this.name=name;        this.age=age;        this.sex=sex;       }       public Dog(){        super();       }       public String toString(){        return "Dog[name="+name+",age="+age+",sex="+sex+"]";       }}
package com.vince;import java.io.*;public class ObjectStreamDemo {    //从文件对象中把内容读取出来,还原成对象      private static void readObject(){          File file=new File("c:\\test\\dog.obj");          try {              InputStream in=new FileInputStream(file);              ObjectInputStream ois=new ObjectInputStream(in);               Dog dog=(Dog)ois.readObject();               ois.close();                System.out.println( dog );          } catch (FileNotFoundException e) {              e.printStackTrace( );          } catch (IOException e) {              e.printStackTrace( );          } catch (ClassNotFoundException e) {              e.printStackTrace( );          }      }    /**     * 对象序列化     * 把对象写入文件:实际写入的是类名,属性名,属性类型,属性的值等     */    //private  static  void   writeObject(){    private  static  void   writeObjects(){        Dog dog=new Dog("2哈",3,"公");        Dog dog2=new Dog( "wangwang",2,"母" );        Dog[] dogs={dog,dog2};        File file=new File("c:\\test\\dog.obj");        try {            OutputStream out=new FileOutputStream(file);            ObjectOutputStream oos=new ObjectOutputStream(out);            oos.writeObject(dog);            oos.close();        } catch (FileNotFoundException e) {            e.printStackTrace( );        } catch (IOException e) {            e.printStackTrace( );        }    }    public static void main(String[] args) {        //writeObject();        readObject();    }}

八、字节数组流

ByteArraylnputStream

包含一个内部缓冲区、该缓冲区包含从流中读取的字节。内部计数器跟踪read方法要提供的下一个字节,关闭ByteArraylnputStream无效,此类中的方法在关闭此流后任然可以被调用,而不会产生任何lOException.

ByteArrayOutputStream

此类实现了一个输入流,其中的数据被写入一个byte数组。缓冲区会随着数据的不断写入而自动增长,可使用toByteArray()和toString()获取数据。关闭ByteArrayOutputStream无效,此类中的方法在关闭此流后任可被调用,而不会产生任何IOException

package com.vince;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;/** * 字节数组流: * 基于内存操作:内部维护着一个字节数组,我们可以利用流的读取机制处理字符串,无需关闭 */public class ByteArrayStreamDemo {    private  static void byteArray(){        String s="123456789(!@#45%^^";        ByteArrayInputStream bais=new ByteArrayInputStream(s.getBytes());        ByteArrayOutputStream baos=new ByteArrayOutputStream(  );        int curr=-1;//每次读取的字节        while (bais.read()!=-1){            if(curr>=65 && curr<=90 ||(curr>=97 && curr<=122)){                baos.write(curr);            }        }        //此时无需关闭,原因:字节数组流基于内存操作流        System.out.println( baos.toString());    }    public static void main(String[] args) {    }}

九、数据流

DatalnputStream:

数据输入流允许程序以与机器无关方式从底层输入流中读取Java数据类型。应用程序可以使用数据输出流写如稍后由数据输入流读取数据。DatalnputStream对于多线程访问不一定安全。线程安全是可选的,它由此类方法的使用者负责。

DataOutputStream:

数据输出流允许应用程序以适当的方式将基本Java数据类型,写入输出流中。然后应用程序可以使用数据输入流将数据读入。

案例:

实现文件分割合并。

package com.vince;import java.io.*;/** * 数据流: * 与机器无关的操作Java的基本数据类型 */      public class DataStreamDemo {          private  static void read(){              File file=new File("c:\\test\\vince.txt");              try {                  InputStream in=new FileInputStream(file);                  BufferedInputStream bis=new BufferedInputStream(in);                  DataInputStream dis=new DataInputStream(bis);                  int num=dis.readInt();                  byte b=dis.readByte();                  String s=dis.readUTF();                  System.out.println( num+","+b+"s" );              } catch (FileNotFoundException e) {                  e.printStackTrace( );              } catch (IOException e) {                  e.printStackTrace( );              }          }          private static void write(){              File file=new File("c:\\test\\vince.txt");              try {                  OutputStream out=new FileOutputStream(file);                  BufferedOutputStream bos=new BufferedOutputStream(out);                  DataOutputStream dos=new DataOutputStream(bos);                  dos.writeInt(10);//写入四个字节                  dos.writeByte(1);                  dos.writeUTF("大");                  dos.close();              } catch (FileNotFoundException e) {                  e.printStackTrace( );              } catch (IOException e) {                  e.printStackTrace( );              }          }    public static void main(String[] args) {            //write();        read();    }    }
package com.vince;import java.io.*;/** * 文件的分割 * targetFile 要分割的目标文件 * cutSize每个文件的大小 */public class FileDivisionMergeDemo {    private static void division(File targetFile,long cutSize){//文件的分割        if(targetFile==null)return;        //计算总分割的文件数        int num=targetFile.length()%cutSize==0?                (int)(targetFile.length()/cutSize):(int)(targetFile.length()/cutSize+1);        //构造一个文件输入流        try {            BufferedInputStream in = new BufferedInputStream(new FileInputStream(targetFile));            BufferedOutputStream out=null;            byte[] bytes=null;//每次要读取的字节数            int len=-1;//每次实际读取的长度            int count=0;//每一个文件要读取的次数                //循环次数文生成文件的个数            for(int i=0;i                out=new BufferedOutputStream(new FileOutputStream(new File("H:\\test"+(i+1)+"-temp-"+targetFile.getName())));           if(cutSize<=1024){               bytes=new byte[(int)cutSize];               count=1;           }else{               bytes=new byte[1024];               count=(int)cutSize/1024;           }           while (count>0 &&(len=in.read( bytes ))!=-1){               out.write(bytes,0,len);               out.flush();               count--;           }           //计算每个文件大小除于1024的余数,决定是否要再读取一次           if(cutSize%1024!=0){               bytes=new byte[(int)cutSize%1024];               len=in.read( bytes );               out.flush();               out.close();           }            }            in.close();        }catch (FileNotFoundException e){            e.printStackTrace();        }catch (IOException e){            e.printStackTrace();        }    }    private static void merge(){//文件合并    }    public static void main(String[] args) {        File file=new File("H:\\第08章 文件与IO_01_File类的使用");        division(file,1024*1024*20);    }}

十、合并流、字符串流、管道流

一、合并流:

SequencelnputStream表示其输入流的逻辑串联,它从输入流有序集合开始,并从第一个输入流开始读取,直到达文件末尾,接着从第二个输入流读取,以此类推,直到到达包含的最后一个输入流的文件末尾为止

二、字符串流

1、StringReader

其源为一个字符串的字符流

2、StringWriter

一个字符流,可以用其回收在字符串缓冲区中输出构造字符,关闭StringWrite无效。此类中的方法在关闭该流后任然可被调用,而不会产生任何IOException

三、管道流

管道输入流因该连接管道输出流,管道输入流要提供要写入管道输出流所以数据字节。通常,数据由一个线程从PipedlnputStream对象读取,并由其线程将其写入到相应的PipedlnputStream,不建议对这两个对象尝试使用单线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。如果向连接管道输出流提供数据字节线程不在存在,则认为该管道已损坏。

package com.vince;import sun.security.x509.IPAddressName;import java.io.*;import java.util.Enumeration;import java.util.Vector;/** * 文件的分割 * targetFile 要分割的目标文件 * cutSize每个文件的大小 */public class FileDivisionMergeDemo {    private static void division(File targetFile,long cutSize){//文件的分割        if(targetFile==null)return;        //计算总分割的文件数        int num=targetFile.length()%cutSize==0?                (int)(targetFile.length()/cutSize):(int)(targetFile.length()/cutSize+1);        //构造一个文件输入流        try {            BufferedInputStream in = new BufferedInputStream(new FileInputStream(targetFile));            BufferedOutputStream out=null;            byte[] bytes=null;//每次要读取的字节数            int len=-1;//每次实际读取的长度            int count=0;//每一个文件要读取的次数                //循环次数文生成文件的个数            for(int i=0;i                out=new BufferedOutputStream(new FileOutputStream(new File("H:\\test"+(i+1)+"-temp-"+targetFile.getName())));           if(cutSize<=1024){               bytes=new byte[(int)cutSize];               count=1;           }else{               bytes=new byte[1024];               count=(int)cutSize/1024;           }           while (count>0 &&(len=in.read( bytes ))!=-1){               out.write(bytes,0,len);               out.flush();               count--;           }           //计算每个文件大小除于1024的余数,决定是否要再读取一次           if(cutSize%1024!=0){               bytes=new byte[(int)cutSize%1024];               len=in.read( bytes );               out.flush();               out.close();           }            }            in.close();        }catch (FileNotFoundException e){            e.printStackTrace();        }catch (IOException e){            e.printStackTrace();        }    }    private static void merge(Enumeration es){//文件合并        //构造一个合并流        try{        SequenceInputStream sis=new SequenceInputStream(es);        BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("H:\\test\\第08章 文件与IO_01_File类的使用\");"));           byte[] bytes=new byte[1024];           int len=-1;           while ((len=sis.read(bytes))!=-1){               bos.write(bytes,0,len);               bos.flush();           }           bos.close();           sis.close();           System.out.println( "合并完成" );        }catch (FileNotFoundException e){            e.printStackTrace();        }catch (IOException e){            e.printStackTrace();        }    }    public static void main(String[] args) {        //File file=new File("H:\\第08章 文件与IO_01_File类的使用");        //division(file,1024*1024*20);        try {            InputStream in1=new FileInputStream(new File("H:test\\(\"H:\\第08章 文件与IO_01_File类的使用\");"));            InputStream in2=new FileInputStream(new File("H:test\\(\"H:\\第08章 文件与IO_01_File类的使用\");"));            InputStream in3=new FileInputStream(new File("H:test\\(\"H:\\第08章 文件与IO_01_File类的使用\");"));            InputStream in4=new FileInputStream(new File("H:test\\(\"H:\\第08章 文件与IO_01_File类的使用\");"));            InputStream in5=new FileInputStream(new File("H:test\\(\"H:\\第08章 文件与IO_01_File类的使用\");"));            //集合工具类,内部实现使用了数组            Vector v=new Vector(  );            v.add(in1);            v.add(in2);            v.add(in3);            v.add(in4);            v.add(in5);           Enumeration es= v.elements();        } catch (FileNotFoundException e) {            e.printStackTrace( );        }    }}
package com.vince;/** * 管道流测试:一个线程写入,一个线程读取 * 作用,用于线程之间的线程通讯 */import java.io.IOException;import java.io.PipedInputStream;import java.io.PipedOutputStream;public class PipedStreamDemo {    public static void main(String[] args) {        PipedInputStream pin=new PipedInputStream(  );        PipedOutputStream pout=new PipedOutputStream(  );        try {            pin.connect(pout);//两个管道进行连接        } catch (IOException e) {            e.printStackTrace( );        }//输入流与输出流连接        ReadThread readTh=new ReadThread(pin);        WriteThread writeTh=new WriteThread(pout);        new Thread( readTh ).start();        new Thread( writeTh ).start();    }}//读取数据的线程class ReadThread implements Runnable{    private PipedInputStream pin;//输入管道    ReadThread(PipedInputStream pin){         this.pin=pin;    }    @Override    public void run() {        byte[] buf=new byte[1024];        try {            int len=pin.read(buf);//管道阻塞            String s=new String( buf,0,len );            System.out.println( "读到:"+s );            pin.close();        } catch (IOException e) {            e.printStackTrace( );        }    }}//写入数据的线程   class WriteThread implements Runnable{    private PipedOutputStream pout;//输出管道    WriteThread(PipedOutputStream pout){           this.pout=pout;    }       @Override       public void run() {           try {               pout.write("国亲节就这样完了 ".getBytes());//管道输出流               pout.close();           } catch (IOException e) {               e.printStackTrace( );           }       }   }

十一、RandomAccessFile

RandomAccessFile是IO包的类,从Object直接继承而来,只可以对文件进行操作,可以对文件进行读写和写入,当模式为r时,当文件不存在时会报异常,当模式为rw时,当文件不存在时,会自己创建文件,当文件已经存在时不会对原有文件进行覆盖。

RandomAccessFile有强大的文件读写功能,其内部是大型的byte[],可以通过seek(),getFilePointer()等方法操作的指针,方便对数据进行写入与读取,还可以对基本数据类型进行直接的读和写操作。

RandomAccessFile的绝大多数功能,已经被JDK1.4的nio的“内存映射文件(memory-mapped files")给取代了,该考虑一下是不是用”内存映射文件“来代替RandomAccessFile了。

package com.vince;import java.io.FileNotFoundException;import java.io.IOException;import java.io.RandomAccessFile;import java.util.RandomAccess;public class RandromAccessFileDemo {     public static void main(String[] args) {       try {              //读取文件         RandomAccessFile r=new RandomAccessFile("c:\\test","vince");                //写入文件         RandomAccessFile w=new RandomAccessFile("c:\\test","vince");         byte[] bytes=new byte[1024];         int len=-1;         while ((len=r.read(bytes))!=-1){           w.write(bytes,0,len);         }         w.close();         r.close();       } catch (FileNotFoundException e) {         e.printStackTrace( );       }catch (IOException e){         e.printStackTrace();       }       System.out.println( "copy success." );     }}

十二、Properties文件操作

Properties(java.util.Properties)主要用于读写Java的配置文件,各种语言都有自己所支持的配置文件,配置文件中有很多变量是经常改变的,这样做也是为了方便用户,让用户能够脱离程序本身去修改相关的变量设置。

它提供了几个主要的方法:

1、getProperty(Sting key)在指定的键在此属性列表中搜索属性,也就是通过参数key,得到key所对应的value

2、load(lnputStream inStream)从输入流中读取属性列表(键和元素对)通过对指定的文件(比如说上面的test.proPerties文件)进行装载来获取该文件所有的键-值对。以提供load(lnputStream inStream)来搜索。

3、setProperty(Sting key、String、value)调用Hashtable的方法put.它通过调用基类的put来设置键-值对

4、store(OutputStream out,String comments)以适合使用load方法加载到properties表中的格式,将此properties表中的属性列表(键和元素对)写入输出流。与load方法相反,该方法将键-值对写入到指定的文件中去。

5、clear()清除所有装载的 键-值对。该方法在基类中提供。

package com.vince;import java.io.*;import java.util.Properties;/** * Properties:可以用来做配置文件 * javaWeb javaEE 开发中通常会用到 * * ResouceBundle 只读 * Properties 可读可写 * */public class PropertiesDemo {    public static String version="";    public static  String username="";    public static  String password="";    //静态代码块,只会执行一次    static {        //readConfig();;    }           //对源文件的写入操作    private static void writeConfig(String version,String username,String password){        Properties p=new Properties(  );        p.put("app.version",version);        p.put("db.password",password);        OutputStream out= null;        try {            out = new FileOutputStream("config.properties");            //写文件            p.store(out,"update config");            out.close();        } catch (FileNotFoundException e) {            e.printStackTrace( );        }catch (IOException e){            e.printStackTrace();        }    }     /**      * 读取Properties配置文件      */    private static void readConfig(){      Properties p=new Properties(  );        try {            //通过当前线程的类加载器对象,来加载指定包下的配置文件         InputStream   inputStream= Thread.currentThread().getContextClassLoader().getResourceAsStream                 ("com/res/config.properties");            //InputStream inputStream=new FileInputStream("com/res/config.properties");            p.load(inputStream);//加载文件            //从Properties中获取数据           version= p.getProperty("app.version");           username= p.getProperty("db.username");           password=p.getProperty("db.password");           inputStream.close();        } catch (FileNotFoundException e) {            e.printStackTrace( );        }catch (IOException e){            e.printStackTrace();        }    }    public static void main(String[] args) {            // readConfig();        writeConfig("2","2","3");             System.out.println( PropertiesDemo.version );             System.out.println( PropertiesDemo.username);             System.out.println( PropertiesDemo.password );    }}

十三、文件压缩与解压缩

java中实现zip的压缩与解压缩

zipOutputStream实现的压缩

zipOutputStream(OuttputStream out)

创建新的ZIP输出流

void putNextEntry(ZipEntry e)

开始写入新的ZIP文件条目并将流定位到条目数据的开始处。

ZipEntry(String name)

使用指定名称创建新的ZIP条目

ZiplnputStream

实现文件压缩

ZiplnputStream(lnputStream  im)

创建新的ZIP输入流

zipEntry getNextEntry()

读取下一个ZIP文件条目并将流定位到该条目数据的开始处。

package com.vince;import jdk.nashorn.api.scripting.ScriptObjectMirror;import java.io.*;import java.util.zip.ZipEntry;import java.util.zip.ZipInputStream;import java.util.zip.ZipOutputStream;/** * 压缩与解压缩 */public class CompressionAndDecompressionDemo {    /**     * 压缩     */    private  static void compression(String zipFileName,File targetFile){        System.out.println( "正在压缩中......" );        try {            //要生成的压缩文件            ZipOutputStream out=new ZipOutputStream(new FileOutputStream(zipFileName));            BufferedOutputStream bos=new BufferedOutputStream(out);            zip(out,targetFile,targetFile.getName(),bos);            bos.close();            out.close();        } catch (FileNotFoundException e) {            e.printStackTrace( );        }catch (IOException e){            e.printStackTrace();        }        System.out.println( "压缩完成!!" );    }    private  static void zip(ZipOutputStream zOut, File targetFile, String name, BufferedOutputStream bos) throws IOException {       //如果是目录        if(targetFile.isDirectory()){        File[] files=  targetFile.listFiles();        if(files.length==0){//空文件夹            zOut.putNextEntry(new ZipEntry(name+"/"));//处理空目录        }        for(File f:files){            //递归处理            zip(zOut,f,name+"/"+f.getName(),bos);        }        }else{            zOut.putNextEntry(new ZipEntry(name));            InputStream in=new FileInputStream(targetFile);            BufferedInputStream bis=new BufferedInputStream(in);            byte[] bytes=new byte[1024];            int len=-1;            while ((len=bis.read( bytes ))!=-1){                bos.write(bytes,0,len);            }            bis.close();            return;        }    }    /**     * 解压缩     */    private static void decompression(String targetFileName,String parent){        try {            //构建解压输入流            ZipInputStream zIn=new ZipInputStream(new FileInputStream(targetFileName));            ZipEntry entry=null;            File file=null;            while ((entry=zIn.getNextEntry())!=null && !entry.isDirectory()){                file =new File(parent,entry.getName());                if(!file.exists()){                    new File(file.getName()).mkdirs();//创建此文件的上级目录                }                OutputStream out=new FileOutputStream(file);                 BufferedOutputStream bos=new BufferedOutputStream(out);                 byte[] bytes=new byte[1024];                 int len=-1;                 while ((len=zIn.read(bytes))!=-1){                     bos.write(bytes,0,len);                 }                 bos.close();                 System.out.println( file.getAbsolutePath()+"解压成功" );            }

96f079dbc08f9260a00c9cade485cd24.png

十四、装饰者模式

意图:

动态的给一个对象添加职责,就增加功能来说,Decorator模式相比生成子类更为灵活,该模式对客户端透明的方式扩展对象的功能。

适用环境

在不影响其它对象的情况下,以动态,透明的方式给单个对象添加职责。处理那些可以撤销的职责。

当不能采用生成子类的方法进行扩充时,一种情况是,可能有大量独立的扩展。为支持每一种组合将产生大量的子类。使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被被隐藏,或类定义不能用于生成子类

类图:

Component(被装饰对象基类)

定义对象的接口,可以给这些对象动态

ConcreteComponent(具体被装饰对象)

定义具体的对象,Decorator可以给他增加额外的职责

Decorator(装饰者抽象类)

维护指向Component实例的引用,定义与Component一致的接口

DconcreteDecorator(具体装饰者)

具体的装饰对象,给内部持有的具体被装饰对象增加具体的职责。

装饰者模式小结:

oo原则:动态的将责任附加到对象身上,想要扩展功能,装饰者提供有别于继承的另一种选择。

要点:

1、继承属于扩展形式之一,但不见得是达到弹性设计的最佳方案。

2、在我们的设计中,应该允许行为可以被扩展,而不需修改现有的代码。

3、组合和委托可以用于在运行时动态地加上新的行为。

4、除了继承,装饰者模式也可以让我们扩展行为。

5、装饰者模式意味着一群装饰者类、这些类用于包装具体组件。

6、装饰者类反映出被装饰的组件类型(实际上,它们具有相同的类型,都经过接口或继承实现)

7、装饰者可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。

8、你可以有无数个装饰者包装一个组件。

9、装饰者一般对组建的客户透明,除非客户程序依赖于组件的具体类型。

package com.vince;/** * 被装饰者接口类: * */public interface Drink {    float cost();//计算价格    String description();//描述}
package com.vince;/** * 具体的被装饰者类 * 豆浆 */public class SoyaBeanMilk  implements  Drink{    @Override    public float cost() {        return 10f;    }    @Override    public String description() {        return "农村豆浆";    }}
package com.vince;/** * 具体的装饰者类 */public class SugarDecorator  extends Decorator {    public SugarDecorator(Drink drink) {        super(drink);    }    @Override    public float cost() {        return super.cost( )+1.0f;    }    @Override    public String description() {        return super.description( )+"+糖";    }}
package com.vince;import java.io.DataInputStream;/** * 装饰者的鸡类 */public  abstract class Decorator  implements  Drink{    private Drink drink;//要装饰者对象    public Decorator(Drink drink){        this.drink=drink;    }    public float cost() {        return drink.cost();    }    @Override    public String description() {        return drink.description();    }}
package com.vince;public class Test {    public static void main(String[] args) {        Drink drink=new SoyaBeanMilk();        SugarDecorator sugar=new SugarDecorator(drink);        System.out.println( "你点的豆浆是:"+drink.description() );        System.out.println( "一共花了"+drink.cost()+"元" );    }}

十五、常见字符编码

在程序中如果没有处理好字符的编码,就可能出现乱码问题,在计算机的世界里任何的文字都是以指定的编码方式存在的。

常见的编码有:ISO8859-1、GBK/GB2312、unicode、UTF。

iso8859-1:

编码属于单字节编码,最多只能表示0--255的字符范围,主要在英文上运用。

GBK/GB2312:

中文的国际编码,专门用来表示汉字,是双字节编码

package com.vince;import java.io.UnsupportedEncodingException;public class CodeDemo {    public static void main(String[] args) {        //通常产生乱码的情况是,两个不兼容的编码互相转换        String info="你好啊!!";//GB2312        try {            String  newInfo=new String(info.getBytes("jb2312"),"iso8859-1");            System.out.println( newInfo );            String newInfo2=new String( newInfo.getBytes("iso8859-1"),"jb2312" );            System.out.println( newInfo2 );        } catch (UnsupportedEncodingException e) {            e.printStackTrace( );        }    }}

十六、new IO

1、为什么要使用NIO?

NIO是JDK1.4加入的新包,NIO的创建目的是为了让Java程序员可以实现高速I/O而无需编写自定义的本机代码。NIO将最耗时的I/O操作(即填充和提取缓冲区)转移回操作系统,因而可以极大地提高速度。

流与块的比较

原来的I/O库(在java.io.*中)与NIO最重要的区别是数据打包和传输的方式,原来的I/O以流的方式处理数据,而NIO以块的方式处理数据。

面向流的I/O系统一次一个字节地处理数据,一个输入流产生一个字节的数据,一个输出流消费一个字节的shu'j,不利的一面是,面向流的I/O通常相当慢。

一个面向块的I/O系统以块的形式处理数据,每一个操作都在一步中产生或消费一个数据块,按块处理数据比按(流式的)字节处理数据要快得多,但是面向块的I/O缺少一些面向流的I/O所具有优雅性和简单性。

缓冲区

在NIO库中,所有数据都是用缓冲区处理的,在读取数据时,它是直接读取到缓冲区的。在写入数据时,它是写入到缓冲区中的,任何时候访问NIO中的数据,都是将它放到缓冲区。

缓冲区实质是一个数组,通常是一个字节数组,但是它可以使用其它种类的数组,但是一个缓冲区不仅仅是一个数组。缓冲区提供了对数组的结构化访问,而且还可以跟踪系统的读/写进程

缓冲区类型

最常用的缓冲区类型是ByteBuffer.一个ByteBuffer可以在其底层字节数组上运行get/set操作(即字节的获取和设置)ByteBuffer不是NIO中唯一的缓冲区类型。事实上,对每一种基本Java类型都有一种缓冲区类型:

ByteBuffer

CharBuffer

shortBuffer

IntBuffer

LongBuffer

FloatBuffer

DoubleBuffer

缓冲区内部细节:

状态变量

可以用三个指定缓冲区在任意时刻的状态。

position

limit

capacity

通道:

Channel

Channel是一个对象,可以通过它读取和写入数据,拿NIO与原来的I/O做个比较,通道就像是流。

所有数据通过Buffer对象来处理,永远不会将字节直接写入通道中,相反,将数据写入包含一个或者多个字节的缓冲区。同样,不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。

package com.nio;import java.nio.ByteBuffer;public class NIODemo {    public static void main(String[] args) {        //创建一个字节缓冲区,申请内存空间为8字节        ByteBuffer buf = ByteBuffer.allocate(8);        System.out.println("position=" + buf.position( ));        System.out.println("limit=" + buf.limit( ));        System.out.println("capacity=" + buf.capacity( ));        //向缓冲区中写入数据        buf.put((byte) 10);        buf.put((byte) 20);        buf.put((byte) 30);        buf.put((byte) 40);        System.out.println("position=" + buf.position( ));        System.out.println("limit=" + buf.limit( ));        System.out.println("capacity=" + buf.capacity( ));        //缓冲区反转        buf.flip( );        System.out.println("---------------------");        System.out.println("position=" + buf.position( ));        System.out.println("limit=" + buf.limit( ));        System.out.println("capacity=" + buf.capacity( ));        //告知在当前位置和限制之间是否有元素        if (buf.hasRemaining( )) {            //返回当前位置与限制之间的元素数            for (int i = 0; i < buf.remaining( ); i++) {                byte b = buf.get( i );                System.out.println(b);            }        }    }}
 package com.nio;import java.io.*;/** * 比较IO操作的性能比较: * 1、内存映射最快 * 2、NIO读写文件 * 3、使用缓存的IO流 * 4、无缓存的IO流 */import java.nio.ByteBuffer;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;public class CopyFileDemo {    private static void RandomAccessFileCopy() throws FileNotFoundException {        RandomAccessFile in=new RandomAccessFile("c:\\3D0.jpg","r");        RandomAccessFile out=new RandomAccessFile("c:\\3D0.jpg","r");        FileChannel fcIn=in.getChannel();        FileChannel fcOut=out.getChannel();        try {            long size=fcIn.size();//输入流的字节大小            MappedByteBuffer inBuf=fcIn.map(FileChannel.MapMode.READ_ONLY,0,size);            //输出缓冲流            MappedByteBuffer outBuf=fcOut.map(FileChannel.MapMode.READ_ONLY,0,size);            for(int i=0;i                outBuf.put(inBuf.get(  ));            }            //关闭通道时写入数据块            fcIn.close();            fcOut.close();            in.close();            out.close();            System.out.println( "copy success." );        } catch (IOException e) {            e.printStackTrace( );        }    }    //通过文件通道实现文件复制    private static void copyFile() throws Exception {        //创建一个文件输出通道        FileChannel fcIn=new FileInputStream("c:\\3D0.jpg").getChannel();        //创建一个文件输出的通道        FileChannel fcOut=new FileOutputStream("c:\\test\\3D0.jpg").getChannel();        ByteBuffer buf= ByteBuffer.allocate(1024);        while (fcIn.read(buf)!=-1){            buf.flip();            fcOut.write(buf);            buf.clear();        }        fcIn.close();            fcOut.close();            System.out.println( "copy success." );    }    public static void main(String[] args) {        try {           copyFile();            } catch (Exception e) {            e.printStackTrace( );        }    }}

JDK1.7引入的新的IO操作类,Java.nio.file包下,Java NIO Path接口和File类

Path接口

1、Path表示的是一个目录名序列,其后还可以跟着一个文件名,路径中第一个根部件时就是绝对路径,例如/或C:\、而允许访问的根部件取决于文件系统。

2、以根部件开始的路径是绝对路径,否则就是相对路径;

3、静态的Paths.get方法接受一个或多个字符串,字符串之间自动使用默认文件系统的路径分隔符连接起来(Unix是/,Windows是\)这就是解决了跨平台的问题,接着解析连接起来的结果,如果不是合法路径就抛出InvalidPathExcePtion异常,否则就返回一个Path对象。

package com.nio;import java.io.File;import java.io.IOException;import java.nio.file.*;/** * JDK1.7新的文件操作类 */public class PathFiledDemo {    public static void main(String[] args) {        File file = new File("c:\\test\\3D0.jpg");        //path        Path p1 = Paths.get("c:\\test\\", "3D0.jpg");        System.out.println(p1);        Path p2 = file.toPath( );        System.out.println(p2);        Path p3 = FileSystems.getDefault( ).getPath("c\\test\\", "3D0.jpg");        //File工具类        Path p4 = Paths.get("c:\\test\\vince.txt");        String info = "哈哈";        /*try {            //写入文件            Files.write(p4,info.getBytes("gb2312"),StandardOpenOption.APPEND );        } catch (IOException e) {            e.printStackTrace( );        }*/        try {            //读取文件            byte[] bytes = Files.readAllBytes(p4);            System.out.println(new String(bytes));        } catch (IOException e) {            e.printStackTrace( );        }        //复制文件        /*try {            Files.copy(p3, Paths.get("c:\\3D0.jpg"), StandardCopyOption.REPLACE_EXISTING);        } catch (IOException e) {            e.printStackTrace( );        }*/        //移动文件       /* try {            Files.move(p3, Paths.get("c:\\3D0.jpg"), StandardCopyOption.REPLACE_EXISTING);        } catch (IOException e) {            e.printStackTrace( );        }*/          //删除文件       /* try {            Files.delete(p3);        } catch (IOException e) {            e.printStackTrace( );        }        */        try {            //创建新目录,除了最后一个部件,其它必须是已经存在的           // Files.createDirectory(Paths.get("c:\\BB"));            //创建不存在的中间部件            //Files.createDirectories(path);            //创建一个空文件,检查文件存在,如果已经存在则抛出异常而检查文件存在是原子性的。因此再次过程中无法执行文件创建操作                Files.createFile(Paths.get("c:\\BB.txt"));            //如果path不存在文件将抛出异常,此时调用下面的比较好           // static boolean deleteIfExists(Path path);            } catch (IOException e) {                e.printStackTrace( );            }            //添加前/后缀创建临时文件或临时目录        //Path newPath=Files.createTempFile(dir,prefix,suffix);        //Path newPath=Files.createTempDirectory(dir,prefix);    }    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值