Java语言设计实验报告
设计者:李晋 考号:010122112755 联系电话19802162861
1. 实验选题:简单Java语句练习,基本图形编程,Java网络编程
2. 实验目的和要求
目的:了解Java语言的基本编程方法,了解Java语言的网络编程和图形编程。
要求: 实验1中要求使用排序算法和查找算法对输入的数据进行排序和查找,实验2中要求使用简洁美观的界面实现计算器的逻辑计算,确保计算结果的准确计算显示,实验3要求使用简洁美观的界面和Socket网络编程实现客户端与服务端的通信,实验结果必须正确且符合题目要求
3. 实验设计
主要涉及的控件:
RecyclerView CardView TextView EditText
界面设计:
实验1:
- **排序算法选择**:**快速排序算法或冒泡排序算法**
- **查找算法选择:二分查找算法**
实验2:
- <br />
实验3:
代码设计:
实验1
排序算法选择:快速排序算法或冒泡排序算法
冒泡排序算法
- **设计思路** - **描述** - 遍历数据,将相邻两个元素进行比较,大的数据放右边,小的数据放左边,以此类推。遍历完成后所有数据变成有序序列 - **实现** 1. 定义一个**数组变量arr,**获取用户输入的8个值,将这8个值放入**arr**中。 1. 定义一个方法**bobbleSort(int arr[]),**用于接收一个数组进行排序 1. 定义第一层遍历条件,**for(int i = 0; i < arr.length; i++)**,用于一共比较多少趟 1. 定义第二层遍历条件。**for(int j = 0; j < arr.length - i -1),**用于一共比较一趟里边要比较多少次,而比较多少次则是**数据长度减去趟数,**至于为什么**减1**下一步说。 1. 当条件满足后就可以进行交换了,交换之前要判断当前数据是否大于下一个数据,所以加入判断条件**arr[j] >** **arr[j + 1]**,条件满足后再进行交换。在第二层遍历中定义一个临时交换的变量**temp,**将**arr[j]**的值赋值给**temp**,再将**arr[j + 1]**的值赋值给**arr[j]**,最后讲**temp**赋值给**arr[j + 1]**完成交换。当访问**arr[j + 1]**的时候,如果是最后一个数据则访问不到,所以为了防止数组越界,需要在遍历条件中**减一。** - 代码实现
// 冒泡排序 public static void bobbleSort(int[] arr){ // 外层循环是一共比较多少趟 for (int i = 0; i < arr.length; i++){ // 内层循环是比较多少次,减1防止越界 for (int j = 0; j < arr.length - i - 1; j++){ if (arr[j] > arr[j + 1]){ int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } }
快速排序算法
- 设计思路
- 描述
- 首先通过一趟排序将要排序的数据分割成独立的两个部分,其中一部分的数据比另外一部分的数据都要小,然后再按此方法对这两部分数据进行快速排序,整个排序过程可以递归进行,以此达到所有数据变成有序序列
- 实现
- 定义一个数组变量arr,获取用户输入的8个值,将这8个值放入arr中,定义一个中间值变量pivot,比如数据[8,6,7,9,3,2],这个中间值就是(最左边的索引值 ➕ 最右边的索引值 )➗ 2 🟰 2(在Java中int类型的数据的除法采用的是去尾法,5 / 2 = 2.5,去掉0.5就是2),最后的结果是一个索引值,即对应数据中的数字7,这个7就是中间值,并且以7为中心分隔开了两部分数据:[8,6],7,[9,3,2]。
- 定义一个方法quickSort(int [] arr,int leftIndex,int rightIndex),用于接收一个数组和两个两个索引值变量leftIndex和rightIndex,再定义两个索引值变量l和r并把leftIndex和rightIndex的值分别赋值给 l 和 r,用于操作arr的数据,再定义一个用于临时交换的变量temp。定义第一层遍历条件 l < r,用于将比pivot小的数字放在左边,比pivot大的数字放在右边。先遍历左边部分的数据,定义第二层遍历条件arr[l] < pivot,如果条件成立说明arr[l] < pivot,不需要进行交换,将 l 的值 + 1之后进入下一轮遍历。如果条件不成立,说明arr[l] > pivot,遍历条件退出,开始遍历右边的数据。定义第二层遍历条件arr[r] > pivot,如果条件成立说明arr[r] > pivot,不需要进行交换,将 r 的值 -1之后进入下一轮遍历。如果条件不成立,说明arr[r] < pivot,遍历条件退出。注意,左边的数据是从左往右开始遍历,而右边的数据则是从右往左开始遍历。如果 l >= r,则使用break语句退出所有循环,当 l >= r 时说明已经重合,并且左边是全部小于pivot的值,右边是全部大于pivot的值,不需要继续遍历。当这三个条件结束之后就可以知道arr[l] > pivot,arr[r] < pivot,此时就可以使用temp变量开始交换位置。交换完毕之后,如果发现arr[l]==pivot,则说明不需要比较了,直接让 r的值 - 1 进行下一轮比较就好了。反之arr[r] ==pivot, l的值 - 1。此时当走完上述步骤后,pivot左边和右边就分别是小于和大于pivot的值了。
- 当走完步骤2后,会发现pivot两边的值并不是有序的。因此同样还需要对左边和右边的数据进行步骤2的操作。在进行操作前需要有一个条件,如果 l == r,说明步骤2条件满足但是退不出来,会出现栈溢出。所以当 l ==r时需要将 l的值+1,r的值-1,此时就可以进行向左递归和向右递归了
- 向左递归的话是对左边的数据进行递归,所以有一个判断条件是leftIndex < r,然后再次执行quickSort(int [] arr,int leftIndex,int rightIndex)方法,将arr、leftIndex、r传递进去进行向左递归。同理,向右递归则是rightIndex > l,执行quickSort(int [] arr,int leftIndex,int rightIndex)方法,将arr、l、rightIndex传递进去进行向右递归。做完之后就完成了排序
- 代码实现
// 快速排序 public static void quickSort(int[] arr,int leftIndex,int rightIndex){ int l = leftIndex; // 左下标 int r = rightIndex; // 右下标 int pivot = arr[(leftIndex + rightIndex) / 2]; // 中间值 int temp; // 临时变量,交换时使用 // while 是让比pivot小的值放到左边 比pivot大的值放右边 while (l < r){ // 在pivot的左边一直找,找到一个大于等于pivot的值退出 while (arr[l] < pivot){ l += 1; } while (arr[r] > pivot){ r -= 1; } // 如果l >= r 成立,说明pivot的左右两边的值已经按照左边全部是小于等于pivot的值,右边全部是大于等于pivot的值 if (l >= r){ break; } // 交换 temp = arr[l]; arr[l] = arr[r]; arr[r] = temp; // 如果交换完毕后发现arr[l] == pivot,r-- 前移 if (arr[l] == pivot){ r -= 1; } // 如果交换完毕后发现arr[r] == pivot,l-- 后移 if (arr[r] == pivot){ l += 1; } } if (l == r){ l += 1; r -= 1; } // 向左递归 if (leftIndex < r){ quickSort(arr, leftIndex, r); } if (rightIndex > l){ quickSort(arr,l,rightIndex); } }
- 描述
- 设计思路
查找算法选择:二分查找算法
- **二分查找算法** - **设计思路** - **描述** - 建立在有序序列基础上,输入一个数据,判断是否存在于数据序列。设置一个中间值,如果要查找的数据大于中间值,则从右边开始往左遍历,反之从左边开始往右遍历 - **实现** 1. 定义一个方法**binarySearch(int [] arr,int key)。**定义两个变量**left和right**用于获取**arr**的首尾索引值。**left = 0;right = arr.length - 1** 1. 设置**遍历条件left <= right,**然后在遍历中定义一个**mid**变量用于找到**中间值索引,**然后在定义一个**midVal**变量用于拿到**中间值。mid = (left + right)/ 2,midVal=arr[mid]。**根据用户输入的**key**值进行判断是否存在于此数列中,所以让**key值**与**midVal值**进行比较,如果**midVal < key,**则说明key存在于**mid**右边**,**所以更改**left**的值为**mid + 1,进行下一次遍历。**反之如果**midVal > key,**说明key值存在于**mid**左边,更改**right**的值为**mid -1,进行下一次遍历。**以此类推遍历后就能找到key值是否存在于**arr**中 - 代码实现
// 二分查找 public static int binarySearch(int[] arr, int key) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = (left + right) / 2; int midVal = arr[mid]; if (midVal < key) left = mid + 1; else if (midVal > key) right = mid - 1; else return mid; } return -1; }
实验2
- 计算器设计思路
```java public interface RecyclerViewListener { // number click void OnItemClick(String number); // 归零点击 void OnAcButtonClick(); // 百分比点击 void OnPercentSignClick(); // 删除点击 void OnDelBtnClick(); // 加减乘除 void OnOperatorBtnClick(String symbol); // 等于 void OnEqualsBtnClick(); }- **描述** - 利用数组的入栈和出栈实现计算器的计算,实现两个栈,一个数字栈,一个计算符号栈。通过两个栈的遍历计算,计算出最后的结果 - **设计思路** - **界面设计** - 使用RecyclerView + Adapter + CardView + TextView实现计算器按键界面,并使用**interface**监听每一个按键的结果 - 代码设计
- 计算器设计思路
- 使用StringBuilder实现当按下按键后将结果拼接展示在界面
- 当按下等号时要计算出前面的结果
- 定义一个栈对象**ArrayStack**,栈对象中存放一个栈变量**arrayStack,**栈类型为**ArrayList<String>**
- 栈对象中应当具有以下方法:
- 查看栈顶的值
- 判断栈是否为空
- 入栈
- 出栈比较运算符的优先级(先乘除后加减)
- 是否是一个运算符
- 计算结果
```java
public class ArrayStack {
private final ArrayList<String> arrayStack = new ArrayList<>();
// 用于操作栈顶
private int top = -1;
// 查看栈顶的值
public String peek(){
return arrayStack.get(top);
}
// 栈空
public boolean isEmpty(){
return top == -1;
}
// 入栈
public void push(String value){
top++;
arrayStack.add(top,value);
}
// 出栈
public String pop(){
if (isEmpty()){
throw new RuntimeException("栈空,没有数据");
}
String value = arrayStack.get(top);
top--;
return value;
}
// 遍历栈,遍历时,需要从栈顶开始显示数据
public void list(){
if (isEmpty()){
Log.e(TAG, "栈空,没有数据");
return;
}
for (int i = top; i >= 0; i--){
Log.e(TAG, arrayStack.get(i));
}
}
/**
* 返回运算符的优先级
* @param operator 符号
* @return 运算级别,数字表示
*/
public int priority(String operator){
if (operator.equals(MULTIPLY) || operator.equals(DIVIDE)){
return 1;
}else if (operator.equals(PLUS) || operator.equals(SUBTRACT)){
return 0;
}else {
return -1;
}
}
/**
* 判断是否是一个运算符
* @param value 按下的字符
* @return 是否是一个运算符
*/
public boolean isOperator(String value){
return value.equals(PLUS) || value.equals(SUBTRACT) || value.equals(MULTIPLY) || value.equals(DIVIDE);
}
/**
* 计算结果
* @param num1 第一个数字
* @param num2 第二个数字
* @param operator 计算符
* @return 结果
*/
public String calc(String num1,String num2,String operator){
Log.e(TAG, "calc: " + "num1:" + num1 + "<--->" + "num2:" + num2);
String result = ZERO; // 用于存放计算的结果
switch (operator){
case PLUS:
if (num1.contains(POINT) || num2.contains(POINT)){
result = String.valueOf(Float.parseFloat(num1) + Float.parseFloat(num2));
}else {
result = String.valueOf(Integer.parseInt(num1) + Integer.parseInt(num2));
}
break;
case SUBTRACT: // 注意顺序
if (num1.contains(POINT) || num2.contains(POINT)){
result = String.valueOf(Float.parseFloat(num2) - Float.parseFloat(num1));
}else {
result = String.valueOf(Integer.parseInt(num2) - Integer.parseInt(num1));
}
break;
case MULTIPLY:
if (num1.contains(POINT) || num2.contains(POINT)){
result = String.valueOf(Float.parseFloat(num1) * Float.parseFloat(num2));
}else {
result = String.valueOf(Integer.parseInt(num1) * Integer.parseInt(num2));
}
break;
case DIVIDE:
result = String.valueOf(Float.parseFloat(num2) / Float.parseFloat(num1));
break;
default:
break;
}
return result;
}
private static final String TAG = "ArrayStack";
public static final String PLUS = "+";
public static final String SUBTRACT = "-";
public static final String MULTIPLY = "×";
public static final String DIVIDE = "÷";
public static final String POINT = ".";
public static final String ZERO = "0";
public static final String EQUAL = "=";
public static final String AC = "AC";
public static final String DEL = "Del";
public static final String PERCENT_SIGN = "%";
}
- 计算表达式结果思路
- 当按下等号时,将表达式显示在屏幕上,并将表达式传入一个**getCalculatedResultMethod(String expresion)中**
- 定义两个变量,分别为**numStack和operatorStack,代表数字栈和符号栈,**获取定义好的**ArrayStack对象**
- 在**getCalculatedResultMethod(String expresion)**方法中定义7个变量:**index(用于扫描表达式)、num1(数字栈中栈顶的值)、num2(数字栈中栈顶后一位的值)、opreator(符号)、res(计算结果)、ch(截取表达式下一位字符判断是否是符号)、keepNum(查看下一位字符是否是数字,如果是数字就继续添加到数字栈)**
- **遍历表达式字符,首先使用substring(index,index + 1)截取第一位字符赋值给ch查看是否是符号,如果是符号并且符号栈不为空就将符号入栈,入栈前要做一次判断,查看符号栈栈顶的符号的运算级别是否小于等于当前符号的运算级别,如果小于等于的话就将数字栈的栈顶的数字赋值给num1,再将栈顶后一位的值赋值给num2,再将符号栈内栈顶的值拿出来进行计算,将计算后的值推入numStack的栈顶。如果不是符号,则先将keepNum与ch合并,如果此时index正好是表达式的最后一位字符,则将keepNum入数字栈。否则的话通过substring(index+1,index+2)查看后一位是否是数字,如果是数字的话将keepNum入数字栈。随后进入下一轮遍历**
- **当遍历完成表达式所有字符时,则可以遍历符号栈,将栈顶的值和栈顶后一位的值还有符号栈栈顶的值一次次的参与计算,并且每一次计算完成后都将结果推送至栈顶,这样的话在每一次遍历的时候都拿到的是计算完成和最新的值,当条件不成立后栈顶的值就是最后的计算结果。**
- **代码设计**
ArrayStack numStack = new ArrayStack();
ArrayStack operatorStack = new ArrayStack();
public String getCalculatedResultMethod(String expression){
int index = 0; // 用于扫描
String num1;
String num2;
String operator;
String res;
String ch;
String keepNum = "";
do {
// 判断是否是字符
ch = expression.substring(index, index + 1);
if (operatorStack.isOperator(ch)) {
if (!operatorStack.isEmpty()) {
// 比较优先级
if (operatorStack.priority(ch) <= operatorStack.priority(operatorStack.peek())) {
num1 = numStack.pop();
num2 = numStack.pop();
operator = operatorStack.pop();
res = numStack.calc(num1, num2, operator);
// 放入栈顶
numStack.push(res);
}
}
operatorStack.push(ch);
} else {
keepNum += ch;
if (index == expression.length() - 1){
numStack.push(keepNum);
}else {
if (operatorStack.isOperator(expression.substring(index + 1,index + 2))){
numStack.push(keepNum);
keepNum = "";
}
}
}
index++;
} while (index < expression.length());
while (!operatorStack.isEmpty()) {
num1 = numStack.pop();
num2 = numStack.pop();
operator = operatorStack.pop();
res = numStack.calc(num1, num2, operator);
numStack.push(res);
}
return numStack.pop();
}
实验3
Socket客户端与服务端聊天代码实现思路
- 界面
- 使用两个RecyclerView实现客户端与服务端的聊天窗口
- 使用Activity + Fragment实现当启动服务并连接服务后,才能打开聊天界面的功能
- 使用Navigation完成Fragment之间的跳转
- 服务端
- 获取用户输入一个端口号,使用Java的ServerSocket类开启一个本地服务端口号
- 获取本机IP地址,显示至界面,客户端点击连接服务可直接连接到服务
- 获取用户输入消息,点击发送消息至客户端,使用Java的Socket类获取ServerSocket的accept连接,这个accept就是客户端
- 当客户端发过来消息时,可以使用DataInputSteam进行接收消息并展示在界面
- 当客户端连接后,通过DataOutputSteam进行向客户端发送消息并展示在界面
- 监听客户端与本身所有状态 ```java public class Server { private final ServiceListener serviceListener; public Server(ServiceListener serviceListener){ this.serviceListener = serviceListener; } private static final String TAG = “Server”; ServerSocket serverSocket = null; private Socket socket; private DataInputStream in; private DataOutputStream out;
/**
- 开启服务
@param port 指定端口,4位数字,开头不可为0 */ public void startServer(int port) { new Thread(() -> {
try { Log.e(TAG, "startServer: waiting...."); serverSocket = new ServerSocket(port); serviceListener.OnServiceStart(getLocalIpAddress() + ":" + port); Log.e(TAG, "等待客户端链接--->"); socket = serverSocket.accept(); in = new DataInputStream(socket.getInputStream()); out = new DataOutputStream(socket.getOutputStream()); serverSendMessage("客户端,你好,我是服务端!"); serviceListener.OnClientConnected(socket.getInetAddress().toString()); Log.e(TAG, "客户端已链接 客户端地址:" + socket.getInetAddress() + "连接到" + "本机地址:" + socket.getLocalAddress()); while (true) { try { serviceListener.OnClientsMessagesReceived(in.readUTF()); } catch (IOException exception) { exception.printStackTrace(); } } } catch (IOException exception) { exception.printStackTrace(); }
}).start(); }
/**
- 向客户端发送消息
- @param message 消息内容/文本
*/
public void serverSendMessage(String message){
new Thread(() -> {
}).start(); }try{ if (socket.isConnected()){ out.writeUTF(message); } }catch (IOException e){ e.printStackTrace(); }
/**
- 获取当前服务IP地址
@return IPv4下的IP地址 */ private static String getLocalIpAddress() { try{
Enumeration<NetworkInterface> enumeration = NetworkInterface.getNetworkInterfaces(); while (enumeration.hasMoreElements()){ NetworkInterface networkInterface = enumeration.nextElement(); Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses(); while (inetAddresses.hasMoreElements()){ InetAddress inetAddress = inetAddresses.nextElement(); if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address){ return inetAddress.getHostAddress(); } } }
}catch (SocketException exception){
exception.printStackTrace();
} return null; } } public interface ServiceListener { // 服务开启成功 void OnServiceStart(String ipAddress); // 客户端连接成功 void OnClientConnected(String connectedMsg); // 当接收到客户端消息时 void OnClientsMessagesReceived(String message); } ```
- 客户端
- 获取用户输入的IP地址和端口号,使用Java的Socket类,连接至服务端
- 使用DataInputStream获取服务端发来的消息并展示在界面
- 通过DataOutputSteam向客户端发送消息
- 监听服务器各种状态 ```java public class Client { private Socket socket; DataInputStream dataInputStream; DataOutputStream dataOutputStream; private final ClientListener clientListener; public Client(ClientListener clientListener){ this.clientListener = clientListener; }
- 客户端
/**
- 连接至服务端
- @param ipAddress 服务端IP地址
- @param port 服务端端口
*/
public void connected(String ipAddress,int port){
new Thread(() -> {
}).start(); }try{ InetAddress ip = InetAddress.getByName(ipAddress); // 连接服务器 socket = new Socket(ipAddress,port); dataOutputStream = new DataOutputStream(socket.getOutputStream()); // 创建DataOutputStream对象 发送数据 dataInputStream = new DataInputStream(socket.getInputStream()); sendMsgToServer("服务端,你好,我是客户端!"); clientListener.OnServiceConnected("服务器已经链接 -->" + ip + ":" + port); while (true){ try{ clientListener.OnServerMessageArrive(dataInputStream.readUTF()); }catch (IOException exception){ exception.printStackTrace(); } } }catch (Exception e){ e.printStackTrace(); clientListener.OnServiceConnectedFailed("链接服务失败" + e); }
/**
- 关闭连接
*/
public void disconnected(){
try{
}catch (Exception e){if (socket != null && socket.isConnected()){ socket.close(); socket = null; clientListener.OnClientDisconnected("服务已经断开"); }
} }e.printStackTrace();
/**
- 发送消息至服务端
- @param message 消息内容
*/
public void sendMsgToServer(String message){
if (dataOutputStream != null){
} } } // 监听状态 public interface ClientListener { // 当连接到服务时 void OnServiceConnected(String message); // 连接服务失败 void OnServiceConnectedFailed(String message); // 断开连接 void OnClientDisconnected(String message); // 当接收到服务端消息时 void OnServerMessageArrive(String message); } ```new Thread(() -> { try { dataOutputStream.writeUTF(message); } catch (IOException exception) { exception.printStackTrace(); } }).start();
- 界面
4. 测试步骤
实验1
- 输入:9,9,7,5,4,6,3,2,每次输入后回车
输出:快速排序算法结果[2, 3, 4, 5, 6, 7, 9, 9]
Please enter one key:
输入 :9
- 输出:exit
- 输入:9,9,7,5,4,6,3,2,每次输入后回车
输出:冒泡排序算法结果[2, 3, 4, 5, 6, 7, 9, 9]
Please enter one key:
输入:1
- 输出:Does not exist
- 测试展示
- 实验2
- 输入:12-9✖️6
- 输出:-42
- 输入:12+3✖️4-5
- 输出:19
- 输入:10+6➗2
- 输出:13
- 输入:11+1-1
- 输入:Del
- 输入:Del
- 输出:11+1
- 输入:100+100
- 输入:AC
- 输出:0
- 测试展示
- 实验3
- 输入:端口号为2000
- 输入:开启服务
- 输出:
- 服务器开启状态:已开启
- 服务地址:192.168.1.6:2000
- 客户端连接状态:未连接
- 客户端地址:未知
- 输入:连接服务
- 输出:
- 客户端连接状态:已连接
- 客户端地址:/192.168.1.6
- 输入:开启聊天
- 输出:
- 进入聊天页面
- 测试展示
5. 心得
通过该次实验,熟悉了Java语言的基础语法,比如for循环、while循环,还有变量的自增和自减,定义方法的类型和返回值等。了解了一些常用算法,比如二分查找算法、快速排序算法和冒泡排序算法等。最让我感兴趣的是Socket网络编程,和计算器,让我学习到了界面的开发与应用,比如RecyclerView的使用、TextView的使用、点击事件的触发等等。我了解到了Socket可以用来进行本地通信和网络通信,是通信的一个技术实现。在做计算器实验中我遇到了总是排序不正常的困难,通过使用开发工具的断点调试功能帮助我解决了问题。关于语法层面也有一些问题,比如说在for遍历循环中的一些计算问题,要精准的定位到要循环的次数等。
〖附〗主要源程序代码