Wt Blog


  • 首页

  • 归档

TCP通信

发表于 2019-05-18

Socket 原理

Socket 简介

  • socket起源于Unix,而Unix/Linux基本哲学之一就是:“一切即文件”,都可以用”打开open -> 读写write/read -> 关闭close”模式来操作。
  • 简单来说socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO,关闭,打开)。
  • 我们在传输数据的时候,可以只使用(传输层)TCP/IP协议,但是那样的话,如果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用成协议,应用层协议有很多,比如HTTP,FTP,TELNET等,也可以自己定义应用层协议。WEB协议使用HTTP协议作应用层协议,以封装HTTP文本信息,然后使用TCP/IP协议作传输层协议将它发到网络上。

Socket概念

  • Socket是一个针对TCP和UDP编程的接口,你可以借助它建立TCP/IP连接等等。而TCP和UDP协议属于传输层。而HTTP是个应用层的协议,它实际上也建立在 TCP协议之上.(HTTP是轿车,提供了封装或者是现实数据的具体形式;socket是发动机,提供了网络通信的能力)。
  • socket称为”套接字”,适用于网络通信的方法。socket是对tcp/IP协议的封装,socket本身并不是协议,而是一个调用接口(api)。通过socket我们才能使用TCP/IP协议。Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口。

获取本地地址和端口号

  • java.net.Socket为套接字类,通过Socke获取:
    • int getLocalPort() :
      • 用获取本地使用的端口号
    • getLocalSocketAddress()
      • 返回此套接字绑定的端点的地址,如果尚未绑定则返回 null。
    • IntAddress getLocalAddress() :
      • 用于获取套接字绑定的本地地址
  • 使用InetAddress获取本地的地址方法:
    • String getCanonicalHostName() :
      • 获取此IP地址的完全限定域名
    • String getHostAddress() :
      • 返回IP地址字符串(以文本表现形式)
    • String getCanonicalHostName() :
      • 获取此 IP 地址的完全限定域名。
    • String getHostName() :
      • 获取此 IP 地址的主机名。

获取远端地址和端口号

  • 通过Socket获取:
    • int getPort() :
      • 用于获取远端使用的端口号
    • InetAddress .getInetAddress() :
      • 用于获取套接字绑定的远端地址

获取网络输入流和网络输出流

  • 通过Socket获取输入流与输出流:

    • InputStream getInputStream() :
      • 用于返回套接字的输入流
    • OutputStream getOutputStream() :
      • 用于返回套接字的输出流
  • close方法:当使用Socket进行通讯完毕后,要关闭Socket以释放系统资源。

  • void close() :
    • 关闭此套接字。当关闭了该套接字后也会同时关闭由此获取的输入流与输出流

Exception

发表于 2019-05-15

异常处理

JAVA异常概念

  • 异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。
  • 比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error

异常发生的原因有很多,通常包含以下几大类:

  • 用户输入了非法数据。
  • 要打开的文件不存在。
  • 网络通信时连接中断,或者JVM内存溢出。
    这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。

要理解Java异常处理是如何工作的,你需要掌握以下三种类型的异常:

  • 检查性异常:
    • 最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
  • 运行时异常:
    • 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
  • 错误:
    • 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

Exception 类的层次

  • 所有的异常类是从 java.lang.Exception 类继承的子类。
  • Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。

Java 内置异常类

  • Java 语言定义了一些异常类在 java.lang 标准包中。
  • 标准运行时异常类的子类是最常见的异常类。由于 java.lang 包是默认加载到所有的 Java 程序的,所以大部分从运行时异常类继承而来的异常都可以直接使用。

  • Java 的非检查性异常:

    • ArithmeticException: 当出现异常的运算条件时,抛出此异常。例如,一个整数”除以零”时,抛出此类的一个实例。
    • ArrayIndexOutOfBoundsException: 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。
    • ArrayStoreException: 试图将错误类型的对象存储到一个对象数组时抛出的异常。
    • ClassCastException: 当试图将对象强制转换为不是实例的子类时,抛出该异常。
    • IllegalArgumentException: 抛出的异常表明向方法传递了一个不合法或不正确的参数。
    • IllegalMonitorStateException: 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。
    • IllegalStateException: 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。
    • IllegalThreadStateException: 线程没有处于请求操作所要求的适当状态时抛出的异常。
    • IndexOutOfBoundsException: 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
    • NegativeArraySizeException: 如果应用程序试图创建大小为负的数组,则抛出该异常。
    • NullPointerException: 当应用程序试图在需要对象的地方使用 null 时,抛出该异常
    • NumberFormatException: 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
    • SecurityException: 由安全管理器抛出的异常,指示存在安全侵犯。
    • StringIndexOutOfBoundsException: 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。
    • UnsupportedOperationException: 当不支持请求的操作时,抛出该异常。
  • Java 定义在 java.lang 包中的检查性异常类:

    • ClassNotFoundException: 应用程序试图加载类时,找不到相应的类,抛出该异常。
    • CloneNotSupportedException: 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。
    • IllegalAccessException: 拒绝访问一个类的时候,抛出该异常。
    • InstantiationException: 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。
    • InterruptedException: 一个线程被另一个线程中断,抛出该异常。
    • NoSuchFieldException: 请求的变量不存在
    • NoSuchMethodException: 请求的方法不存在

