[学习笔记]Java IO之其他流及总结



1. 概述

  • SequenceInputStream是字节流的包装类,能够提供多个流序列输入功能。
  • 序列流只有输入流,适合完成多个源一个目的的需求。
  • SequenceInputStream支持枚举输入,若源的数量大于2个,那么需要先建立枚举再通过构造器创建序列输入流。
  • SequenceInputStream的使用和其他流基本类似。
  • SequenceInputStream本质属于字节流。

2. 构造器

  • SequenceInputStream(Enumeration<? extends InputStream> e)
  • SequenceInputStream(InputStream s1, InputStream s2)

3. 常用方法

  • int  available()
  • void  close()
  • int  read()
  • int  read(byte[] b, int off, int len)

4. 示例

源文件1:"喜欢",源文件2:"Ja",源文件3:"va"。
   
   
public static void SequenceDemo() throws IOException {
List<FileInputStream> list = new ArrayList<FileInputStream>();
list.add(new FileInputStream( new File("temp\\partfiles\\char1.txt" )));
list.add(new FileInputStream( new File("temp\\partfiles\\char2.txt" )));
list.add(new FileInputStream( new File("temp\\partfiles\\char3.txt" )));
SequenceInputStream sis = new SequenceInputStream(Collections.enumeration( list));
BufferedReader br = new BufferedReader( new InputStreamReader(sis));
String str = null;
while (( str = br. readLine()) != null) {
System.out.println( str);
}
br.close();
}

运行结果

喜欢Java

打印输出流(PrintStream/PrintWriter)

1. 概述

  • PrintStream/PrintWriter能够输出多种数据类型的表现形式,多用于基本数据的打印格式化字符串输出。
  • PrintStream/PrintWriter的目标可以是文件和所有的输出流。
  • PrintStream/PrintWriter永远不会抛出IOException,只能通过调用checkError()检查是否出现错误。
  • PrintStream可以实现换行符自动刷新,PrintWriter仅在调用println、printf或format方法时才可以实现自动刷新。
  • PrintStream保留了原始字节流的输出方式,而PrintWriter仅限于带编码的字符数据。
  • PrintStream打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
  • System.out类型即为PrintStream。

2. 构造器

  • PrintStream:
    PrintStream(File file)
    PrintStream(File file, String csn)
    PrintStream(OutputStream out)
    PrintStream(OutputStream out, boolean autoFlush)
    PrintStream(OutputStream out, boolean autoFlush, String encoding)
    PrintStream(String fileName)
    PrintStream(String fileName, String csn)
  • PrintWriter:
    PrintWriter(File file)
    PrintWriter(File file, String csn)
    PrintWriter(OutputStream out)
    PrintWriter(OutputStream out, boolean autoFlush)
    PrintWriter(String fileName)
    PrintWriter(String fileName, String csn)
    PrintWriter(Writer out)
    PrintWriter(Writer out, boolean autoFlush)

3. 常用方法

PrintStream和PrintWriter流的方法高度类似。
  • PrintStream  append(...)
  • boolean  checkError()
  • protected void  clearError()
  • void  close()
  • void  flush()
  • PrintStream  format(String format, Object... args)
  • void  print(...)
  • PrintStream  printf(String format, Object... args)
  • void  println(...)
  • protected void  setError()
  • void  write(...)

4. 示例

   
   
public static void printWrite() throws IOException {
BufferedReader br = new BufferedReader( new FileReader("temp\\char.txt" ));
PrintStream ps = new PrintStream(System.out, true );
String line = null;
while (( line = br.readLine()) != null) {
ps.println(line.toUpperCase());
}
br.close();
}

标准输入输出流(System.in/System.out)

1. 概述

  • Java中内置了标准输入输出流,以静态域的形式提供在System类中。
  • 标准输入流System.in默认为键盘,标准输出流System.out默认为控制台。
  • 标准输入流为字节流InputStream类型,标准输出流为打印流PrintStream类型。
  • 标准输入输出流可以被其他流装饰类包装,以实现更多功能。
  • 标准输入输出流不需要关流。

2. 示例

   
   
package io;
 
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
 
public class KeyPrintDemo {
 
public static void main(String[] args) throws IOException {
// 需求:读取键盘上录入的字符,显示并以 utf-8编码保存成文件
BufferedReader br = new BufferedReader( new InputStreamReader(System.in ));
BufferedWriter bw =
new BufferedWriter( new OutputStreamWriter( new FileOutputStream("temp\\key.txt" ), "utf-8" ));
String str = null;
while (( str = br.readLine()) != null) {
if ("exit".equals( str)) {
break;
}
System.out.println( str);
bw.write(str);
bw.newLine();
bw.flush();
}
bw.close();
}
}

对象输入输出流(ObjectInputStream/ObjectOutputStream)

1. 概述

  • 目标是字节流。
  • 将基本数据或对象转化为数据流的过程被称之为序列化,而反序列化就是相反的过程。
  • ObjectInputStream/ObjectOutputStream的作用就是基本数据类型或对象的序列化和反序列化。
  • ObjectInputStream确保从流创建的图形中所有对象的类型与Java虚拟机中显示的类相匹配。
  • 只有实现了java.io.Serializable或java.io.Externalizable接口的对象才能被序列化或反序列化。
  • 在Java中,字符串和数组都是对象,所以在序列化期间将其视为对象,读取时,需要将其强制转换为期望的类型。
  • 序列化和反序列化进程将忽略声明为瞬态或静态的字段。
  • readObject()及其他非父类读取方法的读取是无法判断文件末尾的,所以需要确定读取次数,以免出现EOFException。

2. 序列化接口Serializable

  • Serializable没有方法,不需要覆盖,是一个标记接口。
  • Serializable启动一个序列化功能,给每一个需要序列化的类都分配一个序列版本号。
  • 实现了Serializable的类需要定义序列版本号serialVersionUID,该号和该类相关联。
  • serialVersionUID用于序列化过程中的验证。在序列化时,会将这个序列号也一同保存到文件中。
    在反序列化会读取这个序列化和本类的序列化进行匹配,如果不匹配会抛出异常:java.io.InvalidClassException
  • 强烈建议:显式声明serialVersionUID,因为默认serialVersionUID的计算是对类的细节高度敏感的,所以可能会有不同平台
    计算默认的的serialVersionUID值有所不同的情况出现,可能就会发生不可预期的InvalidClassExceptions异常。
  • 强烈建议:使用private关键字修饰声明serialVersionUID,因为serialVersionUID只对当前类有效,对子类是毫无意义的。

