Java——再看IO

java

一。编码问题

  • utf-8编码中,一个中文占3个字节,一个英文占1个字节;gbk编码中,一个中文占2个字节,一个英文占1个字节。
  • Java是双字节编码,为utf-16be编码,是说一个字符(无论中文还是英文,都占用2个字节)。因此如果这么问:Java字符串中一个字符可以放一个中文吗?是可以的!
  • 如果一直某个字节序列的编码方式,当我们想将它还原成字符串时,应明确指定其编码格式,否则会出现乱码。
  • 文本文件就是字节序列,可以是任意编码的字节序列。如果在中文机器上,直接创建文本文件,该文本文件只认识ANSI编码

public static void main(String[] args) throws UnsupportedEncodingException {

// TODO Auto-generated method stub

/*

* 在utf-8编码中中文占3个字节,而bgk编码中中文占2个字节

*/

String s = "慕课ABC";

byte[] byte1 = s.getBytes();

for(byte b : byte1) //byte 8bits, int 32 bits. xx vs xxxxxxxx

System.out.print(Integer.toHexString(b & 0xff) + " "); //e6 85 95 e8 af be 41 42 43 (code: utf-8)

System.out.println();

byte[] byte2 = s.getBytes("gbk");

for(byte b : byte2)

System.out.print(Integer.toHexString(b & 0xff) + " "); //c4 bd bf ce 41 42 43

/*

* java是双字节编码,utf-16be编码。意思是Java里的字符串的一个字符占用2个字节

* 面试官会问:Java一个字符中可不可以放汉字呢?如果是gbk编码,是可以的。

*/

System.out.println();

byte[] byte3 = s.getBytes("utf-16be");

for(byte b : byte3)

System.out.print(Integer.toHexString(b & 0xff) + " "); //61 55(慕) 8b fe(课) 0 41(A) 0 42(B) 0 43(C)

System.out.println();

String s1 = new String(byte3);

System.out.println(s1); //乱码

String s2 = new String(byte3, "utf-16be");

System.out.println(s2); //慕课ABC

}

View Code

二。File类的使用

  • java.io.File类用于表示文件(目录);
  • File类值用于表示文件(目录 )的信息(名称、大小等),不能用于文件内容的访问。
  •  静态方法:File.seperator可以直接当成分隔符使用,无论在什么系统下都可以使用,避免\\ or /的困扰。 

/**

* 递归遍历一个目标及所有子目录下的文件,打印其文件名

* @param dir

*/

public void listDirectory(File dir) {

if(!dir.exists())

throw new IllegalArgumentException("目录" + dir + "不存在");

if(!dir.isDirectory())

throw new IllegalArgumentException(dir + "不是一个目录");

//String[] fileNames = dir.list();

File[] files = dir.listFiles();

//如果要遍历子目录下的内容,就要构造File对象做递归操作

if(files != null && files.length > 0) {

for(File file : files) {

if(file.isDirectory())

listDirectory(file);

else System.out.println(file.getName());

}

}

}

View Code

  

三。RandomAccessFile的使用

  • Java提供的对文件内容的访问,既可以读文件,也可以写文件。
  • 支持随机访问文件,可以访问文件的任意位置。
  • Java文件模型:

    • 在硬盘上的文件是byte byte byte存储的,是数据的集合。

  • 打开文件

    • 有两种模式“rw”和“r”
    • RandomAccessFile raf = new RandomAccessFile(file, "rw");
    • 文件指针,打开文件是指针在开头 pointer = 0;

  • 写文件

    • raf.write(int) —— 只写一个字节(后8位),同时指针指向下一个位置,准备再次写入

  • 读方法

    • int b = raf.read() —— 读一个字节

  • 文件读写完成后一定要关闭