捕获异常

  • 使用 try 和 catch 关键字可以捕获异常。try/catch 代码块放在异常可能发生的地方。
    try/catch代码块中的代码称为保护代码,使用 try/catch 的语法如下:

    1
    2
    3
    4
    5
    try{
    代码块
    }catch(XXXExecption e){
    处理try中出现的XXXExecption的代码片段
    }
  • 当出现了异常时,JVM会实例化该异常的实例并将其抛出

多重捕获块

  • catch是可以定义多个的,当针对不同的异常,我们有不同的处理手段时,我们可以分别捕获这些异常并处理。但应当有一个好的习惯,在最后一个catch中捕获Exception,Exception,可以防止因为一个未处理的异常导致程序中断。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    try{
    // 程序代码
    }catch(异常类型1 异常的变量名1){
    // 程序代码
    }catch(异常类型2 异常的变量名2){
    // 程序代码
    }catch(异常类型2 异常的变量名2){
    // 程序代码
    }

throws/throw 关键字:

  • 如果一个方法没有捕获到一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。
  • 一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。
  • 使用throw关键字可以将一个异常抛出。通常如下情况我们会对外主动抛出异常:
    • 1.程序执行中发现一个满足语法要求,但是不满足业务逻辑要求的情况时,可以对外抛出异常告知不应当这样做。
    • 2.程序确实出现了异常,但是该异常不应当在当前代码片段中被解决时,可以对外抛出给调用者解决(责任制问题)
  • 当我们调用一个含有throws声明异常抛出的方法时,编译器要求我们必须处理这个异常,否则编译不通过。
    • 两种处理方式:
      • 1.使用try-catch捕获并处理这个异常
      • 2.在当前方法上继续使用throws将这个异常抛出,具体选择哪种解决方式取决于处理异常的责任问题

finally关键字

  • finally块是异常处理机制的最后一块,可以直接跟在try块之后或者最后一个catch之后。
  • finally块可以确保只要代码执行到try当中,无论try块 当中的代码是否抛出异常,finally块中的代码都必定执行,通常我们将释放的资源这样的操作放在finally中确保执行, 比如IO中关闭流的操作。

注意:

  • catch 不能独立于 try 存在。
  • 在 try/catch 后面添加 finally 块并非强制性要求的。
  • try 代码后不能既没 catch 块也没 finally 块。
  • try, catch, finally 块之间不能添加任何代码。

声明自定义异常

在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。

  • 所有异常都必须是 Throwable 的子类。
  • 如果希望写一个检查性异常类,则需要继承 Exception 类。
  • 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。

通用异常

在Java中定义了两种类型的异常和错误。

  • JVM(Java虚拟机)异常:由 JVM 抛出的异常或错误。例如:NullPointerException 类,ArrayIndexOutOfBoundsException 类,ClassCastException 类。
  • 程序级异常:由程序或者API程序抛出的异常。例如 IllegalArgumentException 类,IllegalStateException 类。

简易聊天室

发表于 2019-05-14

Eclipse控制台简易聊天程序(仅限同一局域网)

服务端程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package socket;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;