3. 构造器

  • ObjectInputStream()
  • ObjectInputStream(InputStream in)
  • ObjectOutputStream()
  • ObjectOutputStream(OutputStream out)

4. 常用方法

  • ObjectInputStream:
    boolean  readBoolean()
    byte  readByte()
    char  readChar()
    double  readDouble()
    float  readFloat()
    void  readFully(byte[] buf)
    void  readFully(byte[] buf, int off, int len)
    int  readInt()
    long  readLong()
    Object  readObject()
    short  readShort()
  • ObjectOutputStream:
    void  write(byte[] buf)
    void  write(byte[] buf, int off, int len)
    void  writeBoolean(boolean val)
    void  writeByte(int val)
    void  writeBytes(String str)
    void  writeChar(int val)
    void  writeChars(String str)
    void  writeDouble(double val)
    void  writeFields()
    void  writeFloat(float val)
    void  writeInt(int val)
    void  writeLong(long val)
    void  writeObject(Object obj)
    void  writeShort(int val)

5. 示例

Employee.java
   
   
package bean;
 
public class Employee extends Person {
 
private static final long serialVersionUID = 201411261L;
 
/*
* transient: 瞬态,被该修饰符修饰的变量将不被序列化。同样地,静态变量也不被序列化。
*/
private transient int salary ;
 
public Employee(String name, int age, int salary) {
super(name, age);
this.salary = salary;
}
 
public Employee() {
super();
}
 
public int getSalary() {
return salary;
}
 
public void setSalary( int salary) {
this.salary = salary;
}
 
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + salary;
return result;
}
 
@Override
public boolean equals(Object obj ) {
if (this == obj) return true ;
if (!super.equals( obj)) return false ;
if (getClass() != obj.getClass()) return false ;
Employee other = (Employee) obj;
if (salary != other.salary) return false;
return true;
}
 
@Override
public String toString() {
 
return "Employee [Name=" + getName() + ", Age=" + getAge() + ", Salary=" + salary + "]" ;
}
 
public static void staticPrint() {
System.out.println( "Static print run!");
}
}

ObjectReader/Writer
   
   
public static Object objectRead() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream( new FileInputStream("temp\\os.txt" ));
Object obj = ois.readObject();
ois.close();
return obj;
}
 
public static void objectWrite() throws IOException {
Employee employee = new Employee( "Java", 25, 25000);
ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("temp\\os.txt" ));
oos.writeObject( employee);
oos.close();
}

数据输入输出流(DataInputStream/DataOutputStream)

1. 概述

  • 目标为字节流。
  • 用于基本数据的内存原始数据的输入输出。
  • 该类特有的数据读取方法无法检测流的末尾,所以需要确定读取次数,以免出现EOFException异常。

2. 示例

   
   
public static int dataRead() throws IOException {
DataInputStream dis = new DataInputStream( new FileInputStream("temp\\data.txt" ));
int num = dis.readInt();
dis.close();
return num;
}
 
public static void dataWrite() throws IOException {
DataOutputStream dos = new DataOutputStream( new FileOutputStream("temp\\data.txt" ));
dos.writeInt(97); // 写入4字节:00000000 00000000 00000000 01100001
dos.write(97); // 写入1字节
dos.close();
}

数组输入输出流(ByteArrayInputStream/ByteArrayOutputStream)

1. 概述

  • 目标为内存数组,本质为字节流。
  • 该流是为了按流的方式处理数据,某些情况下将会更加方便。
  • 由于没有调用底层资源,所以该流不需要关闭。
  • 创建ByteArrayInputStream流对象时需要将字节数组作为数据源参数传入,而ByteArrayOutputStream流对象中维护了一个大数组用于存储数据。
  • 该流的方法与其他流类似,ByteArrayOutputStream流使用toByteArray()方法返回保存数据的数组。

2. 特有方法

ByteArrayOutputStream:
  • 将内置数组数据输出到一个流中
    void  writeTo(OutputStream out)
  • 将内置数组数据输出到一个数组中
    byte[]  toByteArray()
  • 缓冲区大小
    int  size()
  • String  toString()
    String  toString(String charsetName)

3. 示例

   
   
private static byte[] byteArray(byte[] b) {
ByteArrayInputStream bis = new ByteArrayInputStream(b);
ByteArrayOutputStream bos = new ByteArrayOutputStream(); // 内部有一个可自动增长的数组。
int ch = 0;
while(( ch= bis.read())!=-1){
bos.write(ch);
}
//因为没有调用底层资源,所以不要关闭,即使调用了close,也没有任何效果,不会抛出IOException。
return bos.toByteArray();
}

文件随机访问类(RandomAccessFile)

1. 概述

  • 目标为文件,可以实现字节流字符流的读写。
  • 提供了文件指针,可以进行随机读写,就类似存储在文件系统中的一个大型 byte 数组。
  • 创建对象时可选模式:"r"只读, "rw"读写, "rws"读写并将更新的内容和元数据同步到设备, 或者"rwd"读写并将更新的内容同步到设备。

2. "rw", "rws", "rwd" 的区别

  • 当操作的文件是存储在本地的基础存储设备上时(如硬盘, NandFlash等),"rws" 或 "rwd", "rw" 才有区别。
  • 当模式是 "rws" 并且操作的是基础存储设备上的文件;那么,每次“更改文件内容[如write()写入数据]” 或 “修改文件元数据(如文件的mtime)”时,都会将这些改变同步到基础存储设备上。
  • 当模式是 "rwd" 并且操作的是基础存储设备上的文件;那么,每次“更改文件内容[如write()写入数据]”时,都会将这些改变同步到基础存储设备上。
  • 当模式是 "rw" 并且操作的是基础存储设备上的文件;那么,关闭文件时,会将“文件内容的修改”同步到基础存储设备上。至于,“更改文件内容”时,是否会立即同步,取决于系统底层实现。

