- groupBy
- filter
- sample
- distinct
- coalesce
- repartition
- sortBy
- union
- intersection
- subtract
- zip
- partitionBy
- reduceByKey
- groupByKey
- aggregateByKey
- foldByKey
- 数据结构
- 链表
- 单链表
- 双向链表
- 单向环形链表
- 栈
- 栈的应用
- 使用数组模拟栈
- 计算器(中缀表达式)
将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样 的操作称之为 shuffle。极限情况下,数据可能被分在同一个分区中
filter➢ 函数说明
将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。 当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出 现数据倾斜。
package operator.transform
import org.apache.spark.{SparkConf, SparkContext}
object FilterTest {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operate")
val sc = new SparkContext(sparkConf)
val rdd = sc.textFile("datas/apache.log")
rdd.filter(line=>{
val datas=line.split(" ")
val time=datas(3)
time.startsWith("17/05/2015")
}).collect().foreach(println)
sc.stop()
}
}
sample
根据指定的规则从数据集中抽取数据
package operator.transform
import org.apache.spark.{SparkConf, SparkContext}
object SampleTest {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operate")
val sc = new SparkContext(sparkConf)
val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9,10))
//sample算子需要传递三个参数
//1、第一个参数表示,抽取数据后是否将数据返回true(返回),false(丢弃)
//2、第二个参数表示,
// 如果抽取不放回的场合:数据源中每条数据被抽取的概率
// 如果抽取放回的场合:表示数据源中的每条数据被抽取的可能次数
//3、第三个参数表示,抽取数据时随机算法的种子
// 如果不传入第三个参数,那么使用的是当前系统时间
// println(rdd.sample(false,0.4,1).collect().mkString(","))
//
println(rdd.sample(true,3,1).collect().mkString(","))
}
}
distinct
将数据集中重复的数据去重
package operator.transform
import org.apache.spark.{SparkConf, SparkContext}
object DistinctTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("Operator")
val sc = new SparkContext(conf)
val rdd = sc.makeRDD(List(1, 2, 3, 4, 1, 2, 3))
val rdd1 = rdd.distinct()
rdd1.collect().foreach(println)
}
}
coalesce
根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率 当 spark 程序中,存在过多的小任务的时候,可以通过 coalesce 方法,收缩合并分区,减少 分区的个数,减小任务调度成本
package operator.transform
import org.apache.spark.{SparkConf, SparkContext}
object CoalesceTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("Operator")
val sc = new SparkContext(conf)
val rdd=sc.makeRDD(List(1,2,3,4),4)
//默认情况下,不会将数据打乱组合,可能导致数据不均衡
//val newRDD=rdd.coalesce()
//如果想要让数据均衡,可以进行shuffle处理
val newRDD=rdd.coalesce(2,true)
}
}
repartition
该操作内部其实执行的是 coalesce 操作,参数 shuffle 的默认值为 true。无论是将分区数多的 RDD 转换为分区数少的 RDD,还是将分区数少的 RDD 转换为分区数多的 RDD,repartition 操作都可以完成,因为无论如何都会经 shuffle 过程。
package operator.transform
import org.apache.spark.{SparkConf, SparkContext}
object RepartitionTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("Operator")
val sc = new SparkContext(conf)
val rdd=sc.makeRDD(List(1,2,3,4,5),2)
val newRdd = rdd.repartition(3)
}
}
sortBy
该操作用于排序数据。在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理 的结果进行排序,默认为升序排列。排序后新产生的 RDD 的分区数与原 RDD 的分区数一 致。中间存在 shuffle 的过程
package operator.transform
import java.util.Comparator
import org.apache.spark.{SparkConf, SparkContext}
object SortByTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("Operator")
val sc = new SparkContext(conf)
val rdd = sc.makeRDD(List(6, 5, 4, 3, 2,1),2)
//默认为升序
//val sort = rdd.sortBy(num=>num,)
//降序
val sort = rdd.sortBy(num=>num,false)
sort.collect().foreach(println)
}
}
union
对源 RDD 和参数 RDD 求并集后返回一个新的 RDD
intersection对源 RDD 和参数 RDD 求交集后返回一个新的 RDD
subtract以一个 RDD 元素为主,去除两个 RDD 中重复元素,将其他元素保留下来。求差集
zip将两个 RDD 中的元素,以键值对的形式进行合并。其中,键值对中的 Key 为第 1 个 RDD 中的元素,Value 为第 2 个 RDD 中的相同位置的元素。
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("Operator")
val sc = new SparkContext(conf)
val rdd1 = sc.makeRDD(List(1,2,3,4,5))
val rdd2=sc.makeRDD(List(4,5,6,7,8))
val rdd3 = rdd1.intersection(rdd2)
println(rdd3.collect().mkString(","))
val rdd4 = rdd1.union(rdd2)
println(rdd4.collect().mkString(","))
val rdd5=rdd1.subtract(rdd2)
println(rdd5.collect().mkString(","))
val rdd6=rdd1.zip(rdd2)
println(rdd6.collect().mkString(","))
}
交集、并集、差集要求类型要一致
partitionBy将数据按照指定 Partitioner 重新进行分区。Spark 默认的分区器是 HashPartitioner
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("Operator")
val sc = new SparkContext(conf)
val rdd = sc.makeRDD(List(1, 2, 3, 4, 5))
val mapRdd: RDD[(Int, Int)] =rdd.map((_,1))
//RDD=> PairRDDFunction
//隐式转换(二次编译)
mapRdd.partitionBy(new HashPartitioner(2)).saveAsTextFile("output")
}
reduceByKey
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("Operator")
val sc = new SparkContext(conf)
val rdd = sc.makeRDD(List(("a",1),("b",2),("c",3),("a",2)))
//相同的key的数据进行value数据的聚合操作
//如果key的数据只有一个,是不会参与运算的
val reduce = rdd.reduceByKey((x: Int, y: Int) => {
x + y
})
reduce.collect().foreach(println)
sc.stop()
}
groupByKey
将数据源的数据根据 key 对 value 进行分组
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("Operator")
val sc = new SparkContext(conf)
val rdd = sc.makeRDD(List(("a",1),("b",2),("c",3),("a",2)))
//将数据源中的数据,相同key的数据分在一个组中,形成一个对偶元组,元组中的第一个元素就是key,元组中的第二个元素就是相同key的value的集合
val groupRDD: RDD[(String, Iterable[Int])] = rdd.groupByKey()
groupRDD.collect().foreach(println)
val rdd2: RDD[(String, Iterable[(String, Int)])] = rdd.groupBy(_._1)
sc.stop()
}
从 shuffle 的角度:reduceByKey 和 groupByKey 都存在 shuffle 的操作,但是 reduceByKey 可以在 shuffle 前对分区内相同 key 的数据进行预聚合(combine)功能,这样会减少落盘的 数据量,而 groupByKey 只是进行分组,不存在数据量减少的问题,reduceByKey 性能比较 高。
从功能的角度:reduceByKey 其实包含分组和聚合的功能。GroupByKey 只能分组,不能聚 合,所以在分组聚合的场合下,推荐使用 reduceByKey,如果仅仅是分组而不需要聚合。那 么还是只能使用 groupByKey
aggregateByKey将数据根据不同的规则进行分区内计算和分区间计算
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("Operator")
val sc = new SparkContext(conf)
val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3)),2)
//存在函数的柯里化,有两个参数列表
//第一个参数列表 需要传递一个参数,表示初始值
// 主要用于当碰见第一个key的时候,和value进行分区内计算
//第二个参数表示需要传递2个参数 分别表示分区内的计算规则和分区间的计算规则
rdd.aggregateByKey(0)((x,y)=>math.max(x,y),(x,y)=>x+y).collect().foreach(println)
sc.stop()
}
foldByKey
当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为 foldByKey
数据结构 链表
链表是有序的
1、链表式以节点的方式来存储,是链式存储
2、每个节点包含data域,next域:指向下一个节点
3、链表的额各个节点不一定是连续存储
4、链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定
单链表package linkeList;
import java.util.Stack;
public class SinglelinkedListDemo {
public static void main(String[] args) {
//创建节点
HeroNode hero1 = new HeroNode(1, "a", "a");
HeroNode hero2 = new HeroNode(2, "b", "b");
HeroNode hero3 = new HeroNode(3, "c", "c");
HeroNode hero4 = new HeroNode(4, "d", "d");
HeroNode hero5 = new HeroNode(5, "e", "e");
HeroNode hero6 = new HeroNode(6, "f", "f");
SinglelinkedList singlelinkedList = new SinglelinkedList();
singlelinkedList.add(hero1);
singlelinkedList.add(hero2);
singlelinkedList.add(hero3);
singlelinkedList.add(hero4);
singlelinkedList.add(hero5);
singlelinkedList.add(hero6);
singlelinkedList.del(1);
singlelinkedList.update(new HeroNode(6, "g", "g"));
singlelinkedList.list();
System.out.println(getLength(singlelinkedList.getHead()));
System.out.println(findLastIndexNode(singlelinkedList.getHead(),2));
System.out.println("***********");
reverseList(singlelinkedList.getHead());
singlelinkedList.list();
System.out.println("*******反转打印*******");
reversePrint(singlelinkedList.getHead());
}
public static int getLength(HeroNode head){
int len=0;
HeroNode temp=head.next;
while (temp!=null){
len++;
temp=temp.next;
}
return len;
}
//1、编写一个方法接受一个head节点,同时接收一个index
//2、index表示是倒数第index个节点
//3、先把链表从头到尾遍历,得到链表的总长度getLength
//4、得到size后,从链表的第一开始遍历(size-index)个,可以得到
public static HeroNode findLastIndexNode(HeroNode head,int index){
//判断链表为空,返回null
if(head.next==null){
return null;
}
//第一个遍历得到链表的长度(节点个数)
int size=getLength(head);
//第二次遍历size-index位置,
//先做一个index的校验
if(index<=0||index>size){
return null;
}
//定义一个辅助变量
HeroNode cur=head.next;
for(int i=0;istack=new Stack();
HeroNode cur=head.next;
//将链表的所有节点压入栈
while(cur!=null){
stack.push(cur);
cur=cur.next;//让cur后移,这样就可以压入下一个节点
}
//将栈中的节点进行打印,pop出栈
while(stack.size()>0){
System.out.println(stack.pop());//先进后出
}
}
}
//定义SinglelinkedList
class SinglelinkedList {
//先初始化一个头节点,头节点不要动
private HeroNode head = new HeroNode(0, "", "");
public HeroNode getHead() {
return head;
}
//添加节点到单向链表
//思路,当不考虑编号的顺序时
//1、找到当前链表的最后节点
//2、将最后这个节点的next指向新的节点
public void add(HeroNode heroNode) {
//因为head节点不能动,因此需要一个辅助遍历temp
HeroNode temp = head;
//遍历链表,找到最后
while (temp.next != null) {
//找到链表的最后
//如果没有找到最后,将temp后移
temp = temp.next;
}
//当退出while循环时,temp就指向了链表的最后
temp.next = heroNode;
}
//第二种添加方式,根据排名插入到指定位置
//如果有这个排名,则添加失败,并给出提示
public void addByOrder(HeroNode heroNode) {
//因为头节点不能动,因此需要通过一个辅助指针(变量)来帮助找到添加的位置
//因为单链表,因此找的temp是位于添加位置的前一个节点,否则插入不了
HeroNode temp = head;
boolean flag = false;//标志添加的标号是否存在,默认为false
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.no > heroNode.no) {
break;
} else if (temp.next.no == heroNode.no) {
flag = true;//说明标号存在
break;
}
temp = temp.next;//后移,遍历当前链表
}
//判断flag的值
if (flag) {
System.out.println("准备插入的编号已存在,不能加入:" + heroNode.no);
} else {
//插入到链表中,temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
public void update(HeroNode newHeroNode) {
//判断是否为空
if (head.next == null) {
System.out.println("链表为空");
}
//找到需要修改的节点,根据no编号
//定义一个辅助变量
HeroNode temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
break;//已经完成遍历链表
}
if (temp.no == newHeroNode.no) {
//找到
flag = true;
break;
}
temp = temp.next;
}
//根据flag判断是否找到需要修改的节点
if (flag) {
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
} else {//没有找到
System.out.println("没有找到编号为" + newHeroNode.no + "的节点");
}
}
//单链表删除一个节点
public void del(int no) {
if (head.next == null) {
System.out.println("链表为空");
return;
}
HeroNode temp=head;
boolean flag = false;
while(true){
if(temp.next==null)break;
if(temp.next.no==no){
flag=true;
break;
}
temp=temp.next;
}
if(flag){//找到
temp.next=temp.next.next;
}else{
System.out.println("要删除的"+no+"不存在");
}
}
//显示链表
public void list() {
//判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
//因为头节点,不能动,因此需要一个辅助变量来遍历
HeroNode temp = head.next;
while (temp != null) {
//判断是否到链表最后
//输出节点的信息
System.out.println(temp);
//将temp后移
temp = temp.next;
}
}
public int getLength(){
int len=0;
HeroNode temp=head.next;
while(temp!=null){
len++;
temp=temp.next;
}
return len;
}
}
class HeroNode {
public int no;
public String name;
public String nickname;
public HeroNode next;
public HeroNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + ''' +
", nickname='" + nickname + ''' +
'}';
}
}
双向链表
单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找
单向链表不能自我删除,需要辅助节点,而双向链表,则可以自我删除
package linkeList;
public class DoublelinkedListDemo {
public static void main(String[] args) {
HeroNode2 hero1 = new HeroNode2(1, "a", "a");
HeroNode2 hero2 = new HeroNode2(2, "b", "b");
HeroNode2 hero3 = new HeroNode2(3, "c", "c");
HeroNode2 hero4 = new HeroNode2(4, "d", "d");
DoublelinkedList doublelinkedList=new DoublelinkedList();
doublelinkedList.add(hero1);
doublelinkedList.add(hero2);
doublelinkedList.add(hero3);
doublelinkedList.add(hero4);
doublelinkedList.del(2);
doublelinkedList.update(new HeroNode2(3,"c","c1"));
doublelinkedList.list();
}
}
class DoublelinkedList {
private HeroNode2 head = new HeroNode2(0, "", "");
public HeroNode2 getHead() {
return head;
}
//对于双向链表,可以直接找到要删除的节点
//找到后,自我删除,即可
public void del(int no) {
if (head.next == null) {
System.out.println("链表为空");
return;
}
HeroNode2 temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) break;//已经找到链表的最后的next
if (temp.no == no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {//找到
temp.pre.next = temp.next;
if (temp.next != null) temp.next.pre = temp.pre;
} else {
System.out.println("要删除的" + no + "不存在");
}
}
public void update(HeroNode2 newHeroNode) {
//判断是否为空
if (head.next == null) {
System.out.println("链表为空");
}
//找到需要修改的节点,根据no编号
//定义一个辅助变量
HeroNode2 temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
break;//已经完成遍历链表
}
if (temp.no == newHeroNode.no) {
//找到
flag = true;
break;
}
temp = temp.next;
}
//根据flag判断是否找到需要修改的节点
if (flag) {
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
} else {//没有找到
System.out.println("没有找到编号为" + newHeroNode.no + "的节点");
}
}
public void add(HeroNode2 heroNode) {
//因为head节点不能动,因此需要一个辅助遍历temp
HeroNode2 temp = head;
//遍历链表,找到最后
while (temp.next != null) {
//找到链表的最后
//如果没有找到最后,将temp后移
temp = temp.next;
}
//当退出while循环时,temp就指向了链表的最后
temp.next = heroNode;
heroNode.pre = temp;
}
public void list() {
//判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
//因为头节点,不能动,因此需要一个辅助变量来遍历
HeroNode2 temp = head.next;
while (temp != null) {
//判断是否到链表最后
//输出节点的信息
System.out.println(temp);
//将temp后移
temp = temp.next;
}
}
}
class HeroNode2 {
public int no;
public String name;
public String nickname;
public HeroNode2 next;//指向下一个节点,默认为null
public HeroNode2 pre;//指向前一个节点,默认为null
public HeroNode2(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
@Override
public String toString() {
return "HeroNode2{" +
"no=" + no +
", name='" + name + ''' +
", nickname='" + nickname + ''' +
'}';
}
}
单向环形链表
约瑟夫环问题
设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
package linkeList;
public class Josepfu {
public static void main(String[] args) {
CircleSinglelinkedList circleSinglelinkedList = new CircleSinglelinkedList();
circleSinglelinkedList.addBoy(5);
circleSinglelinkedList.countBoy(1,2,5);
}
}
//创建一个环形的单向链表
class CircleSinglelinkedList {
//创建一个first节点,当前没有编号
private Boy first = new Boy(-1);
//添加节点,构建成一个环形链表
public void addBoy(int nums) {
if (nums < 1) {
System.out.println("nums的值不正确");
return;
}
Boy curBoy = first;//辅助指针,帮助构建环形链表
//使用for来创建环形链表
for (int i = 1; i <= nums; i++) {
//根据编号创建节点
Boy boy = new Boy(i);
//如果是第一个小孩
curBoy.setNext(boy);
curBoy = curBoy.getNext();
// if (i == 1) {
// first = boy;
// first.setNext(first);
// curBoy = first;
// } else {
// curBoy.setNext(boy);
// boy.setNext(first);
// curBoy = boy;
// }
}
curBoy.setNext(first.getNext());
first = first.getNext();
}
//遍历当前环行链表
public void showBoy() {
//判断链表是否为空
if (first == null) {
System.out.println("链表为空");
return;
}
Boy curBoy = first;
while (true) {
System.out.println("编号:" + curBoy);
curBoy = curBoy.getNext();
if (curBoy == first) {
break;
}
}
}
//根据用户的输入,计算小孩出圈的顺序
public void countBoy(int startNo, int countNum, int nums) {
if (first == null || startNo < 1 || startNo > nums) {
System.out.println("参数输入有误,请重新输入");
return;
}
//创建辅助指针,先将指针指向环型链表的最后这个节点
Boy helper = first;
while (true) {
if (helper.getNext() == first) {
break;
}
helper = helper.getNext();
}
for (int i = 0; i < startNo - 1; i++) {
first = first.getNext();
helper = helper.getNext();
}
while(true){
if(helper==first){//圈中只有一个节点
break;
}
//
for (int i = 0; i
栈
栈的应用
1)子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
2)处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
3)表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。
4)二叉树的遍历。
5)图形的深度优先(depth一first)搜索法。
使用数组模拟栈
package stack;
import java.util.Scanner;
public class ArrayStackDemo {
public static void main(String[] args) {
ArrayStack stack = new ArrayStack(4);
String key = "";
boolean loop = true;
Scanner scan = new Scanner(System.in);
while (loop) {
System.out.println("show:表示显示栈");
System.out.println("exit:退出程序");
System.out.println("push:添加程序到栈");
System.out.println("pop:表示从栈取出数据(出栈)");
key = scan.next();
switch (key) {
case "show":
stack.list();
break;
case "push":
System.out.println("请输入一个数");
int value = scan.nextInt();
stack.push(value);
break;
case "pop":
try {
int res = stack.pop();
System.out.println("出栈的数据是" + res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case "exit":
scan.close();
loop = false;
default:
break;
}
}
}
}
//表示栈结构
class ArrayStack {
public int maxSize;//栈的大小
public int[] stack;//数组,数组模拟栈,数据就放在该数组
public int top = -1;//top表示栈,初始为-1
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
//栈满
public boolean isFull() {
return top == maxSize - 1;
}
//栈空
public boolean isEmpty() {
return top == -1;
}
//入栈push
public void push(int value) {
//先判断栈是否满
if (isFull()) {
System.out.println("栈满");
return;
}
top++;
stack[top] = value;
}
//出栈-pop,将栈顶的数据返回
public int pop() {
//先判断栈是否为空
if (isEmpty()) {
//抛出异常
throw new RuntimeException("栈空,没有数据");
}
int value = stack[top];
top--;
return value;
}
//显示栈的情况[遍历栈].遍历时,需要从栈顶开始显示数据
public void list() {
if (isEmpty()) {
System.out.println("栈空,没有数据");
}
for (int i = top; i >= 0; i--) {
System.out.println("stack[" + i + "]" + "=" + stack[i]);
}
}
}
计算器(中缀表达式)
使用栈完成表达式的计算 思路
-
通过一个 index 值(索引),来遍历我们的表达式
-
如果我们发现是一个数字, 就直接入数栈
-
如果发现扫描到是一个符号, 就分如下情况
3.1 如果发现当前的符号栈为空,就直接入栈
3.2 如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符, 就需要从数栈中pop出两个数,在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈, 如果当前的操作符的优先大于栈中的操作符, 就直接入符号栈.
4. 当表达式扫描完毕,就顺序的从 数栈和符号栈中pop出相应的数和符号,并运行.
5. 最后在数栈只有一个数字,就是表达式的结果
package stack;
public class Calculator {
public static void main(String[] args) {
String expression = "43+2*6-2";
//创建两个栈,数栈和符号栈
ArrayStack2 numStack = new ArrayStack2(10);
ArrayStack2 operStack = new ArrayStack2(10);
//定义需要的相关变量
int index = 0;//用于扫描
int num1 = 0;
int num2 = 0;
int oper = 0;
int res = 0;
char ch = ' ';//将每次扫描得到char保存到ch
String keepNum = "";//用于拼接多位数
//开始while循环的扫描expression
while (true) {
//依次得到expression的每一个字符
ch = expression.substring(index, index + 1).charAt(0);
//判断ch是什么,然后相应的处理
if (operStack.isOper(ch)) {//如果是运算符
//判断当前的符号栈是否为空
if (!operStack.isEmpty()) {
//如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或等于栈中的操作符,就需要从数栈中pop出两个数
//在从符号中pop出一个符号,将得到结果,入数栈,然后将当前的操作符入符号栈
if (operStack.priority(ch) <= operStack.priority(operStack.peek())) {
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(num1, num2, oper);
//把运算的结果入数栈
numStack.push(res);
//然后将当前的操作符入符号栈
operStack.push(ch);
} else {
//如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈
operStack.push(ch);
}
} else {
//如果为空直接入栈
operStack.push(ch);
}
} else {
//如果是数,则直接入数栈
// numStack.push(ch-48);
//1、当处理多位数时,不能发现是一个数据立即入栈,因为可能是多位数
//2、当处理数,需要向expression的表达式的index后再看一位,如果时数就进行扫描,如果有符号就入栈
//3、需要定义一个变量字符串,用于拼接
//处理多位数
keepNum += ch;
//判断下一个字符是不是数字,如果时数字,就继续扫描,如果是运算符,则入栈
if (index == expression.length() - 1) {
numStack.push(Integer.parseInt(keepNum));
} else {
if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
//如果后一位是运算符,则入栈
numStack.push(Integer.parseInt(keepNum));
//
keepNum = "";
}
}
}
//让index+1,并判断是否扫描到expression最后
index++;
if (index >= expression.length()) {
break;
}
}
//当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行
while (true) {
//如果符号为空,则计算到最后的结果,数栈中只有一个数字结果
if (operStack.isEmpty()) {
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(num1, num2, oper);
numStack.push(res);
}
System.out.println(expression + "=" + numStack.pop());
}
}
class ArrayStack2 extends ArrayStack {
public ArrayStack2(int maxSize) {
super(maxSize);
}
//返回当前栈顶的值,但是不是真正的pop
public int peek() {
return stack[top];
}
//返回运算符的优先级,优先级是程序员来确定,优先级使用数字来表示
//数字越大,则优先级越高
public int priority(int oper) {
if (oper == '*' || oper == '/') {
return 1;
} else if (oper == '+' || oper == '_') {
return 0;
} else {
return -1;//假定目前的表达式只有+、-、*、/
}
}
//判断是不是一个运算符
public boolean isOper(char val) {
return val == '+' || val == '-' || val == '*' || val == '/';
}
//计算方法
public int cal(int num1, int num2, int oper) {
int res = 0;//res 用于存放计算的结果
switch (oper) {
case '+':
res = num1 + num2;
break;
case '-':
res = num2 - num1;//注意顺序
break;
case '*':
res = num1 * num2;
break;
case '/':
res = num2 / num1;
break;
default:
break;
}
return res;
}
}