/**
* 聊天室的服务端
* @author soft01
*
*/
public class Server {
/*
* java.net.ServerSocket
* 运行在服务端的ServerSocket主要有两个作用
* 1:向系统申请服务端口,客户端就是通过这个端口与服务器建立连接的。
* 2:监听该端口,这样一旦一个客户端通过该端口尝试建立连接时,
* ServerSocket就会自动实例化一个Socket,
* 那么通过Socket就可以与客户端对等并进行数据交互了。
*/
private ServerSocket server;
/*
* 该数组用于保存所有ClientHandler对应客户端的输出流,
* 以便所有ClientHandler都可以互访这些输出流来广播消息。
*/
private PrintWriter[] allOut = {};

public Server() {
try {
/*
* 实例化ServerSocket的同时要向系统申请服务端口,
* 客户端就是通过这个端口与服务端建立连接的。
*/
System.out.println("正在启动服务端...");
server = new ServerSocket(8088);
System.out.println("服务端启动完毕!");
} catch (Exception e) {
e.printStackTrace();
}
}

public void start() {
try {
/*
* Socket accept()
* ServerSocketde的accept方法返回是一个阻塞方法,
* 调用后会一直等待,知道一个客户端建立连接为止,
* 此时该方法会返回一个Socket实例,通过这个Socket就
* 可以与刚连接上的客户端进行数据交互了。
* 多次调用accept可以等待不同客户端的连接
*/
System.out.println("等待客户端连接...");
Socket socket = server.accept();
System.out.println("一个客户端连接了!");

//启动一个线程负责与客户端交互
ClientHandler handler = new ClientHandler(socket);
Thread t = new Thread(handler);
t.start();

} catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
Server server = new Server();
server.start();
}
/**
* 该线程任务是负责与指定客户端进行交互操作
*/
private class ClientHandler implements Runnable{
private Socket socket;
private String host;

public ClientHandler(Socket socket) {
this.socket = socket;
//获取远端计算机IP地址信息的字符串格式
host = socket.getInetAddress().getHostAddress();
}

public void run() {
PrintWriter pw = null;
try {
/*
* 通过Socket获取输入流,可以读取到远端计算机发送过来的字节。
*/
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in,"UTF-8");
BufferedReader br = new BufferedReader(isr);
/*
* 通过socket获取输出流,用于给客户段发送消息。
*/
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
BufferedWriter bw = new BufferedWriter(osw);
pw = new PrintWriter(bw,true);
/*
* 将当前客户端对应的输出流存入allOut,
* 以便于其他ClientHandler可以将消息转发给这个客户端。
*/
synchronized (allOut) {
// 1.对allOut数组扩容
allOut = Arrays.copyOf(allOut, allOut.length + 1);
// 2.将输出流存入数组最后一个位置
allOut[allOut.length - 1] = pw;
}
System.out.println(host+"上线了!");
System.out.println("当前在线人数为:"+allOut.length);

String line = null;
while((line = br.readLine())!=null) {
System.out.println(host+"说:"+line);
//将内容发送给客户端
synchronized (allOut) {
for (int i = 0; i < allOut.length; i++) {
allOut[i].println(host + "说:" + line);
}
}
}
} catch (Exception e) {
} finally {
// 处理客户端断开连接的操作
synchronized (allOut) {
// 1.将当前客户端输出流从allOut中删除
for (int i = 0; i < allOut.length; i++) {
if (allOut[i] == pw) {
allOut[i] = allOut[allOut.length - 1];
allOut = Arrays.copyOf(allOut, allOut.length - 1);
}
}
}
System.out.println(host+"下线了!");
System.out.println("当前在线人数为:"+allOut.length);
// 2.关闭socket,释放资源
try {
socket.close();
} catch (IOException e) {
}

}
}
}
}

客户端程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package socket;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

/**
* 聊天室的客户端
*
* @author soft01
*
*/
public class Client {
/**
* java.net.Socket 套接字 封装了TCP协议的通讯细节,
* 使得我们可以通过它来与远端计算机建立TCP连接, 并利用两个流的读写完成数据交换
*/
Scanner scan = new Scanner(System.in);
private Socket socket;
/**
* 构造方法,用来初始化客户端
*/
public Client() {
try {
/*
* Socket实例化是需要传两个参数
* 参数1:服务端IP地址
* 参数2:服务端申请程序的端口 我们通过IP可以找到网络上的服务端计算机
* 通过端口可以连接到服务端计算机上运行的服务端应用程序。
* 注意: 实例化Socket的过程就是连接的过程, 若服务端没有响应这里会抛出异常。
*/
System.out.println("正在连接服务端...");
// localhost 本地服务
System.out.println("请输入您要连接的IP:");
socket = new Socket(scan.nextLine(), 8088);
System.out.println("成功连接服务端!");

} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 客户端开始工作的方法
*/
public void start() {
try {
// 用于读取服务端发送过来消息的线程
ServerHandler handler = new ServerHandler();
Thread t = new Thread(handler);
t.start();
/*
* Socket提供的方法: OutputStream getOutputStream()
* 该方法获取的输入流是一个字节输出流
* 通过这个流写出的字节会通过网络发送到远端计算机上 (对于客户端这边而言,远端计算机指的就是服务端)
*/
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
BufferedWriter bw = new BufferedWriter(osw);
PrintWriter pw = new PrintWriter(bw, true);

while (true) {
String line = scan.nextLine();
pw.println(line);

}

} catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
Client client = new Client();
client.start();

}
/**
* 该线程负责循环读取服务发送过来的消息并输出到客户端的控制台上。
*
* @author asus
*
*/
private class ServerHandler implements Runnable {
public void run() {
try {
// 通过socket获取输入流,读取服务端发送过来的内容。
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in, "UTF-8");
BufferedReader br = new BufferedReader(isr);

String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}

} catch (Exception e) {

}
}
}
}