3. 构造器

  • RandomAccessFile(File file, String mode)
  • RandomAccessFile(String name, String mode)

4. 常用方法

  • void  close()
  • 获取文件指针
    long  getFilePointer()
  • 获取文件长度
    long  length()
  • int  read()
    int  read(byte[] b)
    int  read(byte[] b, int off, int len)
    boolean  readBoolean()
    byte  readByte()
    char  readChar()
    double  readDouble()
    float  readFloat()
    void  readFully(byte[] b)
    void  readFully(byte[] b, int off, int len)
    int  readInt()
    String  readLine()
    long  readLong()
    short  readShort()
    int  readUnsignedByte()
    int  readUnsignedShort()
    String  readUTF()
  • 设置文件指针
    void  seek(long pos)
  • 设置文件长度
    void  setLength(long newLength)
  • 跳过字节数
    int  skipBytes(int n)
  • void  write(byte[] b)
    void  write(byte[] b, int off, int len)
    void  write(int b)
    void  writeBoolean(boolean v)
    void  writeByte(int v)
    void  writeBytes(String s)
    void  writeChar(int v)
    void  writeChars(String s)
    void  writeDouble(double v)
    void  writeFloat(float v)
    void  writeInt(int v)
    void  writeLong(long v)
    void  writeShort(int v)
    void  writeUTF(String str)

5. 示例

   
   
public static void readFile() throws IOException {
RandomAccessFile raf = new RandomAccessFile("temp\\random.txt" , "r" );
raf.seek(4);
int i = raf.readInt();
System.out.println( i);
raf.close();
}
 
public static void writeFile() throws IOException {
RandomAccessFile raf = new RandomAccessFile("temp\\random.txt" , "rw" );
raf.seek(4);
raf.writeInt(102);
System.out.println( raf.getFilePointer());
raf.close();
}

案例

文件的切割与合并

1. 使用配置文件的方案

   
   
package io;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
 
/**
* 使用配置文件的方法分割合并文件
*/
public class TSplitJoinByConfig {
private static final int BUFFER_SIZE = 4096;
 
/**
* 以配置文件的方法分割文件
* @param srcFile 需要分割的文件
* @param destPath 分割文件的保存目录
* @param len 分割文件的大小
* @throws IOException
*/
public void split(File srcFile, File destPath, long len) throws IOException {
// 健壮性判断,源文件目的目录是否存在
if (!srcFile.exists() || srcFile.isDirectory()) {
throw new RuntimeException( "源文件不存在!" );
}
if (len <= 1024) {
throw new RuntimeException( "分卷大小设置太小或错误!" );
}
if (!destPath.exists() || destPath.isFile()) {
destPath.mkdirs();
}
// 定义缓冲区
byte[] buf = new byte[ BUFFER_SIZE];
// 定义分割文件序号
int num = 0;
// 定义缓冲区数据大小
int bufLen = 0;
// 定义单个分割文件剩余的写入字节数
long splitLen = len;
// 源文件名
String fileName = srcFile.getName();
FileInputStream fis = new FileInputStream( srcFile);
FileOutputStream fos = null;
 
while ( bufLen != -1) {
// 单趟外层循环完成一个分割文件的写入
splitLen = len;
// 创建输出流,确定分割文件名
fos = new FileOutputStream( new File( destPath, fileName + ".part" + String.format( "%02d", ++num )));
while ( splitLen > 0) {
// splitLen为该单个文件剩余的写入字节数,以此为依据来从输入流读取数据到缓冲区
if ( splitLen >= BUFFER_SIZE) {
bufLen = fis.read( buf);
} else {
bufLen = fis.read( buf, 0, ( int) splitLen);
}
if ( bufLen == -1) {
break;
}
fos.write( buf, 0, bufLen);
splitLen -= ( long) bufLen;
}
fos.close();
}
fis.close();
// 将文件名和分割文件数写入配置文件
Properties prop = new Properties();
prop.setProperty( "FileName", srcFile .getName());
prop.setProperty( "PartNo.", String.valueOf( num));
FileWriter fw = new FileWriter( new File( destPath, srcFile.getName() + ".properties" ));
prop.store(fw, "Split File Config");
fw.close();
}
public void join(File splitFirFile) throws IOException {
// 健壮性判断
if (!splitFirFile.exists() || splitFirFile.isDirectory()) {
throw new RuntimeException( "分割文件不存在!" );
}
// 分割文件名(不包括扩展名)
String splitFileName = splitFirFile.getName().substring(0, splitFirFile.getName().length() - 7);
// 分割文件目录
File splitFilePath = splitFirFile.getParentFile();
// 配置文件
File propFile = new File( splitFilePath, splitFileName + ".properties");
if (!propFile.exists() || propFile.isDirectory()) {
throw new RuntimeException( "配置文件不存在!" );
}
// 读取配置文件
Properties prop = new Properties();
FileReader fr = new FileReader( propFile);
prop.load(fr);
fr.close();
// 合并文件名
String fileName = prop.getProperty( "FileName");
int num = Integer.parseInt(prop.getProperty( "PartNo."));
// 判断分割文件是否完整
File[] splitFiles = new File[ num];
for (int i = 1; i <= num; i++) {
splitFiles[i - 1] = new File( splitFilePath, splitFileName + ".part" + String.format( "%02d", i ));
if (!splitFiles[i - 1].exists() || splitFiles[ i - 1].isDirectory()) {
throw new RuntimeException( "缺少分割文件:" + splitFiles[i - 1].getName());
}
}
// 将流加入集合,并使用序列输入流完成合并
List<FileInputStream> list = new ArrayList<FileInputStream>();
for (int i = 1; i <= num; i++) {
list.add(new FileInputStream( splitFiles[ i - 1]));
}
byte[] buf = new byte[ BUFFER_SIZE];
int len = -1;
SequenceInputStream sis = new SequenceInputStream(Collections.enumeration( list));
FileOutputStream fos = new FileOutputStream( new File(splitFilePath, fileName ));
while (( len = sis.read( buf)) != -1) {
fos.write(buf, 0, len);
}
fos.close();
sis.close();
}
}

