输入输出

Java IO Tutorial - JournalDev

文件读写(File IO)

open:当程序需要从文件(例如数据文件、网络等)读取数据时,创建一个适当类型的输入流对象,建立与数据源的连接,然后调用该输入流对象的方法(read),来读取外设的数据。当程序需要输出数据时,应该创建一个输出流对象来完成对文件的连接,然后利用输出流对象的write方法将数据顺序写入到目标。

close:完成读写后关闭对应的流,释放占用的资源。

输入输出流

流的类型

Java提供两类基本输入输出方式,即基于字节的(InputStream/OutputStream)和基于字符的(Reader/Writer对应了C的文件打开方式bt)。

根据数据源的区别,可以将流分为:

  • 节点流:直接与输入输出目标相连的流;
  • 过滤流(基于FilterInputStream/FilterOutputStream):与另一个流相连的流。使用过滤流需要注意,当关闭过滤流时,其所连接的源输入流会被自动关闭,变得不能再使用。而在使用过滤流时,也不能提前关闭其所依赖的源输入流。

img

字节流输入输出

InputStream

数据作为字节流输入,其提供的读取方法包括:

int read()
int read(byte[] b)
int read(byte[] b,int off,int len)

(1)读取1byte数据并返回读取数据值;如果读取到流的末端则返回-1;该方法发生阻塞直到流的数据可用、检测到已经到达流的结束或抛出异常。 (2) 将数据读入字节数组,返回实际读取的字节数(阻塞); (3) 从数组off下标开始的位置写入,并指定最大读取的字节数len

定位:

long skip(long n)
void mark(int readlimit)
void reset()

skip使位置指针从当前位置向后跳过n个字节,返回实际跳过的字节数。 mark标记当前位置,以备reset方法使用,标记以最后一次调用mark为准。 reset将位置指针移动到标记的位置处。

基于InputStream实现的类型包括:

  • ByteArrayInputStream:以字节数组作为输入的数据源;

  • FileInputStream:以数据文件作为数据源。

    InputStream f = new FileInputStream(path);	// path string
    InputStream f = new FileInputStream(file);	// file object
    

基于InputStream的过滤流类型包括:

  • BufferedInputStream增加了输入缓冲区功能,支持mark和reset,显著提高了输入效率。
  • DataInputStream:实现了DataInput接口中定义的独立于具体机器的带格式的读操作,从而实现了针对不同类型数据的字节读取,例如:readBytereadBooleanreadShortreadCharreadIntreadLongreadFloatreadDoublereadLinereadUTF等。注意:这种读取方式适合读取二进制文件、而不适合读取控制台的标准输入和文本文件后者应该使用Reader类。
OutputStream
void write(int b)
void write(byte[] b)
void write(byte[] b,int off,int len)

(1) 向输出流写入一个字节,该字节为参数b的低8位,其余24位忽略。 (2) 将字节数组b的内容按字节写入流。 (3) 将字节数组从下标off开始的数据逐字节写入流,并指定要写入流的字节数。如果len+off大于数组的长度,则会引起IndexOutOfBoundsException

void flush()

将输出流缓冲区的数据立即输出到目标文件。

基于OutputStream的子类:

  • ByteArrayOutputStream:将数据输出到内存中的一个无名数组中。如果想获取无名数组中的数据,可以利用该输出流的以下方法

    byte[] toByteArray()
    String toString()
    
  • FileOutputStream:输出至文件。

    OutputStream f = new FileOutputStream(path)  // path string
    OutputStream f = new FileOutputStream(new File(path));
    

基于OutputStream的过滤流类:

  • DataOutputStream:提供了针对不同类型数据的写方法,如:writeBytewriteBooleanwriteShortWriteCharWriteIntWriteLongWriteFloatWriteDoubleWriteChars等。需要注意写入目的地的数据是数据的二进制形式而非字符形式。

  • PrintStream即可作为过滤流,也可以作为节点流(直接以文件作为输出)。该输出流不产生IOException、具有自动刷新功能等特点。

    PrintStream(String fileName) // option-args: encoding
    PrintStream(File file)
    PrintStream(OutputStream out) // out is an underlay output stream
    

    该输出流提供了类似于一般高级语言的输出语句,例如:

    print(int i);
    println(String x);
    

    在标准输出和文件输出,都输出数据的字符形式,适合于写文本文档。

    直接从File或文件名创建输出流默认未覆盖模式,需要使用底层输出流支持追加模式。