API-6

发表于 2019-05-13

文本数据IO操作

Reader与Writer

  • Reader是字符输入流的父类,Writer是字符输出流的父类。
  • 字符流是以字符(char)为单位读写数据的,一次处理一个unicode。
  • 字符流的底层仍然是基本的字节流。
  • 字符流封装了字符的编码解码算法。

  • Reader的常用方法:

    • int read():
      • 读取一个字节,返回的int值”低16”位有效。
    • int read(char[] chs):
      • 读取一个字符数组的length个字符并存入该数组,返回值为实际读取到的字符量。
  • Writer的常用方法:

    • void write(int c):
      • 写出一个字符,写出给定int值”低16”位表示的字符。
    • void write(char[] chs):
      • 将给定字符数组中所有字符写出。
    • void write(String str):
      • 将给定字符串写出。
    • void write(char[] chs, int off, int len):
      • 将给定的字符数组中从off处开始连续的冷个字符写出。

转换流

  • InputStreamReader:字符输入流
    • 该流可以设置字符集,并按照指定的字符集从流中按照该编码将字节数据转换为字符并读取。
  • OutputStreamWriter:字符输出流
    • 该流可以设置字符集,并按照指定的字符集将字符转换为对应字节都通过该流写出。
  • InputStreamReader的构造方法允许我们设置字符集:
    • InputStreamReader(InputStream in, String charsetName):
      • 基于给定的字节输入流以及字符编码创建ISR。
    • InputStreamReader(InputStream in):
      • 本构造方法会根据系统默认字符集创建ISR。
  • OutputStreamWriter的构造方法:
    • OutputStreamWrite(OutputStream out, String charsetName):
      • 基于给定的字节输出流以及字符编码创建OSW。
    • OutputStreamWrite(OutputStream out):
      • 本构造方法会根据系统默认字符集创建OSW。

PrintWriter

  • 创建PW对象
  • PrintWriter是具有自动行刷新的缓冲字符输出流。

    • 常用构造方法:
      • PrintWriter(File file)
      • PrintWriter(String fileName)
      • PrintWriter(OutputStream out)
      • PrintWriter(OutputStream out, boolean autoFlush)
      • PrintWriter(Writer writer)
      • PrintWriter(Writer writer, boolean autoFlush)
        • 其中参数为OutputSream与Writer的构造方法提供了可以传入boolean值的参数,该参数用于表示PrintWriter是否具有自动行刷新。
  • print与println方法

  • PrintWriter提供了丰富的重载方法。其中println方法在输出目标数据后自动输出一个系统支持的换行符。若该流是具有自动行刷新的,那么每当通过println方法写出的内容都会被实际写出,而不是进入缓存。
  • 常用方法:
    • void print(int i ):打印整数
    • void print(char c):打印字符
    • void print(boolean b):打印boolean值
    • void print(char[] c):打印字符数组
    • void print(double d):打印double值
    • void print(int float):打印float值
    • void print(long l):打印long值
    • void print(String str):打印字符串

BufferedReader

  • BufferedReader是缓冲字符输入流,其内部提供了缓冲区,可以提高读取效率。
  • 常用方法:

    • BufferedReader(Reader reader)
  • BufferedReader提供了一个读取一行字符串的方法:

    • String readLine()
    • 读取一行字符串,返回值不含有该行末尾的换行符,如果返回值为null,则表示读取到了流的末尾,若是读取的文件,则表示文件读取到了末尾。

String、StringBuffer、StringBuilder区别

  • StringBuffer、StringBuilder和String一样,也用来代表字符串。String类是不可变类,任何对String的改变都 会引发新的String对象的生成;StringBuffer则是可变类,任何对它所指代的字符串的改变都不会产生新的对象。既然可变和不可变都有了,为何还有一个StringBuilder呢?相信初期的你,在进行append时,一般都会选择StringBuffer吧!
  • 先说一下集合的故事,HashTable是线程安全的,很多方法都是synchronized方法,而HashMap不是线程安全的,但其在单线程程序中的性能比HashTable要高。StringBuffer和StringBuilder类的区别也是如此,他们的原理和操作基本相同,区别在于StringBufferd支持并发操作,线性安全的,适 合多线程中使用。StringBuilder不支持并发操作,线性不安全的,不适合多线程中使用。新引入的StringBuilder类不是线程安全的,但其在单线程中的性能比StringBuffer高。

API-5

发表于 2019-05-11

基本IO操作

