测试环境如下:JAVA版本是11,IDE是IDEA2019.3.1的社区版本,操作系统是win10专业版。
程序要处理的是一个简单的算法课作业:
两重循环,迭代变量i,j每执行一次循环体则自增1,取值范围为0<=i<N以及0<=j<N,循环体内容任意,比如s=s+i-j;请记录下运行时间,输出当两重循环运行时间是0.1s, 1s, 10s时的N.
题目要求很简单,思路是每次都记录下循环时间,用二分法让程序自己找出来这3个N。
以下是源码:
一个小工具类,用来输出结果到文本:
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class OutData
{
public OutData(Integer n1,//最接近0.1s的N
Integer n2,//最接近1s的N
Integer n3,//最接近10s的N
StringBuffer useTime1,//n1的具体时间
StringBuffer useTime2,//n2的具体时间
StringBuffer useTime3//n3的具体时间
) throws IOException
{
File fileOfDataIn = new File("Result.txt");
FileWriter fileWriter;
if (!fileOfDataIn.exists())
{
fileOfDataIn.createNewFile();
fileWriter = new FileWriter(fileOfDataIn.getName(), true);
} else
{
fileWriter = new FileWriter(fileOfDataIn.getName(), false);
fileWriter.write("");
}
fileWriter.write("N为:" + n1.toString() + "时,所用时间为:" + useTime1.toString() + "毫秒"
+"\n"+"N为:" + n2.toString() + "时,所用时间为:" + useTime2.toString() + "毫秒"
+"\n"+"N为:" + n3.toString() + "时,所用时间为:" + useTime3.toString() + "毫秒");
fileWriter.close();
}
}
接下来是main类,为了节省运行时间,求1s和10s的两次先注释掉:
import java.io.IOException;
//后来我的解决办法是再加一个随机函数到循环体里面,这里先注释掉
//import java.util.Random;
public class DoubleCirculationMain
{
public static void main(String[] args) throws IOException
{
int startN = 1000;
StringBuffer useTime1 = new StringBuffer(),
useTime2 = new StringBuffer(),
useTime3 = new StringBuffer();
//使用二分法自动找到用时分别接近0.1s、1s和10s的N值并输出
int n1 = getClosestValue(100, startN, useTime1);
int n2=0;//节省测试时间
//int n2 = getClosestValue(1000, n1, useTime2);
int n3=0;//节省测试时间
//int n3 = getClosestValue(10000, n2, useTime3);
new OutData(n1, n2, n3, useTime1, useTime2, useTime3);
}
private static int getClosestValue(int timeToApproach, int startN, StringBuffer useTimeBuffer)
{
int nLower = startN,
increment = 10000,
nHeigher = nLower + increment,
nMedian = (nHeigher + nLower) / 2,
forCaculate = 0;
long startTime, endTime, costime = 0, costime2 = 0;
//后来我的解决办法是再加一个随机函数到循环体里面,这里先注释掉
//Random random = new Random();
while (true)
{//循环至目标处于上下限区间内
nMedian = (nHeigher + nLower) / 2;//刷新中位数
forCaculate = 0;
startTime = System.currentTimeMillis();//开始计时
for (int i1 = 0; i1 < nHeigher; i1++)
{
for (int i2 = 0; i2 < nHeigher; i2++)
{
forCaculate = forCaculate + i1 - i2;
//后来我的解决办法是再加一个随机函数到循环体里面,这里先注释掉
//forCaculate = forCaculate + i1 - i2+random.nextInt(100);
}
}
endTime = System.currentTimeMillis();//结束计时
//一开始就防着JAVA虚拟机自动优化代码,所以故意调用次运行结果
System.out.println(forCaculate);
costime = endTime - startTime;
if (costime < timeToApproach)
{//如果上限小于目标,则下限取该值,然后上限增大。
nHeigher += increment;
nLower = nHeigher;
} else
{//如果上限大于目标,则退出循环,花费时间值是上限时间
break;
}
}
while (costime >= 1.01 * timeToApproach && nHeigher - nLower > 1)
{//当花费时间超出1.01倍目标且上下限差距大于1时,继续逼近
nMedian = (nHeigher + nLower) / 2;//刷新中位数。
startTime = System.currentTimeMillis();//开始计时
for (int i1 = 0; i1 < nMedian; i1++)
{
for (int i2 = 0; i2 < nMedian; i2++)
{
forCaculate = forCaculate + i1 - i2;
//后来我的解决办法是再加一个随机函数到循环体里面,这里先注释掉
//forCaculate = forCaculate + i1 - i2+random.nextInt(100);
}
}
endTime = System.currentTimeMillis();//结束计时
//还是为了防着JAVA虚拟机自动优化代码,所以故意调用次运行结果
System.out.println(forCaculate);
costime2 = endTime - startTime;
if (costime2 > timeToApproach)
{//如果中位数也大于目标,则中位数变为新上限,上限花费时间更新
nHeigher = nMedian;
costime = costime2;
} else
{//如果中位数小于目标,则中位数变成新下限
nLower = nMedian;
}
}
useTimeBuffer.append(costime);//输出逼近处理后的上限时间
return nHeigher;
}
}
run输出的文本内容是:
在getClosestValue()方法中随意加个断点debug之后输出的文本内容是:
首先,我已经反复确认几遍了,这个问题确实存在,rebuild也好,完全重启IDEA也好,重启windows也好,run和debug的结果就是不一样。
其次,如果把断点加在main方法里,则debug的运行结果会和run是一样的,但是如果随意在二分法方法getClosestValue()中加断点就会出现debug的运行结果和run不同的问题,debug的结果是理论上应该得到的结果。
发现这个问题之后,直觉让我在循环体里加入一个随机过程,因为我怀疑在解释运行时,编译器判断出了两个重复的代码段,于是就直接调用了运行结果以优化程序的运行效率,或许这就是JAVA在执行重复调用的代码段时效率可以追上C++的秘诀吧。不过如果真的是这样的话,显然这忽略了我在程序运行时间上的验证性要求,最后导致了输出结果和理论不一样,所以我一往循环体里加入随机过程之后,debug和run的结果就统一的符合程序的理论结果了。
另一个值得一提的奇怪现象是,在我每次测试完这段代码之后,如果此时关闭idea,则下次打开idea一定会在开始的时候卡住,但是重启windows之后idea又可以正常打开,所以这个问题可能和idea有关?JAVA IDE我一直用的是IntelliJ IDEA,虽然在刚开始学习JAVA的时候我就遇到过一次,不过当时没有注意把问题记录下来,和老师说了之后也只能不了了之。这次我又遇到了这样的问题,不得不说还是很吓人的,毕竟如果连debug工具都不能相信了那我们又该相信谁。
作为一个小白,目前不太想过于深究,因为问题可能出现的地方太多了,而我现在也整不明白,所以姑且先记在这里,如果有路过的朋友对这个问题有兴趣,可以在评论区说说自己的看法,或者可以自己试一下。