Android 连接 SQL Server (jtds方式)——下
本文主要补充介绍jtds的查询方法,将以博主的一个实际开发程序进行说明
下图是项目的文件列表与界面效果:
运行效果:
1、三个EditText对应的是单个计划的序号、品种名、数量
2、填入三个数据后,点击“增加”,Insert到数据库中
3、填入三个数据后,点击“修改”,根据序号,Update数据库中的品种名、数量
4、填入序号后,点击“删除”,根据序号,Delete数据库中的一行记录
5、点击“查询全部”,在下方GridView中显示整个表格的记录
6、点击“清空”,三个EditText的内容清空,GridView的内容清空
数据库信息:
1、数据库名:SYSTEM TEST
2、用户名:sa
3、密码:123
4、表名:DayPlan
5、字段1:ID,nvarchar =>表示序号
6、字段2:PCBA,nvarchar =>表示品种名
7、字段3:AMOUNT,nvarchar =>数量
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.androidsqltest"
android:versionCode="1"
android:versionName="1.0" > <uses-permission android:name="android.permission.INTERNET" /> <uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="21" /> <application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application> </manifest>
插入网络操作权限
strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources> <string name="app_name">AndroidSqlTest</string>
<string name="action_settings">Settings</string>
<string name="id">序 号:</string>
<string name="pcb">机种名:</string>
<string name="amount">数 量:</string>
<string name="insert">增加</string>
<string name="update">修改</string>
<string name="delete">删除</string>
<string name="clear">清空</string>
<string name="select">查询全部</string> </resources>
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.test.androidsqltest.MainActivity" > <TextView
android:id="@+id/tvId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="5dp"
android:text="@string/id" /> <TextView
android:id="@+id/tvPcb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="45dp"
android:text="@string/pcb" /> <TextView
android:id="@+id/tvAmount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="85dp"
android:text="@string/amount" /> <EditText
android:id="@+id/etId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="70dp"
android:layout_marginLeft="70dp"
android:layout_marginTop="-5dp"
android:inputType="text"
android:ems="10" > <requestFocus />
</EditText> <EditText
android:id="@+id/etPcb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="70dp"
android:layout_marginLeft="70dp"
android:layout_marginTop="35dp"
android:inputType="text"
android:ems="10" /> <EditText
android:id="@+id/etAmount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="70dp"
android:layout_marginLeft="70dp"
android:layout_marginTop="75dp"
android:inputType="text"
android:ems="10" /> <Button
android:id="@+id/btnInsert"
android:layout_width="80dp"
android:layout_height="40dp"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="120dp"
android:text="@string/insert" /> <Button
android:id="@+id/btnUpdate"
android:layout_width="80dp"
android:layout_height="40dp"
android:layout_marginStart="105dp"
android:layout_marginLeft="105dp"
android:layout_marginTop="120dp"
android:text="@string/update" /> <Button
android:id="@+id/btnDelete"
android:layout_width="80dp"
android:layout_height="40dp"
android:layout_marginStart="205dp"
android:layout_marginLeft="205dp"
android:layout_marginTop="120dp"
android:text="@string/delete" /> <Button
android:id="@+id/btnClear"
android:layout_width="120dp"
android:layout_height="40dp"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="180dp"
android:text="@string/clear" /> <Button
android:id="@+id/btnSelect"
android:layout_width="120dp"
android:layout_height="40dp"
android:layout_marginStart="165dp"
android:layout_marginLeft="165dp"
android:layout_marginTop="180dp"
android:text="@string/select" /> <GridView
android:id="@+id/gv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="240dp"
android:numColumns="1"
android:verticalSpacing="15dp" >
</GridView> </RelativeLayout>
planlist.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent" > <TextView android:id="@+id/header1"
android:layout_height="wrap_content"
android:layout_width="60px"
android:layout_marginLeft="0px"
android:textColor="#000000"
android:text="">
</TextView> <TextView android:id="@+id/header2"
android:layout_height="wrap_content"
android:layout_width="180px"
android:layout_marginLeft="0px"
android:textColor="#000000"
android:text="">
</TextView> <TextView android:id="@+id/header3"
android:layout_height="wrap_content"
android:layout_width="80px"
android:layout_marginLeft="0px"
android:textColor="#000000"
android:text="">
</TextView> </LinearLayout>
这是GridView中单个item的布局,即一行记录有3个TextView
SqlHelper.java:
package MyJtds; import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.List; import org.json.JSONArray;
import org.json.JSONObject; public class SqlHelper {
private String drive = "net.sourceforge.jtds.jdbc.Driver";
private String connStr;
private String server;
private String dbName;
private String userName;
private String userPwd;
private Connection con;
private PreparedStatement pstm; public SqlHelper(String server, String dbName, String userName, String userPwd) {
this.server = server;
this.dbName = dbName;
this.connStr = "jdbc:jtds:sqlserver://" + this.server + ":1433/" + this.dbName;
this.userName = userName;
this.userPwd = userPwd; try {
Class.forName(drive);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} public int ExecuteNonQuery(String sql, List<Object> params) {
try {
con = DriverManager.getConnection(this.connStr, this.userName, this.userPwd);
pstm = con.prepareStatement(sql);
if (params != null && !params.equals("")) {
for (int i = 0; i < params.size(); i++) {
pstm.setObject(i + 1, params.get(i));
}
}
return pstm.executeUpdate();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return -1;
} finally {
try {
pstm.close();
con.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} public String ExecuteQuery(String sql, List<Object> params) {
// TODO Auto-generated method stub
JSONArray jsonArray = new JSONArray();
try {
con = DriverManager.getConnection(this.connStr, this.userName, this.userPwd);
pstm = con.prepareStatement(sql);
if (params != null && !params.equals("")) {
for (int i = 0; i < params.size(); i++) {
pstm.setObject(i + 1, params.get(i));
}
}
ResultSet rs = pstm.executeQuery();
ResultSetMetaData rsMetaData = rs.getMetaData();
while (rs.next()) {
JSONObject jsonObject = new JSONObject();
for (int i = 0; i < rsMetaData.getColumnCount(); i++) {
String columnName = rsMetaData.getColumnLabel(i + 1);
String value = rs.getString(columnName);
jsonObject.put(columnName, value);
}
jsonArray.put(jsonObject);
}
return jsonArray.toString();
} catch (Exception e) {
// TODO: handle exception
return null;
} finally {
try {
pstm.close();
con.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Plan.java:
package Models; public class Plan {
public String id; public String pcb; public String amount; public String getId() {
return id;
} public void setId(String id) {
this.id = id;
} public String getPcb() {
return pcb;
} public void setPcb(String pcb) {
this.pcb = pcb;
} public String getAmount() {
return amount;
} public void setAmount(String amount) {
this.amount = amount;
}
}
MainActivity.java:
package com.test.androidsqltest; import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import Models.Plan;
import MyJtds.SqlHelper;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.SimpleAdapter;
import android.widget.Toast; public class MainActivity extends Activity { // 定义控件
private EditText etId;
private EditText etPcb;
private EditText etAmount;
private Button btnInsert;
private Button btnUpdate;
private Button btnDelete;
private Button btnClear;
private Button btnSelect;
private GridView gv;
// 定义变量
private String id = "";
private String pcb = "";
private String amount = "";
// SQL帮助类,参数用于设置连接字符串,参数1:主机ip,参数2:数据库名,参数3:用户名,参数4:用户密码
private SqlHelper sh = new SqlHelper("192.168.1.1", "SYSTEM TEST", "sa", "123"); @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // 找到变量,并且赋值
etId = (EditText) findViewById(R.id.etId);
etPcb = (EditText) findViewById(R.id.etPcb);
etAmount = (EditText) findViewById(R.id.etAmount);
btnInsert = (Button) findViewById(R.id.btnInsert);
btnUpdate = (Button) findViewById(R.id.btnUpdate);
btnDelete = (Button) findViewById(R.id.btnDelete);
btnClear = (Button) findViewById(R.id.btnClear);
btnSelect = (Button) findViewById(R.id.btnSelect);
gv = (GridView) findViewById(R.id.gv); // 绑定按钮的click事件监听,click事件触发后,运行clickEvent()方法
// 绑定的方式都是clickEvent(),到时在方法体中判断按下的是哪个按键
btnInsert.setOnClickListener(clickEvent());
btnUpdate.setOnClickListener(clickEvent());
btnDelete.setOnClickListener(clickEvent());
btnClear.setOnClickListener(clickEvent());
btnSelect.setOnClickListener(clickEvent());
} // clickEvent()
private OnClickListener clickEvent() {
// TODO Auto-generated method stub
return new OnClickListener() { // clickEvent()方法体,参数是控件的基类View,必须加上final
@Override
public void onClick(final View view) {
// TODO Auto-generated method stub
// 用view来判断按下的哪个按钮
if (view == btnClear) {
// ClearEdit()方法用于复位控件
ClearEdit();
} else {
// 如果不是btnClear,那就是增删改查的按钮,必须开启新的线程进行操作
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
// 通过Message类来传递结果值,先实例化
Message msg = Message.obtain();
// 下面分别是增删改查方法
if (view == btnInsert) {
// 设定msg的类型,用what属性,便于后面的代码区分返回的结果是什么类型
// 这里的1是指操作是否成功,String
// 这里的2是指查询的结果,String,用json的形式表示
msg.what = 1;
msg.obj = Insert();
} else if (view == btnUpdate) {
msg.what = 1;
msg.obj = Update();
} else if (view == btnDelete) {
msg.what = 1;
msg.obj = Delete();
} else if (view == btnSelect) {
String jsonResult = Select();
msg.what = 2;
msg.obj = jsonResult;
} else { }
// 执行完以后,把msg传到handler,并且触发handler的响应方法
handler.sendMessage(msg);
}
});
// 进程开始,这行代码不要忘记
thread.start();
}
}
};
} // Handler类用于接收Message的值,并且其父类有一个默认的handleMessage方法,用super。handleMessage()方法,传入msg,就能控制主线程的控件了
@SuppressLint("HandlerLeak")
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
// 调用super的方法,传入handler对象接收到的msg对象
super.handleMessage(msg);
// 判断msg.what的值,有1和2,
// 1表示操作是否成功,2表示查询时得到的json结果
switch (msg.what) {
case 1:
// 获得执行的结果,String字符串,返回操作是否成功提示
String rst = msg.obj.toString();
// 使用气泡提示
Toast.makeText(getApplicationContext(), rst, Toast.LENGTH_SHORT).show();
break;
case 2:
// 获得查询的json结果
String jsonResult = msg.obj.toString();
// 控制台输出,用于监视,与实际使用无关
System.out.println(jsonResult); // Gson类,用于json的转类型操作
Gson gson = new Gson();
// 定义查询到的结果类型,每一行记录映射为对象,本程序查询的是生产计划,所以一行记录表示一个品种的生产计划,用Plan类表示,用List收集全部Plan类
Type type = new TypeToken<List<Plan>>() {
}.getType();
// 使用gson的fromJson()方法,参数1:json结果,参数2:想要转哪一个类型
List<Plan> plans = gson.fromJson(jsonResult, type); // 由于要使用GridView表示,绑定数据时只能使用Map<K,T>的类型,并且多个记录时,要用List<Map<K,T>>
// 先实例化
List<Map<String, String>> mPlans = new ArrayList<Map<String, String>>();
// 实例化一个title,是GridView的列头
Map<String, String> title = new HashMap<String, String>();
title.put("id", "序号");
title.put("pcb", "机种名");
title.put("amount", "计划数");
// 首先把表头追加到List<Map<String, String>>
mPlans.add(title); // for循环从json转过来的List<Plan>
for (Plan plan : plans) {
// 实例化用于接收plan的HashMap<K,T>类
HashMap<String, String> hmPlans = new HashMap<String, String>();
// 使用put()方法把数值加入到HashMap<K,T>,参数1:键,参数2:值
hmPlans.put("id", plan.id);
hmPlans.put("pcb", plan.pcb);
hmPlans.put("amount", plan.amount);
// 把HashMap加入到List<Map>中
mPlans.add(hmPlans);
} // SimpleAdapter是GridView的适配器,参数1:上下文内容,参数2:List<Map<K,T>>对象,参数3:GridView的布局文件,指每一个item的布局,需要在res/layout中创建xml,
// 参数4:String数组,指每一列要绑定到Map中的值,数组中的值就是上文“hmPlans.put("id",
// plan.id);”的键"id"
// 参数5:列头的显示文件,存放在res/values/strings.xml
SimpleAdapter sa = new SimpleAdapter(getApplicationContext(), mPlans, R.layout.planlist,
new String[] { "id", "pcb", "amount" }, new int[] { R.id.header1, R.id.header2, R.id.header3 });
// 把SimpleAdapter绑定到GridView
gv.setAdapter(sa); // 气泡提示
Toast.makeText(getApplicationContext(), "读取成功!", Toast.LENGTH_SHORT).show();
break;
default:
Toast.makeText(getApplicationContext(), "操作失败!", Toast.LENGTH_SHORT).show();
break;
}
}
}; // 用于接收EditText的输入值,并赋值到字符串
public void GetMsg() {
id = etId.getText().toString().trim();
pcb = etPcb.getText().toString().trim();
amount = etAmount.getText().toString().trim();
} // Insert()方法,通过判断受影响行数,返回“添加成功”或“操作失败”
public String Insert() {
String sql = "INSERT INTO [DayPlan]([ID],[PCBA],[AMOUNT]) VALUES (?,?,?)";
GetMsg();
List<Object> params = new ArrayList<Object>();
params.add(id);
params.add(pcb);
params.add(amount);
try {
int count = sh.ExecuteNonQuery(sql, params);
if (count == 1) {
return "添加成功!";
} else {
return "操作失败!";
}
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println(e.getMessage());
return "操作失败!";
}
} public String Update() {
String sql = "UPDATE [DayPlan] SET [PCBA]=?,[AMOUNT]=? where [ID]=?";
GetMsg();
// params用于存放变量参数,即sql中的“?”
List<Object> params = new ArrayList<Object>();
params.add(pcb);
params.add(amount);
params.add(id);
try {
int count = sh.ExecuteNonQuery(sql, params);
if (count == 1) {
return "更新成功!";
} else {
return "操作失败!";
}
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
return "操作失败!";
}
} public String Delete() {
String sql = "DELETE FROM [DayPlan] where [ID]=?";
GetMsg();
List<Object> params = new ArrayList<Object>();
params.add(id);
try {
int count = sh.ExecuteNonQuery(sql, params);
if (count == 1) {
return "删除成功!";
} else {
return "操作失败!";
}
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
return "操作失败!";
}
} // Select()方法,查询生产计划
public String Select() {
String sql = "SELECT [ID] AS id,[PCBA] AS pcb,[AMOUNT] AS amount FROM [DayPlan] ORDER BY id";
String jsonResult = null;
try {
// sh.ExecuteQuery(),参数1:查询语句,参数2:查询用到的变量,用于本案例不需要参数,所以用空白的new
// ArrayList<Object>()
jsonResult = sh.ExecuteQuery(sql, new ArrayList<Object>());
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
return null;
}
return jsonResult;
} // 界面复位,清空显示
public void ClearEdit() {
//清空文本输入框
etId.setText("");
etPcb.setText("");
etAmount.setText("");
//etId获得焦点
etId.setFocusable(true);
etId.setFocusableInTouchMode(true);
etId.requestFocus();
etId.requestFocusFromTouch();
//清空GridView的绑定值
gv.setAdapter(null);
} @Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
} @Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
完成。
Android 连接 SQL Server (jtds方式)——下的更多相关文章
- Android 连接 SQL Server (jtds方式)——上
本文将介绍开发Android程序,连接SQL Server,通过第三方包jtds的方式. 如果你有同样的需求,请跟着做一遍,博主将以最详细的方式,进行介绍. 首先说明,Java.Android连接SQ ...
- Android 连接网络数据库的方式
以连接MS SQL(sqlserver数据库)的网络数据库为例,从当前搜集的资料来看,一共有两种方式:在Android工程中引入JDBC驱动,直接连接:通过WebService等方法的间接连接. 采用 ...
- 【原创】Qt 使用ODBC driver 连接SQL Server
最近在做数据库的课程设计.第一个需要解决的问题是使用什么工具来实现这个系统.经过一番资料查找,决定使用SQL Server Express 2012作为服务器,使用Qt作为编写客户端程序语言.问题是c ...
- jTDS Java连接SQL Server 2000数据库
Java连接SQL Server 2000数据库时,有两种方法: (1)通过Microsoft的JDBC驱动连接.此JDBC驱动共有三个文件,分别 是mssqlserver.jar.msutil.ja ...
- Java连接SQL Server:jTDS驱动兼容性问题
Java连接SQL Server 2000数据库时,有两种方法: (1)通过Microsoft的JDBC驱动连接.此JDBC驱动共有三个文件,分别是mssqlserver.jar.msutil.jar ...
- ASP .Net Core 在 CentOS8 ARM 下连接 SQL Server 2008 R2(Hypervisor)
本文主要记录在 ARM 系统下无法连接SQL Server 2008 R2 的解决过程. 解决方案是使用 ODBC 的方式连接数据库,进行操作. 手上有公司的华为鲲鹏云计算 ARM 架构的 CentO ...
- phpstudy连接SQL Server 2008数据库 以及 php使用sql server出现乱码解决方式
开始也尝试自己配置php安装环境,找到一个详细的百度经验http://jingyan.baidu.com/article/154b46315242b328ca8f4101.html,前面有问题也一一去 ...
- Ubuntu下erlang连接SQL SERVER 2008
erlang连接SQL Server使用ODBC方法,但在网络上还是缺少资料,自己折腾了2天才成功.现在特记录下来,以供大家借鉴. 基本思路是 erlang odbcserver + unixodbc ...
- JeeSite如何正确连接SQL SERVER 数据库
JeeSite如何正确连接SQL SERVER 数据库 jeesite介绍 感谢jeesite项目的作者thinkgem. 没有你我也不会更改这数据源非了恁大的劲,,,,嘻嘻嘻说多了. JeeSite ...
随机推荐
- Notepad++插件之TextFX
Technorati 标记: notepad Notepad++插件TextFX Characters是一款默认安装的插件,由于功能强大,被编程爱好者认为是最好的Notepad++插件,第二名是L ...
- 转:基于ASP.NET的Comet长连接技术解析
原文来自于: Comet技术原理 来自维基百科:Comet是一种用于web的技术,能使服务器能实时地将更新的信息传送到客户端,而无须客户端发出请求,目前有两种实现方式,长轮询和iframe流. 简单的 ...
- Node.js Express框架
Express 介绍 Express是一个最小的,灵活的Node.js Web应用程序框架,它提供了一套强大的功能来开发Web和移动应用程序. 它有助于基于Node Web应用程序的快速开发.下面是一 ...
- The Child and Sequence
Codeforces Round #250 (Div. 1)D:http://codeforces.com/problemset/problem/438/D 题意:给你一个序列,然后有3种操作 1x ...
- cf B Inna and Candy Boxes
题意:输入n,然后输入n个数ai,再输入n个数bi,如果在1-ai中能找到两个数x,y,x和y可以相等,如果x+y=bi,答案加上x*y,否则减去1,让结果尽可能大,输出结果. #include &l ...
- Tiling(递推+大数)
Description In how many ways can you tile a 2xn rectangle by 2x1 or 2x2 tiles? Here is a sample tili ...
- 【VS调试】C#读写Windows 7注册表时抛出“不允许所请求的注册表访问权”的解决办法
原文:[VS调试]C#读写Windows 7注册表时抛出"不允许所请求的注册表访问权"的解决办法 项目 - 属性 - 安全性,"使用ClickOnce",修改a ...
- Gold Balanced Lineup(哈希表)
Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 10711 Accepted: 3182 Description Farm ...
- 嵌入式 linux 查看内存
在Windows系统中查看内存的使用情况很简单,想必大家都已经耳熟能详了,那么在linux系统如何查看内存使用情况呢?下面和大家分享在Linux下查看内存使用情况的free命令: [root@scs- ...
- 【单调栈】Vijos P1926 紫色的手链
题目链接: https://vijos.org/p/1926 题目大意: 给n个数(n<=100 000),求任意区间的最大值异或次大值的最大值. 题目思路: [模拟][单调栈] 我们维护一个严 ...