1.IO与OS

  • 输入与输出:
    • 什么是输入:
      • 输入是一个从外界进入到程序的方向,通常我们需要”读取”外界的数据时,使用到输入。所以输入是用来读取数据的。
    • 什么是输出:
      • 输出是一个从程序发送到外界的方向,通常我们需要”写出”数据到外界时,使用到输出。所以输出是用来写出数据的。

2.节点流与处理流

  • 按照流是否直接与特定的地方(如磁盘,内存,设备等)相连,分为节点流和处理流两类。
    • 节点流(低级流)
      • 可以从或向一个特定的地方(节点)读写数据。
    • 处理流(高级流或过滤流)
      • 是对一个已存在的流的连接与封装,通过已封装的流的功能调用实现数据读写。

3.IS和OS常用方法

  • InputStream是所有字节输入流的父类,其定义了基础的读取方法。
    • 方法:
      • int read():
        • 读取一个字节,以int形式返回,该int值的”低八位”有效,若返回值为-1则表示EOF。
      • int read(byte[] d):
        • 尝试最多读取给定数组length个字节并存入该数组,返回值为实际读取到的字节量。
  • OutputStream是所有字节输出流的父类,其定义了基础写出方法。
    • 方法:
      • void write(int d):
        • 写出一个字节,写的是给定的int的”低八位”
      • void write(byte[] d):
        • 将给定的字节数组中的所有字节全部写出
      • void write(byte[] d,int off,int len):
        • 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。

4.文件流

创建FOS对象(重写模式)
  • FlieOutputStream是文件的字节输出流,我们使用该流可以以字节为单位将数据写入文件
    • 构造方法:
      • FlieOutputStream(File file):
        • 创建一个向指定Flie对象表示的文件中写出数据的输出流
      • FlieOutputStream(String filename):
        • 创建一个向具有指定名称的文件中写出数据的输出流
  • 注意:
    • 若指定文件已经存在内容,使用Fos写入数据时,会将该文件中的数据覆盖。
创建FOS对象(追加模式)
  • 若想在原文件后追加新数据则需要用以下构造方法创建FOS。
    • 构造方法:
      • FlieOutputStream(File file,boolean append):
      • FlieOutputStream(String filename,boolean append):
        • 如第二个参数为true,通过该FOS写出的数据都是追加在文件原数据末尾写入的。
创建FIS对象
  • FlieInputStream是文件的字节输入流,使用该流可以以字节为单位从文件中读取数据。
    • 构造方法:
      • FlieInputStream(File file):
        • 创建一个从指定File对象表示的文件中读取数据的文件输入流
      • FlieInputStream(String filename):
        • 创建用于读取给定文件系统中的路径名name所指定的文件的文件输入流

5.缓冲流

  • BufferedOutputStream缓冲输出流内部维护着一个缓冲区,每当我们向该流写数据时,会先将数据存入缓冲区,当缓冲区已满时,缓冲流会将数据一次性全部写出。
  • BOS的flush方法

    • 使用缓冲输出流可以提高写出效率,但是有一个问题,就是写出数据缺乏即时性。因为数据都存在缓冲区,当缓冲区满时,缓冲流才会将数据写出。所以我们在执行完写出操作后,无法及时查看写出数据。这时我们就需要用到缓冲流的flush方法。
    • void flush():
      • 强制将当前缓冲流已缓存的字节一次性写出
      • 频繁调用flush会提高写出数据的频率,这会降低写出效率,但是会提高写出数据即时性。(按需求而定)
  • BufferedInputStream是缓冲字节输入流,其内部维护着一个缓充区(字节数组),该流在读取一个字节时,该流会尽可能多的一次性读取若干字节并存入缓冲区,然后逐一的将字节返回,直到缓冲区中的数据被全部读取完毕,会再次读取若干字节从而反复。这样就减少了读取的次数,从而提高了读取效率。

  • BIS是一个处理流,该流为我们提供了缓冲功能。

6.对象流

  • 对象序列化
    • 对象是存在内存中的。有时我们需要将对象保存到硬盘上,又有时我们需要经对象传到另一台计算机上,等等这样的操作,而这个过程就称作对象序列化。相反,我们有这样的一个字节序列需要将其转换为对应的对象,这个过程称为对象的反序列化。
  • ObjectPutputStream是用来对对象进行序列化的输出流。
    • 实现对象序列化方法:
      • void writeObject(Object o):
        • 可以将给定的对象转换为一个字节序列后写出。
  • ObjectInputStream是用来对对象进行反序列化的输入流。
    • 实现对象反序列化方法:
      • Object readObject():
        • 可以从流中读取字节并转换为对应的对象。