字符流输入输出

Reader

数据作为编码后的字符流输入,Java使用Unicode字符编码。

  1. 读取

    int read()
    int read(char[] cbuf)
    

    (1) 从输入流读取并返回一个字符。 (2) 从输入流中读取字符,并写入字符数组,返回实际读取的字符数。

  2. 定位

    long skip(long n)
    void mark(int readAheadLimit)
    void reset()
    

    以字符为单位进行定位。

    mark标记当前位置,并规定能返回当前位置的最大读取字符数readAheadLimit,当从当前位置读取的字符数超过这个上限,使用reset函数将不能返回标记位置。并不是所有输入字符流都支持标记功能。

基于Reader的子类:

  • CharArrayReader
  • StringReader
  • FileReader

基于Reader的过滤流类型:

  • BufferedReader:以一个Reader类对象为数据源,加入了缓冲功能,提供了读字符、数组和行的功能。

  • InputStreamReader该类型提供从字节流到字符流的桥梁。它可以从字节输入流获得数据,然后转换为字符数据交给程序。InputStreamReader虽然将字节流转换为字符流,但是没有提供更多的read方法,不方便文本的读取,例如:将FileInputStreamInputStreamReader联合使用,其效果相当于FileReader。如果要实现更方便的读功能,就要在字符流的基础上增加BufferedReader,即:

    br = new BufferedReader(new InputStreamReader(new FileInputStream(...)))
    br = new BufferedReader(new FileReader(...))
    
Writer
  1. 写入

    void write(char[] cbuf)
    void write(String str)
    void write(String str, int off,int len)  
    
  2. 刷新缓冲区

    void flush()
    

基于Writer的子类:

  • CharArrayWriter:该字符输出流的目的地是内存中的无名数组,其方式类似于ByteArrayOutputStream

  • FileWriter

    FileWriter(String fileName, ...)
    FileWriter(File file, boolean append) // [append=False]
    FileWriter(File file, Charset charset, boolean append)
    

    如果文件不存在,自动创建;默认为覆盖模式。

基于Writer的过滤流子类:

  • BufferedWriter:参看BufferedReader

  • OutputStreamWriter:参看InputStreamReader

  • PrintWriter:类似于PrintStream

    PrintWriter(String fileName)		// auto-create and overwrite
    PrintWriter(File file)
    PrintWriter(OutputStream out)
    PrintWriter(Writer out)
    

标准输入输出

标准输入/输出是System类中的两个静态成员字段(System类中所有字段和方法都是静态的)System.inSystem.out

System.inInputStream类的子类对象。使用read方法需要注意一下几点:

  1. System.in.read()语句必须包含在try语句块中,且其后必须有一个可接收IOExceptioncatch语句块

  2. 执行该语句虽然只读取一个字节的数据,但返回的是16bit的整型量,只有低8位的数据是真正的输入数据,高8位为0。

  3. 作为InputStream类的对象,System.in只能从键盘读取字节数据,要实现复杂输入,可以将System.in作为源,使用上述的过滤流(BufferedReader)来实现。

    java.util.Scanner 是 Java5 的新特征,我们可以通过 Scanner 类来获取用户的输入。

    Scanner s = new Scanner(System.in);
    
  4. 标准输入时,无论是字节流或字符流,若将数据输入到数组中,会将Enter键也算入输入,并且相当于"回车(13,\r)"和"换行(\n,10)",所以在使用输入数据时应该去掉这两个字节(字符)的数据。当使用记事本等程序打开文件时,\r\n\r\n出现时,识别为一个换行。

  5. 当键盘缓冲区没有数据时,执行System.in.read()会引起阻塞。

System.outPrintStream类的对象。

日志

Logger in Java - Java Logging Example - JournalDev

网络编程

基本元素

IP地址和端口号