2. 不使用配置文件的方案

   
   
package io;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
public class TSplitJoin {
private static final int BUFFER_SIZE = 4096;
private static final long PROPERTIES_SIZE = 1024;
 
/**
* 分割文件,必要信息存储到第一个文件中
* @param srcFile 需要分割的文件
* @param destPath 分割文件的保存目录
* @param len 分割文件的大小
* @throws IOException
*/
public void split(File srcFile, File destPath, long len) throws IOException {
// 健壮性判断,源文件目的目录是否存在
if (!srcFile.exists() || srcFile.isDirectory()) {
throw new RuntimeException( "源文件不存在!" );
}
if (len <= 4096) {
throw new RuntimeException( "分卷大小设置太小或者错误!" );
}
if (!destPath.exists() || destPath.isFile()) {
destPath.mkdirs();
}
// 定义缓冲区
byte[] buf = new byte[ BUFFER_SIZE];
// 定义分割文件序号
int num = 0;
// 定义写入缓冲区数据大小
int bufLen = 0;
// 定义单个分割文件剩余的写入字节数
long splitLen = len - PROPERTIES_SIZE;
// 源文件名
String fileName = srcFile.getName();
FileInputStream fis = new FileInputStream( srcFile);
FileOutputStream fos = new FileOutputStream( new File(destPath, fileName + ".part01" ));
String content = fileName + "\n" + (int)Math.ceil(( double)srcFile .length() / (double) len) + "\n";
System.arraycopy(content .getBytes("utf-8" ), 0, buf , 0, content.getBytes().length );
fos.write(buf, 0, (int)PROPERTIES_SIZE);
 
while ( bufLen != -1) {
// 单趟外层循环完成一个分割文件的写入
// 创建输出流,确定分割文件名
if (num != 0) {
fos = new FileOutputStream( new File( destPath, fileName + ".part" + String.format( "%02d", ++num )));
} else {
num++;
}
while ( splitLen > 0) {
// splitLen为该单个文件剩余的写入字节数,以此为依据来从输入流读取数据到缓冲区
if ( splitLen >= BUFFER_SIZE) {
bufLen = fis.read( buf);
} else {
bufLen = fis.read( buf, 0, ( int) splitLen);
}
if ( bufLen == -1) {
break;
}
fos.write( buf, 0, bufLen);
splitLen -= ( long) bufLen;
}
fos.close();
splitLen = len;
}
fis.close();
}
public void join(File splitFirFile) throws IOException {
// 健壮性判断
if (!splitFirFile.exists() || splitFirFile.isDirectory()) {
throw new RuntimeException( "分割文件不存在!" );
}
// 分割文件名(不包括扩展名)
String splitFileName = splitFirFile.getName().substring(0, splitFirFile.getName().length() - 7);
// 分割文件目录
File splitFilePath = splitFirFile.getParentFile();
 
// 读取配置信息
byte[] buf = new byte[ BUFFER_SIZE];
FileInputStream fis = new FileInputStream(splitFirFile );
fis.read(buf, 0, (int)PROPERTIES_SIZE);
fis.close();
String[] prop = new String( buf, "utf-8").split("\n" );
String fileName = prop[0];
int num = Integer.parseInt(prop [1]);
// 判断分割文件是否完整
File[] splitFiles = new File[ num];
for (int i = 1; i <= num; i++) {
splitFiles[i - 1] = new File( splitFilePath, splitFileName + ".part" + String.format( "%02d", i ));
if (!splitFiles[i - 1].exists() || splitFiles[ i - 1].isDirectory()) {
throw new RuntimeException( "缺少分割文件:" + splitFiles[i - 1].getName());
}
}
// 将流加入集合,并使用序列输入流完成合并
List<FileInputStream> list = new ArrayList<FileInputStream>();
for (int i = 1; i <= num; i++) {
list.add(new FileInputStream( splitFiles[ i - 1]));
}
int len = -1;
SequenceInputStream sis = new SequenceInputStream(Collections.enumeration( list));
FileOutputStream fos = new FileOutputStream( new File(splitFilePath, fileName ));
sis.skip(PROPERTIES_SIZE);
while (( len = sis.read( buf)) != -1) {
fos.write(buf, 0, len);
}
fos.close();
sis.close();
}
}

流总结

1. IO流框架继承结构

字节流
InputStream/OutputStream
    |--FileInputStream/FileOutputStream
    |--FilterInputStream/FilterOutputStream
        |--BufferedInputStream/BufferedOutputStream
        |--DataInputStream/DataOutputStream
        |--PrintStream
    |--ObjectInputStream/ObjectOutputStream
    |--ByteArrayInputStream/ByteArrayOutputStream
    |--SequenceInputStream

字符流
Reader/Writer
    |--InputStreamReader/OutputStreamWriter
        |--FileReader/FileWriter
    |--BufferedReader/BufferedWriter
    |--CharArrayReader/CharArrayWriter
    |--StringReader/StringWriter
    |--PrintWriter

2. IO流要素

目标
  • 源:InputStream/Reader及其子类
  • 目的:OutputStream/Writer及其子类
内容
  • 字节流:InputStream/OutputStream及其子类
  • 字符流:Reader/Writer及其子类
设备
  • 外存:FileInputStream/FileOutputStream、FileReader/FileWriter
  • 内存:ByteArrayInputStream/ByteArrayOutputStream、CharArrayReader/CharArrayWriter、StringReader/StringWriter
  • 键盘和控制台:System.in/System.out
  • 网络:Socket

附加功能(装饰类)

  • 转换:InputStreamReader/OutputStreamWriter(字节流)
  • 缓冲区:BufferedInputStream/BufferedOutputStream(字节流)、BufferedReader/BufferedWriter(字符流)
  • 多源:SequenceInputStream(字节流)
  • 序列化:ObjectInputStream/ObjectOutputStream(字节流)
  • 保证数据的表现形式:PrintStream(字节流,文件)、PrintWriter(字节流,字符流,文件)
  • 保证数据原始字节:DataInputStream/DataOutputStream(字节流)