7.Serializable接口

  • 用于启动对象的序列化功能,可以强制让指定类具备序列化功能,该接口中没有成员,这是一个标记接口。这个标记接口用于给序列化类提供UID。这个uid是依据类中的成员的数字签名进行运行获取的。如果不需要自动获取一个uid,可以在类中,手动指定一个名称为serialVersionUID id号。依据编译器的不同,或者对信息的高度敏感性。最好每一个序列化的类都进行手动显示的UID的指定。

8.transient关键字

  1. transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。
  2. 被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
  3. 一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。也可以认为在将持久化的对象反序列化后,被transient修饰的变量将按照普通类成员变量一样被初始化。

练习案例-5

发表于 2019-05-09

猜字符游戏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class GuessingGame {
public static void main(String[] args) {
// 表示玩家猜测的次数
int count = 0;
// 用于保存判断的结果
int[] result = new int[2];
Scanner scanner = new Scanner(System.in);
System.out.println("GuessingGame>欢迎尝试猜字母游戏!");
// 表示猜测的字符串
char[] chs = generate();
System.out.println(chs); //作弊
System.out.println("GuessingGame>游戏开始,请输入你所猜的5个字母序列:(exit——退出)");
while (true) {
String inputStr = scanner.next().toUpperCase();
if ("EXIT".equals(inputStr)) {
System.out.println("GuessingGame>谢谢你的尝试,再见!");
break;
}

char[] input = inputStr.toCharArray();
result = check(chs, input);
if (result[0] == chs.length) {// 完全猜对的情况
int score = 100 * chs.length - count * 10;
System.out.println("GuessingGame>恭喜你猜对了!你的得分是:" + score);
break;
} else {
count++;
System.out.println("GuessingGame>你猜对" + result[1] + "个字符,其中"
+ result[0] + "个字符的位置正确!(总次数=" + count + ",exit——退出)");
}
}
scanner.close();
}

/**
* 随机生成需要猜测的字母序列
*
* @return 存储随机字符的数组
*/
public static char[] generate() {

char[] letters = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z' };
boolean[] flags = new boolean[letters.length];
char[] chs = new char[5];
for (int i = 0; i < chs.length; i++) {
int index;
do {
index = (int) (Math.random() * (letters.length));
} while (flags[index]);// 判断生成的字符是否重复
chs[i] = letters[index];
flags[index] = true;
}
return chs;
}

/**
* 比较玩家输入的字母序列和程序所生成的字母序列,逐一比较字符及其位置,并记载比较结果
*
* @param chs
* 程序生成的字符序列
* @param input
* 玩家输入的字符序列
* @return 存储比较的结果。返回值int数组 的长度为2,其中,索引为0的位置
* 用于存放完全猜对的字母个数(字符和位置均正确),索引为1的位置用于存放猜对的字母个数(字符正确,但是位置不正确)。
*/
public static int[] check(char[] chs, char[] input) {
int[] result = new int[2];
for (int i = 0; i < 5; i++) {
for (int j = 0; j < chs.length; j++) {
if (input[i] == chs[j]) {// 判断字符是否正确
result[1]++;
if (i == j) {// 判断位置是否正确
result[0]++;
}
break;
}
}
}
return result;
}

}

练习案例-4

发表于 2019-05-08

JAVA 数列求和

1. 有数列为:9,99,999,…,9999999999。要求使用程序计算此数列的和,并在控制台输出结果。

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
long num = 9;
long result = num;
for (int i = 2; i <= 10; i++) {
num = num * 10 + 9;
result = result+num;
}
System.out.println("num=" + result);
}

2. 另有数列:1+1/2+1/3…+1/n(n>=2)。要求使用交互的方式计算此数列的和:用户在控制台录入需要计算的整数 n 的值,程序计算此数列的和,并在控制台输出结果。

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("请输入一个整数:");
int n = scan.nextInt();
if (n >= 2) {
double result1 = 0;
for (int i = 1; i <= n; i++) {
result1 += 1.0 / i ;
}
System.out.println("result=" + result1);
}
}

数组案例

1. 查询数组最小值,并将其放在第一位

创建程序,实现查询数组中最小值的功能,并将最小值放入数组的第一位。需求为:创建一个长度为 10 的数组,数组内放置 10 个 0 到 99 之间(包含0,包含99)的随机整数作为数组元素,要求查询出数组中的最小值,并打印显示在界面上。然后,将查询到的数组最小值记载为数组的第一个元素,并打印赋值后的数组内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {	
int[] arr = new int[10];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 100);
System.out.println(arr[i]);
}
int min = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] < min) {
min = arr[i];
}
}
System.out.println("最小值为:" + min);
int[] as = new int[arr.length + 1];
System.arraycopy(arr, 0, as, 1, arr.length);
as[0] = min;
for (int i = 0; i < as.length; i++) {
System.out.println(as[i]);
}
}

2. 随机生成数组