InetAddress封装了IP地址,端口号使用整数表示。

InetAddress.getByName("127.0.0.1");   // or domain.name
InetAddress.getLocalHost();
InetAddress.getHostAddress();  // return IP text

SocketAddress封装了IP地址和端口号。

UDP通信

DatagramSocket类封装了UDP通信的服务端和客户端。

udp_conn = new DatagramSocket(); // client
udp_conn = new DatagramSocket(port[, inet_addr]); //server
udp_conn.send(packet);			// DatagramPacket
udp_conn.receive(packet);
sql_conn.getInetAddress();
ip_addr = udp_conn.getLocalAddress();
sql_conn.getPort()
port = udp_conn.getLocalPort();
sock_addr = sql_conn.getLocalSocketAddress();
sql_conn.getRemoteSocketAddress();  # return null if not connected
数据报分组

DatagramPacket封装了UDP协议分组,该类型包含一个数据缓存(byte[])的引用,可通过构造函数或setData设置分组的数据缓存。

send_packet = new DatagramPacket(msg.getBytes(), msg.length(), server_addr, server_port);  // msg (String) convert to byte array.
send_packet = new DatagramPacket(msg.getBytes(), msg.length(), socket_addr);
send_packet.setData(msg.getBytes(), offset, msg.length());
byte[] recv_buffer = new byte[128];
recv_packet = new DatagramPacket(recv_buffer, recv_buffer.length);

待发送的分组需要设置目标套接字地址,接收到的分组可以读取远端的套接字地址。

send_packet.setAddress(inet_addr);
send_packet.setPort(port);
send_packet.setSocketAddress(inet_addr);
client_addr = recv_packet.getAddress();
client_port = recv_packet.getPort();
client_addr = recv_packet.getSocketAddress();

TCP通信

ServerSocket类代表服务器端。

ServerSocket([int port[, int backlog[, InetAddress address]]]) 
             throws IOException
Socket accept() throws IOException
void bind(SocketAddress host, int backlog)

Socket类代表TCP客户端。

Socket()
Socket(String host, int port) throws UnknownHostException, IOException
Socket(InetAddress host, int port) throws IOException
Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException
Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException
void connect(SocketAddress host, int timeout) throws IOException
InputStream getInputStream() throws IOException
OutputStream getOutputStream() throws IOException
void close() throws IOException

数据序列化

对象序列化

一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化。

当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个.ser扩展名。

ObjectInputStreamObjectOutputStream是高层次的数据流,它们包含反序列化和序列化对象的方法。

final void writeObject(Object x) throws IOException  // ObjectOutputStream
final Object readObject() throws IOException, 		  // ObjectInputStream
                                 ClassNotFoundException

对于 JVM 可以反序列化对象,它必须是能够找到字节码的类。

一个类的对象要实现序列化功能,必须:

  • 实现java.io.Serializable对象。
  • 所有字段是可序列化的或transient

JSON

org.json提供处理JSON数据的方法。

<!-- https://mvnrepository.com/artifact/org.json/json -->
<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20190722</version>
</dependency>

JSON的数据类型主要分为两类,即

  1. 对象JSONObject:包含一个或多个key-value(可以是JSONArray)的字典;

    import org.json.JSONObject;
    JSONObject jo = new JSONObject(); // creating empty JSONObject 
    JSONObject jo = new JSONObject(JSON_String); // Parse from string 
    jo.put(key, value);  // add or modify elements
    jo.getString(key);	// throw exception if key not exist
    jo.optString(key);  // return empty string if key not exist
    jo.optString(key,default_value); // return default if key not exist
    jo.getLong(key);   // get Numerical type
    jo.optLong(key);
    
    jo.toString();		// convert to string
    

    转换为字符串时,特殊字符将使用转义字符执行逆向转换。

  2. 数组JSONArray:包含value(可以是JSONObject)一个序列。JSONArray的方法与JSONObject类似,但没有key参数。

https://www.geeksforgeeks.org/parse-json-java/

https://stackoverflow.com/questions/2591098/how-to-parse-json-in-java

XML

Java XML - Parsers

XML解析