public static void main(String[] args) throws IOException {

// TODO Auto-generated method stub

File demo = new File("demo");

if(!demo.exists())

demo.mkdir();

File file = new File(demo, "raf.dat");

if(!file.exists())

file.createNewFile();

RandomAccessFile raf = new RandomAccessFile(file, "rw");

//指针的位置,输出为0,随机读取文件好处:文件下载时,文件很大,分成程序同时下载,灭个下载

//然后在拼接在一起,迅雷每个线程下载文件的一部分,需要知道拼接的位置在哪里,所以需要随机读取

System.out.println(raf.getFilePointer());

raf.write('A'); //只写了一个字节(后8位)

System.out.println(raf.getFilePointer());

int i = 0x7fffffff;

//用write方法,每次只能写一个字节,所以得写4次

raf.write(i >>> 24);

raf.write(i >>> 16);

raf.write(i >>> 8);

raf.write(i);

System.out.println(raf.getFilePointer());

//可以直接写一个int

raf.writeInt(i);

String s = "中";

byte[] utf = s.getBytes("gbk");

raf.write(utf);

System.out.println(raf.getFilePointer());

//读文件,必须把指针移到头部

raf.seek(0);

byte[] buf = new byte[(int)raf.length()];

raf.read(buf);

System.out.println(Arrays.toString(buf));

System.out.println(new String(buf, "utf-8"));

for(byte b : buf) {

System.out.print(Integer.toHexString(b & 0xff) + " ");

}

}

View Code

四。字节流的使用

  • IO流(输入流、输出流; 字节流、字符流)
  • InputStream  

    • 抽象了应用程序读取数据的方式;
    • int b = in.read(); //读取一个字节,无符号填充到int的低八位。-1是EOF
    • in.read(byte[] buf) //读取数据填充到字符数组buf中

  • OutputStream

    • 抽象了应用程序写出数据的方式;
    • out.write(int b) //只写出一个byte到流,b的低八位
    • out.write(byte[] buf) //将buf字节数组都写入到流

  • EOF = End 读到-1就读到结尾了
  • FileInputStream —— 具体实现了在文件上读取数据
  • byte类型为8位,int类型为32位,为了避免数据转换错误,通过&0xff 将高位24位清零!

    批量读取与单独读取有什么区别?用批量读取会节省很多时间。

//批量读取,适合大文件

public static void printHexByByteArray(String fileName) throws IOException {

FileInputStream in = new FileInputStream(fileName);

byte[] buf = new byte[20 * 1024];

int bytes = 0;

int j = 1;

while((bytes = in.read(buf, 0, buf.length)) != -1) {

for(int i=0; i<bytes; i++) {

System.out.print(Integer.toHexString(buf[i] & 0xff) + " ");

if(j++ % 10 == 0)

System.out.println();

}

}

}

//单字节读取

/**

* 读取指定文件内容,按照16进制输出到控制台

* 每输出10个byte就换行

* @param fileName

* @throws IOException

*/

public static void printHex(String fileName) throws IOException {

//把文件作为字节流进行读操作

FileInputStream in = new FileInputStream(fileName);

int b;

int i = 0;

while((b = in.read()) != -1) {

if(b <= 0xf)

System.out.print("0");

System.out.print(Integer.toHexString(b) + " ");

if(i++ % 10 == 0)

System.out.println();

}

in.close();

}

View Code

  • DataInputDtream 和 DataOutputStream

    • 对流功能的扩展,可以更加方便的读取int,long,字符等类型数据,如writeInt()/writeDouble()/writeUTF()

  • BufferedInoutStream 和 BufferedOutputStream

    • 这两个类为IO提供了带缓冲区的操作,一般打开文件进行写入或读取操作时,都会加上缓冲,这种流模式提高了IO的性能
    • 从应用程序中把输入放入文件,相当于将一桶水倒入另一个桶中:

      • FileOutputStream的write()方法相当于一滴一滴地把水转移过去;
      • DataOutputStream的writexxx()方法相当于用一个非常小的容器转移;
      • BufferedOutputStream的write()方法相当于用一个更大的容器做缓存,提高性能。

/**

* 通过缓冲区的方法拷贝文件

* @param srcFile

* @param dstFile

* @throws IOException

*/

public static void copyFileByBuffer(File srcFile, File dstFile) throws IOException {

if(!srcFile.exists())

throw new IllegalArgumentException("File: " + srcFile + " not exist.");

if(!srcFile.isFile())

throw new IllegalArgumentException(srcFile + "not a file.");

BufferedInputStream bis = new BufferedInputStream(

new FileInputStream(srcFile));

BufferedOutputStream bos = new BufferedOutputStream(

new FileOutputStream(dstFile));

int c;

while((c = bis.read()) != -1) {

bos.write(c);

bos.flush();

}

bis.close();

bos.close();

}

View Code