封装一个方法generateArray,该方法实现生成指定长度的int数组,该数组的元素为0到指定范围内的随机数,并将该数组返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("请输入指定长度:");
int m = scan.nextInt();
System.out.println("请输入指定范围:");
int n = scan.nextInt();
int[] arr = generateArray(m,n);
for(int i=0;i<arr.length;i++) {
System.out.print(arr[i]+" ");
}
}
public static int[] generateArray(int len,int max){
int[] arr = new int[len];
for(int i=0;i<arr.length;i++) {
arr[i] = (int)(Math.random()*max);
}
return arr;
}

练习案例-3

发表于 2019-05-07

1.编写个人所得税计算程序

个人所得税是国家对本国公民、居住在本国境内的个人的所得和境外个人来源于本国的所得征收的一种所得税。目前,北京地区的个人所得税的计算公式为:应纳税额=(工资薪金所得-扣除数)×适用税率-速算扣除数。其中,扣除数为3500元,适用税率以及速算扣除数如下表所示。

全月应纳税所得额 税率 速算扣除数(元)
全月应纳税额不超过1500元 3% 0
全月应纳税额超过1500元至4500元 10% 105
全月应纳税额超过4500元至9000元 20% 555
全月应纳税额超过9000元至35000元 25% 1005
全月应纳税额超过35000元至55000元 30% 2755
全月应纳税额超过55000元至80000元 35% 5505
全月应纳税额超过80000元 45% 13505

上表中的全月应纳税所得额=工资薪金所得-扣除数。
本案例要求计算个人所得税的缴纳额度:用户从控制台输入税前工资的金额,程序计算所需要交纳的个人所得税的金额,并将计算结果输出到控制台。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("请输入您的工资税前金额(¥):");
double wage = scan.nextDouble();
wage -= 3500;
double num = 0;
if (wage <= 0) {
num = 0;// 不纳税
} else if (wage <= 1500) {
num = wage * 0.03;
} else if (wage <= 4500) {
num = wage * 0.1 - 105;
} else if (wage <= 9000) {
num = wage * 0.2 - 555;
} else if (wage <= 35000) {
num = wage * 0.25 - 1005;
} else if (wage <= 55000) {
num = wage * 0.3 - 2755;
} else if (wage <= 80000) {
num = wage * 0.35 - 5505;
} else if (wage >= 80000) {
num = wage * 0.45 - 13505;
}
System.out.println("您应该缴纳的个人所得税是(¥):" + num);

}

2.输入年份和月份,输出该月的天数(使用switch-case)

一年有 12 个月,而每个月的天数是不一样的。其中,有7个月为 31 天,称为大月,分别为1、3、5、7、8、10、12月;有 4个月为 30 天,称为小月,分别为4、6、9、11月;还有二月比较特殊,平年的二月只有28天,而闰年的二月有 29 天。
本案例需要使用交互的方式计算某年某月的天数:由用户在控制台输入年份和月份值,程序计算该年该月的天数,并将结果输出在控制台。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("请输入年份:");
int year = scan.nextInt();
System.out.println("请输入月份:");
int month = scan.nextInt();
int day = 0;
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
day = 31;
break;
case 4:
case 6:
case 9:
case 11:
day = 30;
break;
case 2:
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
System.out.println(year + "是闰年");
day = 29;
} else {
System.out.println(year + "是平年");
day = 28;
}
break;
}
System.out.println(year + "年" + month + "月" + "有" + day + "天");

}

API-4

发表于 2019-05-06

File

对文件的操作

获取某个目录下所有的子项目

  • listFiles()
    返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。
    
  • listFiles(FileFilter filter)
    返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。
    
  • listFiles(FilenameFilter filter)
    返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录
    
  • listFiles(FileFilter filter):File[]
    • 获取指定目录下所有满足条件的File对象
    • FileFilter:是个接口,其中有抽象方法accept,此方法用于实现过滤,将过滤条件写在本方法体中。

      递归:

      在一个方法体内部调用方法本身

      文件操作 - RandomAccessFile

  • File的实例可以对文件进行以上3种访问操作,但是不可以访问文件中的数据。
  • RandomAccessFile实例可以实现对文件数据的访问(读写操作)
  • 构造方法:
    • RandomAccessFile(File/String dest,String mode)
      mode:访问模式
      常用的2个访问模式:
      "r" :只读
      "rw":读写
      
  • 方法:
    • 写数据:
      • write(int n):向文件中写入一个字节
- 作为了解:
    - 计算机底层存储的是二进制数据,即保存的是0和1,一个二进制数占用的内存大小即为1位,一个字节占用8位
        - byte  1字节 8位的
        - int   4字节  32位
        - long  8字节  64位 