1. 概述

  • SequenceInputStream是字节流的包装类,能够提供多个流序列输入功能。
  • 序列流只有输入流,适合完成多个源一个目的的需求。
  • SequenceInputStream支持枚举输入,若源的数量大于2个,那么需要先建立枚举再通过构造器创建序列输入流。
  • SequenceInputStream的使用和其他流基本类似。
  • SequenceInputStream本质属于字节流。

2. 构造器

  • SequenceInputStream(Enumeration<? extends InputStream> e)
  • SequenceInputStream(InputStream s1, InputStream s2)

3. 常用方法

  • int  available()
  • void  close()
  • int  read()
  • int  read(byte[] b, int off, int len)

4. 示例

源文件1:"喜欢",源文件2:"Ja",源文件3:"va"。
    
    
public static void SequenceDemo() throws IOException {
List<FileInputStream> list = new ArrayList<FileInputStream>();
list.add(new FileInputStream( new File("temp\\partfiles\\char1.txt" )));
list.add(new FileInputStream( new File("temp\\partfiles\\char2.txt" )));
list.add(new FileInputStream( new File("temp\\partfiles\\char3.txt" )));
SequenceInputStream sis = new SequenceInputStream(Collections.enumeration( list));
BufferedReader br = new BufferedReader( new InputStreamReader(sis));
String str = null;
while (( str = br. readLine()) != null) {
System.out.println( str);
}
br.close();
}

运行结果

喜欢Java

打印输出流(PrintStream/PrintWriter)

1. 概述

  • PrintStream/PrintWriter能够输出多种数据类型的表现形式,多用于基本数据的打印格式化字符串输出。
  • PrintStream/PrintWriter的目标可以是文件和所有的输出流。
  • PrintStream/PrintWriter永远不会抛出IOException,只能通过调用checkError()检查是否出现错误。
  • PrintStream可以实现换行符自动刷新,PrintWriter仅在调用println、printf或format方法时才可以实现自动刷新。
  • PrintStream保留了原始字节流的输出方式,而PrintWriter仅限于带编码的字符数据。
  • PrintStream打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
  • System.out类型即为PrintStream。

2. 构造器

  • PrintStream:
    PrintStream(File file)
    PrintStream(File file, String csn)
    PrintStream(OutputStream out)
    PrintStream(OutputStream out, boolean autoFlush)
    PrintStream(OutputStream out, boolean autoFlush, String encoding)
    PrintStream(String fileName)
    PrintStream(String fileName, String csn)
  • PrintWriter:
    PrintWriter(File file)
    PrintWriter(File file, String csn)
    PrintWriter(OutputStream out)
    PrintWriter(OutputStream out, boolean autoFlush)
    PrintWriter(String fileName)
    PrintWriter(String fileName, String csn)
    PrintWriter(Writer out)
    PrintWriter(Writer out, boolean autoFlush)

3. 常用方法

PrintStream和PrintWriter流的方法高度类似。
  • PrintStream  append(...)
  • boolean  checkError()
  • protected void  clearError()
  • void  close()
  • void  flush()
  • PrintStream  format(String format, Object... args)
  • void  print(...)
  • PrintStream  printf(String format, Object... args)
  • void  println(...)
  • protected void  setError()
  • void  write(...)

4. 示例

    
    
public static void printWrite() throws IOException {
BufferedReader br = new BufferedReader( new FileReader("temp\\char.txt" ));
PrintStream ps = new PrintStream(System.out, true );
String line = null;
while (( line = br.readLine()) != null) {
ps.println(line.toUpperCase());
}
br.close();
}

标准输入输出流(System.in/System.out)

1. 概述

  • Java中内置了标准输入输出流,以静态域的形式提供在System类中。
  • 标准输入流System.in默认为键盘,标准输出流System.out默认为控制台。
  • 标准输入流为字节流InputStream类型,标准输出流为打印流PrintStream类型。
  • 标准输入输出流可以被其他流装饰类包装,以实现更多功能。
  • 标准输入输出流不需要关流。

2. 示例

    
    
package io;
 
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
 
public class KeyPrintDemo {
 
public static void main(String[] args) throws IOException {
// 需求:读取键盘上录入的字符,显示并以 utf-8编码保存成文件
BufferedReader br = new BufferedReader( new InputStreamReader(System.in ));
BufferedWriter bw =
new BufferedWriter( new OutputStreamWriter( new FileOutputStream("temp\\key.txt" ), "utf-8" ));
String str = null;
while (( str = br.readLine()) != null) {
if ("exit".equals( str)) {
break;
}
System.out.println( str);
bw.write(str);
bw.newLine();
bw.flush();
}
bw.close();
}
}

对象输入输出流(ObjectInputStream/ObjectOutputStream)

1. 概述

  • 目标是字节流。
  • 将基本数据或对象转化为数据流的过程被称之为序列化,而反序列化就是相反的过程。
  • ObjectInputStream/ObjectOutputStream的作用就是基本数据类型或对象的序列化和反序列化。
  • ObjectInputStream确保从流创建的图形中所有对象的类型与Java虚拟机中显示的类相匹配。
  • 只有实现了java.io.Serializable或java.io.Externalizable接口的对象才能被序列化或反序列化。
  • 在Java中,字符串和数组都是对象,所以在序列化期间将其视为对象,读取时,需要将其强制转换为期望的类型。
  • 序列化和反序列化进程将忽略声明为瞬态或静态的字段。
  • readObject()及其他非父类读取方法的读取是无法判断文件末尾的,所以需要确定读取次数,以免出现EOFException。

2. 序列化接口Serializable

  • Serializable没有方法,不需要覆盖,是一个标记接口。
  • Serializable启动一个序列化功能,给每一个需要序列化的类都分配一个序列版本号。
  • 实现了Serializable的类需要定义序列版本号serialVersionUID,该号和该类相关联。
  • serialVersionUID用于序列化过程中的验证。在序列化时,会将这个序列号也一同保存到文件中。
    在反序列化会读取这个序列化和本类的序列化进行匹配,如果不匹配会抛出异常:java.io.InvalidClassException
  • 强烈建议:显式声明serialVersionUID,因为默认serialVersionUID的计算是对类的细节高度敏感的,所以可能会有不同平台
    计算默认的的serialVersionUID值有所不同的情况出现,可能就会发生不可预期的InvalidClassExceptions异常。
  • 强烈建议:使用private关键字修饰声明serialVersionUID,因为serialVersionUID只对当前类有效,对子类是毫无意义的。