五。字符流的使用

  • 文本和文本文件

    • Java中的文本(char)是16位无符号整数,是字符的Unicode编码(双字节编码);
    • 文件是byte byte byte的数据序列;
    • 文本文件是文本(char)序列按照某种编码方案(utf-8, utf-16be,gbk)序列化为byte的存储。

  • 字符流(Reader,Writer)—— 操作的是文本文件

    • 字符的处理,一次处理一个字符;
    • 字符的底层仍然是基本的字节序列。
    • 字符流的基本实现:

      • InputStreamReader 完成byte流解析为char流,按照编码解析
      • OutputStreamReader 提供char流解析为byte流,按照编码处理

    • 文件读写流:

      • FileReader
      • FileWriter

    • 字符流的过滤

      • BufferedReader —— 可以readLine,一次读一行
      • BufferedWriter/Printer —— 写一行

public static void main(String[] args) throws IOException {

BufferedReader br = new BufferedReader(

new InputStreamReader(

new FileInputStream("demo/out.data")));

BufferedWriter bw = new BufferedWriter(

new OutputStreamWriter(

new FileOutputStream("demo/out1.data")));

PrintWriter pw = new PrintWriter("demo/out2.data");

String line;

while((line = br.readLine()) != null) {

System.out.println(line);

pw.println(line);

pw.flush();

// bw.write(line);

// bw.newLine();

// bw.flush();

}

pw.close();

bw.close();

br.close();

}

View Code

六。序列化与反序列化

  • 对象的序列化和反序列化

    • 对象序列化是指将Object对象转化成byte序列,反之叫做对象的反序列化;
    • 序列化流(ObjectOutputStream)是过滤流——writeObject();
    • 反序列化流(ObjectInputStream)—— readObject();
    • 序列化接口(Serializable)

      • 对象必须实现序列化接口才能实现序列化,否则将出现异常,这个接口没有任何方法,只是一个标准。

    • 对象的序列化和反序列化将那些实现了Serialization接口的对象转换成一个字节序列,并能够在以后将这个字节完全恢复为原来的对象;

      • 这样做的一个好处是:能够自动弥补不同操作系统之间的差异。可以在运行Windows操作系统的计算机上创建一个对象,将其序列化,通过网络将它发送给一台运行Unix系统的计算机,然后在那里能够准确的重新组装,而不必担心数据在不同机器上的表示会不同,也不必关心字节的顺序或者其他任何细节;
      • 将序列化的概念加入java中主要是为了支持两种特性:

        • 一是java的远程方法调用(Remote Method Invocation,RMI),它使存活在其他计算机上的对象使用起来就像存活于本机上一样,当向远程对象大宋消息时,需要通过对象序列化来传输参数和返回值;
        • 二是对于java Bean来说,序列化也是必须的。使用一个Bean时,一般情况是在设计阶段对他的状态信息进行配置。这种状态信息必须保存下来,并在程序启东市进行后期恢复。

public static void main(String[] args) throws IOException, IOException {

String file = "demo/obj.txt";

//1. 对象序列化

ObjectOutputStream oos = new ObjectOutputStream(

new FileOutputStream(file));

StudentDemo st = new StudentDemo("aaa", "12", 15);

oos.writeObject(st);

oos.flush();

oos.close();

//2.反序列化

ObjectInputStream pis = new ObjectInputStream(

new FileInputStream(file));

try {

StudentDemo stu = (StudentDemo) pis.readObject();

System.out.println(stu);

} catch (ClassNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

pis.close();

}

View Code

  • transient 修饰后,不会进行虚拟机默认的序列化;也可以自己完成这个元素的序列化

    • 为什么要使用transient关键字呢?
    • 分析ArrayList的序列化与反序列化:实质是一个数组,但是这个数组并不一定放满了,因此我们不需要讲后面没有使用的地方进行序列化,可以根据自己的需要定制序列化,只是序列化数组中的有效元素,提高性能。

  • 序列化中子父类构造函数调用问题

    • 一个类实现了序列化接口,其子类都可以进行序列化
    • 对子类对象进行反序列化操作时,如果其父类没有实现序列化接口,那么其父类的构造函数会被调用;反之不会。

以上是 Java——再看IO 的全部内容, 来源链接: utcz.com/z/390609.html

回到顶部