处理器:32位的:处理一个数据时一次占用32位
    byte占用低8位
    00000000 00000000 00000000 01100000
                                低八位
    处理器处理int值和处理byte值效率是一样的,
    因为都是占用32位处理的
- 读数据
    - read():int
        - 读取一个字节数据,以int形式返回
        - 若读取到文件末尾,返回-1

按照单字节方式复制效率低,为了提高效率,我们可以采取每次复制一个字节数组的字节量,从而减少复制的次数,提高效率
  • 按照字节数组读取数据
    • read(byte[] bys):int
      • 将从文件中读取到的字节数据存入此数组中,int表示实际读取到的字节长度
    • write(byte[]):
      • 向文件中写入一个字节数组
    • write(byte[] bys,int index,int len)
      • 向目标文件中写入bys中从index开始的连续len个字节
    • close()方法
      • RandomAccessFile对文件访问的操作全部结束后,调用close()方法
        来释放与其关联的所有系统资源

        文件指针操作

        RandomAccessFile的读写操作都是基于指针的,也就是说总是在指针当前所指向的位置进行读写操作
  • getFilePointer()
    • 该方法用于获取当前RandomAccessFile指针位置
  • seek()
    • 该方法用于移动当前RandomAccessFlie的指针位置
  • skipBytes()
    • 尝试跳过输入的 n 个字节以丢弃跳过的字节。
    • 此方法可能跳过一些较少数量的字节(可能包括零)。这可能由任意数量的条件引起;在跳过 n 个字节之前已到达文件的末尾只是其中的一种可能。
    • 此方法从不抛出 EOFException。返回跳过的实际字节数。如果 n 为负数,则不跳过任何字节。

API-3

发表于 2019-05-05

Object

Object是java提供的一个类,是所有类的父类。

  • 1) toString():
    • 返回该对象的字符串表示
    • 类名@散列码(整数)
    • 当输出一个对象的引用时,会自动去调用所属类的toString()
    • java中建议:子类对toString()最好都进行重写,使其更具有实际意义
  • 2) equals()
    • 在Object类中作用等同于 == 的作用
    • java中对此方法给出建议:建议在子类中重写此方法,使其更具有实际意义

包装类

什么是包装类

1)java中对8种基本类型都提供了对应的包装类,用于将数据包装成对象
2)java是面向对象的语言,基本数据类型不是对象,所以提供了包装类用于
将基本类型数据包装成对象使用

8种基本类型对应的包装类:

1
2
3
4
5
6
7
8
byte  --> Byte
short --> Short
int --> Integer
long --> Long
float --> Float
double --> Double
char --> Character
boolean --> Boolean

  • 了解:
    基本类型和包装类型,若用于计算,基本类型计算的比包装类型计算更快

8种包装类中实现的功能都类似,列举其一进行讲解:

Integer

  • valueOf(int):Integer
    • 得到int对应的包装类型对象
  • intValue():
    • 从包装类型–>基本类型
  • parseInt(String):int
    • String–>int
    • 前提:String表示的值一定是可以转换成int类型才可以

      自动拆装箱

  1. 装箱:将基本类型数据包装成包装类型
    1. int–>Integer
    2. 通过valueOf(int)完成的
  2. 拆箱:将包装类型数据拆成基本类型
    1. Integer–>int
    2. 通过intValue()实现的

      自动装箱,自动拆箱

  • 自动装箱,自动拆箱是java从JDK1.5开始提供的功能,是编译器实现的
    如何实现:编译过程中,编译器将Integer i = 3;替换成了
    Integer i = Integer.valueOf(3);
    

文件操作-File

  • File:实现对文件的操作
  • 通过File对象可以实现对文件/目录的以下操作
    1. 创建,删除文件/目录
    2. 获取文件/目录的属性信息
    3. 获取指定目录下的所有子项目
  • 构造方法:
    • File(String path)
    • File(File parent,String child)
    • File(String parent,String child)
  • 方法:
    1. 创建文件
      1. creatNewFile()
    2. 创建目录
      1. mkdir():创建一个目录
      2. mkdirs():创建多级目录
    3. delete():
      1. 删除文件/目录,注意,若删除的目录下还有子文件/目录,删除失败
    4. getName():String
      1. 获取文件名称
    5. length():long
      1. 获取文件内容的大小(字节)
      2. 对文件进行读写操作的基本单位是字节
    6. exists():boolean
      1. 判断文件是否存在
      2. 通常和其他方法一起使用
        1. eg:创建文件
          1. 如果不存在,创建
          2. 否则不创建,给出提示
    7. isFile():boolean
      1. 判断File对象指向的是否是文件
    8. isDirectory():boolean
      1. 判断File对象指向的是否是目录
1…567

Alone5

70 日志
1 标签
© 2020 Alone5
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4