3. 构造器

  • ObjectInputStream()
  • ObjectInputStream(InputStream in)
  • ObjectOutputStream()
  • ObjectOutputStream(OutputStream out)

4. 常用方法

  • ObjectInputStream:
    boolean  readBoolean()
    byte  readByte()
    char  readChar()
    double  readDouble()
    float  readFloat()
    void  readFully(byte[] buf)
    void  readFully(byte[] buf, int off, int len)
    int  readInt()
    long  readLong()
    Object  readObject()
    short  readShort()
  • ObjectOutputStream:
    void  write(byte[] buf)
    void  write(byte[] buf, int off, int len)
    void  writeBoolean(boolean val)
    void  writeByte(int val)
    void  writeBytes(String str)
    void  writeChar(int val)
    void  writeChars(String str)
    void  writeDouble(double val)
    void  writeFields()
    void  writeFloat(float val)
    void  writeInt(int val)
    void  writeLong(long val)
    void  writeObject(Object obj)
    void  writeShort(int val)

5. 示例

Employee.java
    
    
package bean;
 
public class Employee extends Person {
 
private static final long serialVersionUID = 201411261L;
 
/*
* transient: 瞬态,被该修饰符修饰的变量将不被序列化。同样地,静态变量也不被序列化。
*/
private transient int salary ;
 
public Employee(String name, int age, int salary) {
super(name, age);
this.salary = salary;
}
 
public Employee() {
super();
}
 
public int getSalary() {
return salary;
}
 
public void setSalary( int salary) {
this.salary = salary;
}
 
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + salary;
return result;
}
 
@Override
public boolean equals(Object obj ) {
if (this == obj) return true ;
if (!super.equals( obj)) return false ;
if (getClass() != obj.getClass()) return false ;
Employee other = (Employee) obj;
if (salary != other.salary) return false;
return true;
}
 
@Override
public String toString() {
 
return "Employee [Name=" + getName() + ", Age=" + getAge() + ", Salary=" + salary + "]" ;
}
 
public static void staticPrint() {
System.out.println( "Static print run!");
}
}

ObjectReader/Writer
    
    
public static Object objectRead() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream( new FileInputStream("temp\\os.txt" ));
Object obj = ois.readObject();
ois.close();
return obj;
}
 
public static void objectWrite() throws IOException {
Employee employee = new Employee( "Java", 25, 25000);
ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("temp\\os.txt" ));
oos.writeObject( employee);
oos.close();
}

数据输入输出流(DataInputStream/DataOutputStream)

1. 概述

  • 目标为字节流。
  • 用于基本数据的内存原始数据的输入输出。
  • 该类特有的数据读取方法无法检测流的末尾,所以需要确定读取次数,以免出现EOFException异常。

2. 示例

    
    
public static int dataRead() throws IOException {
DataInputStream dis = new DataInputStream( new FileInputStream("temp\\data.txt" ));
int num = dis.readInt();
dis.close();
return num;
}
 
public static void dataWrite() throws IOException {
DataOutputStream dos = new DataOutputStream( new FileOutputStream("temp\\data.txt" ));
dos.writeInt(97); // 写入4字节:00000000 00000000 00000000 01100001
dos.write(97); // 写入1字节
dos.close();
}

数组输入输出流(ByteArrayInputStream/ByteArrayOutputStream)

1. 概述

  • 目标为内存数组,本质为字节流。
  • 该流是为了按流的方式处理数据,某些情况下将会更加方便。
  • 由于没有调用底层资源,所以该流不需要关闭。
  • 创建ByteArrayInputStream流对象时需要将字节数组作为数据源参数传入,而ByteArrayOutputStream流对象中维护了一个大数组用于存储数据。
  • 该流的方法与其他流类似,ByteArrayOutputStream流使用toByteArray()方法返回保存数据的数组。

2. 特有方法

ByteArrayOutputStream:
  • 将内置数组数据输出到一个流中
    void  writeTo(OutputStream out)
  • 将内置数组数据输出到一个数组中
    byte[]  toByteArray()
  • 缓冲区大小
    int  size()
  • String  toString()
    String  toString(String charsetName)

3. 示例

    
    
private static byte[] byteArray(byte[] b) {
ByteArrayInputStream bis = new ByteArrayInputStream(b);
ByteArrayOutputStream bos = new ByteArrayOutputStream(); // 内部有一个可自动增长的数组。
int ch = 0;
while(( ch= bis.read())!=-1){
bos.write(ch);
}
//因为没有调用底层资源,所以不要关闭,即使调用了close,也没有任何效果,不会抛出IOException。
return bos.toByteArray();
}

文件随机访问类(RandomAccessFile)

1. 概述

  • 目标为文件,可以实现字节流字符流的读写。
  • 提供了文件指针,可以进行随机读写,就类似存储在文件系统中的一个大型 byte 数组。
  • 创建对象时可选模式:"r"只读, "rw"读写, "rws"读写并将更新的内容和元数据同步到设备, 或者"rwd"读写并将更新的内容同步到设备。

2. "rw", "rws", "rwd" 的区别

  • 当操作的文件是存储在本地的基础存储设备上时(如硬盘, NandFlash等),"rws" 或 "rwd", "rw" 才有区别。
  • 当模式是 "rws" 并且操作的是基础存储设备上的文件;那么,每次“更改文件内容[如write()写入数据]” 或 “修改文件元数据(如文件的mtime)”时,都会将这些改变同步到基础存储设备上。
  • 当模式是 "rwd" 并且操作的是基础存储设备上的文件;那么,每次“更改文件内容[如write()写入数据]”时,都会将这些改变同步到基础存储设备上。
  • 当模式是 "rw" 并且操作的是基础存储设备上的文件;那么,关闭文件时,会将“文件内容的修改”同步到基础存储设备上。至于,“更改文件内容”时,是否会立即同步,取决于系统底层实现。

3. 构造器

  • RandomAccessFile(File file, String mode)
  • RandomAccessFile(String name, String mode)

4. 常用方法

  • void  close()
  • 获取文件指针
    long  getFilePointer()
  • 获取文件长度
    long  length()
  • int  read()
    int  read(byte[] b)
    int  read(byte[] b, int off, int len)
    boolean  readBoolean()
    byte  readByte()
    char  readChar()
    double  readDouble()
    float  readFloat()
    void  readFully(byte[] b)
    void  readFully(byte[] b, int off, int len)
    int  readInt()
    String  readLine()
    long  readLong()
    short  readShort()
    int  readUnsignedByte()
    int  readUnsignedShort()
    String  readUTF()
  • 设置文件指针
    void  seek(long pos)
  • 设置文件长度
    void  setLength(long newLength)
  • 跳过字节数
    int  skipBytes(int n)
  • void  write(byte[] b)
    void  write(byte[] b, int off, int len)
    void  write(int b)
    void  writeBoolean(boolean v)
    void  writeByte(int v)
    void  writeBytes(String s)
    void  writeChar(int v)
    void  writeChars(String s)
    void  writeDouble(double v)
    void  writeFloat(float v)
    void  writeInt(int v)
    void  writeLong(long v)
    void  writeShort(int v)
    void  writeUTF(String str)

5. 示例

    
    
public static void readFile() throws IOException {
RandomAccessFile raf = new RandomAccessFile("temp\\random.txt" , "r" );
raf.seek(4);
int i = raf.readInt();
System.out.println( i);
raf.close();
}
 
public static void writeFile() throws IOException {
RandomAccessFile raf = new RandomAccessFile("temp\\random.txt" , "rw" );
raf.seek(4);
raf.writeInt(102);
System.out.println( raf.getFilePointer());
raf.close();
}

案例

文件的切割与合并

1. 使用配置文件的方案

    
    
package io;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
 
/**
* 使用配置文件的方法分割合并文件
*/
public class TSplitJoinByConfig {
private static final int BUFFER_SIZE = 4096;
 
/**
* 以配置文件的方法分割文件
* @param srcFile 需要分割的文件
* @param destPath 分割文件的保存目录
* @param len 分割文件的大小
* @throws IOException
*/
public void split(File srcFile, File destPath, long len) throws IOException {
// 健壮性判断,源文件目的目录是否存在
if (!srcFile.exists() || srcFile.isDirectory()) {
throw new RuntimeException( "源文件不存在!" );
}
if (len <= 1024) {
throw new RuntimeException( "分卷大小设置太小或错误!" );
}
if (!destPath.exists() || destPath.isFile()) {
destPath.mkdirs();
}
// 定义缓冲区
byte[] buf = new byte[ BUFFER_SIZE];
// 定义分割文件序号
int num = 0;
// 定义缓冲区数据大小
int bufLen = 0;
// 定义单个分割文件剩余的写入字节数
long splitLen = len;
// 源文件名
String fileName = srcFile.getName();
FileInputStream fis = new FileInputStream( srcFile);
FileOutputStream fos = null;
 
while ( bufLen != -1) {
// 单趟外层循环完成一个分割文件的写入
splitLen = len;
// 创建输出流,确定分割文件名
fos = new FileOutputStream( new File( destPath, fileName + ".part" + String.format( "%02d", ++num )));
while ( splitLen > 0) {
// splitLen为该单个文件剩余的写入字节数,以此为依据来从输入流读取数据到缓冲区
if ( splitLen >= BUFFER_SIZE) {
bufLen = fis.read( buf);
} else {
bufLen = fis.read( buf, 0, ( int) splitLen);
}
if ( bufLen == -1) {
break;
}
fos.write( buf, 0, bufLen);
splitLen -= ( long) bufLen;
}
fos.close();
}
fis.close();
// 将文件名和分割文件数写入配置文件
Properties prop = new Properties();
prop.setProperty( "FileName", srcFile .getName());
prop.setProperty( "PartNo.", String.valueOf( num));
FileWriter fw = new FileWriter( new File( destPath, srcFile.getName() + ".properties" ));
prop.store(fw, "Split File Config");
fw.close();
}
public void join(File splitFirFile) throws IOException {
// 健壮性判断
if (!splitFirFile.exists() || splitFirFile.isDirectory()) {
throw new RuntimeException( "分割文件不存在!" );
}
// 分割文件名(不包括扩展名)
String splitFileName = splitFirFile.getName().substring(0, splitFirFile.getName().length() - 7);
// 分割文件目录
File splitFilePath = splitFirFile.getParentFile();
// 配置文件
File propFile = new File( splitFilePath, splitFileName + ".properties");
if (!propFile.exists() || propFile.isDirectory()) {
throw new RuntimeException( "配置文件不存在!" );
}
// 读取配置文件
Properties prop = new Properties();
FileReader fr = new FileReader( propFile);
prop.load(fr);
fr.close();
// 合并文件名
String fileName = prop.getProperty( "FileName");
int num = Integer.parseInt(prop.getProperty( "PartNo."));
// 判断分割文件是否完整
File[] splitFiles = new File[ num];
for (int i = 1; i <= num; i++) {
splitFiles[i - 1] = new File( splitFilePath, splitFileName + ".part" + String.format( "%02d", i ));
if (!splitFiles[i - 1].exists() || splitFiles[ i - 1].isDirectory()) {
throw new RuntimeException( "缺少分割文件:" + splitFiles[i - 1].getName());
}
}
// 将流加入集合,并使用序列输入流完成合并
List<FileInputStream> list = new ArrayList<FileInputStream>();
for (int i = 1; i <= num; i++) {
list.add(new FileInputStream( splitFiles[ i - 1]));
}
byte[] buf = new byte[ BUFFER_SIZE];
int len = -1;
SequenceInputStream sis = new SequenceInputStream(Collections.enumeration( list));
FileOutputStream fos = new FileOutputStream( new File(splitFilePath, fileName ));
while (( len = sis.read( buf)) != -1) {
fos.write(buf, 0, len);
}
fos.close();
sis.close();
}
}

2. 不使用配置文件的方案

    
    
package io;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
public class TSplitJoin {
private static final int BUFFER_SIZE = 4096;
private static final long PROPERTIES_SIZE = 1024;
 
/**
* 分割文件,必要信息存储到第一个文件中
* @param srcFile 需要分割的文件
* @param destPath 分割文件的保存目录
* @param len 分割文件的大小
* @throws IOException
*/
public void split(File srcFile, File destPath, long len) throws IOException {
// 健壮性判断,源文件目的目录是否存在
if (!srcFile.exists() || srcFile.isDirectory()) {
throw new RuntimeException( "源文件不存在!" );
}
if (len <= 4096) {
throw new RuntimeException( "分卷大小设置太小或者错误!" );
}
if (!destPath.exists() || destPath.isFile()) {
destPath.mkdirs();
}
// 定义缓冲区
byte[] buf = new byte[ BUFFER_SIZE];
// 定义分割文件序号
int num = 0;
// 定义写入缓冲区数据大小
int bufLen = 0;
// 定义单个分割文件剩余的写入字节数
long splitLen = len - PROPERTIES_SIZE;
// 源文件名
String fileName = srcFile.getName();
FileInputStream fis = new FileInputStream( srcFile);
FileOutputStream fos = new FileOutputStream( new File(destPath, fileName + ".part01" ));
String content = fileName + "\n" + (int)Math.ceil(( double)srcFile .length() / (double) len) + "\n";
System.arraycopy(content .getBytes("utf-8" ), 0, buf , 0, content.getBytes().length );
fos.write(buf, 0, (int)PROPERTIES_SIZE);
 
while ( bufLen != -1) {
// 单趟外层循环完成一个分割文件的写入
// 创建输出流,确定分割文件名
if (num != 0) {
fos = new FileOutputStream( new File( destPath, fileName + ".part" + String.format( "%02d", ++num )));
} else {
num++;
}
while ( splitLen > 0) {
// splitLen为该单个文件剩余的写入字节数,以此为依据来从输入流读取数据到缓冲区
if ( splitLen >= BUFFER_SIZE) {
bufLen = fis.read( buf);
} else {
bufLen = fis.read( buf, 0, ( int) splitLen);
}
if ( bufLen == -1) {
break;
}
fos.write( buf, 0, bufLen);
splitLen -= ( long) bufLen;
}
fos.close();
splitLen = len;
}
fis.close();
}
public void join(File splitFirFile) throws IOException {
// 健壮性判断
if (!splitFirFile.exists() || splitFirFile.isDirectory()) {
throw new RuntimeException( "分割文件不存在!" );
}
// 分割文件名(不包括扩展名)
String splitFileName = splitFirFile.getName().substring(0, splitFirFile.getName().length() - 7);
// 分割文件目录
File splitFilePath = splitFirFile.getParentFile();
 
// 读取配置信息
byte[] buf = new byte[ BUFFER_SIZE];
FileInputStream fis = new FileInputStream(splitFirFile );
fis.read(buf, 0, (int)PROPERTIES_SIZE);
fis.close();
String[] prop = new String( buf, "utf-8").split("\n" );
String fileName = prop[0];
int num = Integer.parseInt(prop [1]);
// 判断分割文件是否完整
File[] splitFiles = new File[ num];
for (int i = 1; i <= num; i++) {
splitFiles[i - 1] = new File( splitFilePath, splitFileName + ".part" + String.format( "%02d", i ));
if (!splitFiles[i - 1].exists() || splitFiles[ i - 1].isDirectory()) {
throw new RuntimeException( "缺少分割文件:" + splitFiles[i - 1].getName());
}
}
// 将流加入集合,并使用序列输入流完成合并
List<FileInputStream> list = new ArrayList<FileInputStream>();
for (int i = 1; i <= num; i++) {
list.add(new FileInputStream( splitFiles[ i - 1]));
}
int len = -1;
SequenceInputStream sis = new SequenceInputStream(Collections.enumeration( list));
FileOutputStream fos = new FileOutputStream( new File(splitFilePath, fileName ));
sis.skip(PROPERTIES_SIZE);
while (( len = sis.read( buf)) != -1) {
fos.write(buf, 0, len);
}
fos.close();
sis.close();
}
}

流总结

1. IO流框架继承结构

字节流
InputStream/OutputStream
    |--FileInputStream/FileOutputStream
    |--FilterInputStream/FilterOutputStream
        |--BufferedInputStream/BufferedOutputStream
        |--DataInputStream/DataOutputStream
        |--PrintStream
    |--ObjectInputStream/ObjectOutputStream
    |--ByteArrayInputStream/ByteArrayOutputStream
    |--SequenceInputStream

字符流
Reader/Writer
    |--InputStreamReader/OutputStreamWriter
        |--FileReader/FileWriter
    |--BufferedReader/BufferedWriter
    |--CharArrayReader/CharArrayWriter
    |--StringReader/StringWriter
    |--PrintWriter

2. IO流要素

目标
  • 源:InputStream/Reader及其子类
  • 目的:OutputStream/Writer及其子类
内容
  • 字节流:InputStream/OutputStream及其子类
  • 字符流:Reader/Writer及其子类
设备
  • 外存:FileInputStream/FileOutputStream、FileReader/FileWriter
  • 内存:ByteArrayInputStream/ByteArrayOutputStream、CharArrayReader/CharArrayWriter、StringReader/StringWriter
  • 键盘和控制台:System.in/System.out
  • 网络:Socket

附加功能(装饰类)

  • 转换:InputStreamReader/OutputStreamWriter(字节流)
  • 缓冲区:BufferedInputStream/BufferedOutputStream(字节流)、BufferedReader/BufferedWriter(字符流)
  • 多源:SequenceInputStream(字节流)
  • 序列化:ObjectInputStream/ObjectOutputStream(字节流)
  • 保证数据的表现形式:PrintStream(字节流,文件)、PrintWriter(字节流,字符流,文件)
  • 保证数据原始字节:DataInputStream/DataOutputStream(字节流)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值