我有种感觉,我不知道swing Timer是如何工作的。我对JavaGUI API还是新手,我编写的程序只是为了测试自己,帮助我更熟悉它的内部工作。
它应该做的是等待用户按下开始按钮,然后迭代显示(白色或黑色JPanel网格),以1秒的间隔显示一个简单的细胞自动机模拟,并在按下暂停按钮时暂停(与开始按钮相同,但更改名称)。网格中的每个单元格都应该以随机颜色(白色/黑色)开始。它所做的是暂停半秒左右,然后再“跑”半秒,然后暂停,然后跑,等等。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CA_Driver extends JFrame{
private JPanel gridPanel, buttonPanel;
private JButton start_pause, pause;
private static Timer timer;
private Color black = Color.black;
private Color white = Color.white;
static Color[][] currentGrid, newGrid;
static Cell[][] cellGrid;
static boolean run, stop;
static int height = 20, width = 30, state;
public CA_Driver(){
stop = false;
run = false;
currentGrid = new Color[height][width];
newGrid = new Color[height][width];
cellGrid = new Cell[height][width];
//Initialize grid values
for (int x = 0; x < currentGrid.length; x++)
for (int y = 0; y < currentGrid[x].length; y++){
int z = (int) (Math.random() * 2);
if (z == 0)
currentGrid[x][y] = newGrid[x][y] = white;
else currentGrid[x][y] = newGrid[x][y] = black;
}
//Create grid panel
gridPanel = new JPanel();
gridPanel.setLayout(new GridLayout(height,width));
//Populate grid
for (int x = 0; x < newGrid.length; x++)
for (int y = 0; y < newGrid[x].length; y++){
cellGrid[x][y] = new Cell(x,y);
cellGrid[x][y].setBackground(newGrid[x][y]);
int z = (int) Math.random();
if (z == 0) cellGrid[x][y].setBackground(black);
else cellGrid[x][y].setBackground(currentGrid[x][y]);
gridPanel.add(cellGrid[x][y]);
}
//Create buttons
state = 0;
start_pause = new JButton();
start_pause.setText("Start");
start_pause.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
if (state == 0) {
start_pause.setText("Pause");
run = true;
timer.start();
state += 1;
}
else {
start_pause.setText("Start");
run = false;
timer.stop();
state -= 1;
}
}
});
buttonPanel = new JPanel(new BorderLayout());
buttonPanel.add(start_pause, BorderLayout.NORTH);
// buttonPanel.add(pause, BorderLayout.EAST);
//Initialize and display frame
this.add(gridPanel, BorderLayout.NORTH);
this.add(buttonPanel, BorderLayout.SOUTH);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
//this.setSize(500, 500);
pack();
this.setVisible(true);
//Initialize timer
timer = new Timer(1000, new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
for (int x = 0; x < cellGrid.length; x++)
for (int y = 0; y < cellGrid[x].length; y++){
cellGrid[x][y].setColor();
currentGrid[x][y] = newGrid[x][y];
}
//Display processing for next frame
for (int x = 0; x < currentGrid.length; x++)
for (int y = 0; y < currentGrid[x].length; y++){
int b = checkNeighbors(y,x);
if (b > 4 || b < 2)
newGrid[x][y] = black;
else newGrid[x][y] = white;
}
if(!run) timer.stop();
}
});
}
public static void main(String[] args) {
new CA_Driver();
}
private int checkNeighbors(int w, int h){
int b = 0;
//Top Left
if((w != 0) && (h != 0) && (currentGrid[h - 1][w - 1] == black))
b++;
//Top Middle
if((h != 0) && (currentGrid[h - 1][w] == black))
b++;
//Top Right
if((w != width - 1) && (h != 0) && (currentGrid[h - 1][w + 1] == black))
b++;
//Middle Left
if((w != 0) && (currentGrid[h][w - 1] == black))
b++;
//Middle Right
if((w != width - 1) && (currentGrid[h][w + 1] == black))
b++;
//Bottom left
if((w != 0) && (h != height - 1) && (currentGrid[h + 1][w - 1] == black))
b++;
//Bottom Middle
if((h != height - 1) && (currentGrid[h + 1][w] == black))
b++;
//Bottom Right
if((w != width - 1) && (h != height - 1) && (currentGrid[h + 1][w + 1] == black))
b++;
return b;
}
private class Cell extends JPanel{
private Color c;
private int posx, posy;
public Cell(int x, int y){
posx = x;
posy = y;
}
public Point getLocation(){
return new Point(posx, posy);
}
public void setColor(){
c = newGrid[posx][posy];
setBackground(c);
}
public Dimension getPreferredSize(){
return new Dimension(10,10);
}
}
}
这是计时器部分:
timer = new Timer(1000, new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
for (int x = 0; x < cellGrid.length; x++)
for (int y = 0; y < cellGrid[x].length; y++){
cellGrid[x][y].setColor();
currentGrid[x][y] = newGrid[x][y];
}
//Display processing for next frame
for (int x = 0; x < currentGrid.length; x++)
for (int y = 0; y < currentGrid[x].length; y++){
int b = checkNeighbors(y,x);
if (b > 4 || b < 2)
newGrid[x][y] = black;
else newGrid[x][y] = white;
}
if(!run) timer.stop();
}
});
我计划稍后添加更多功能,让用户更好地控制各种变量,如网格大小和迭代速度,但我想让显示的核心功能正常工作。我相当确定问题在于我如何使用计时器类,因为它是时间问题。
我的第一个问题是:我是否正确使用了Timer类?如果是,那么问题是什么?如果没有,我应该如何使用它?
更新这是个好主意,麦德程序员,很高兴知道我正确使用了定时器。我意识到它“运行”的部分实际上是每个单元格更新颜色需要多长时间,所以实际上我的程序就像现在一样慢得离谱,效率低下。
这是我提高速度和效率的想法。主要是,我会使用计时器延迟来处理下一次迭代的输出,然后下一次计时器“触发”时,我会改变一个“滴答”变量,每个单元格都会用它作为改变颜色的信号,就像建议的那样。为了实现这一点,我给每个单元格添加了一个计时器(这是一个多好/多坏的想法?),可以消磨一点时间,然后,在一个阻塞的同时循环中,等着看内部“滴答”是否等同于全局“滴答”,并在发生这种情况时立即改变颜色。
最终的结果是,它一开始就会冻结。
这是我添加到cell类构造函数中的计时器:
c_timer = new Timer(500, new ActionListener(){
public void actionPerformed(ActionEvent e){
c_timer.stop();
while (c_tick != tick);
setBackground(currentGrid[posx][posy]);
c_tick = 1 - c_tick;
if(run) timer.restart();
}
});
c_timer.start();
这就是我修改全局计时器的方式:
timer = new Timer(1000, new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
currentGrid[y][x] = newGrid[y][x];
tick = 1 - tick;
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++){
if (b[y][x] > 6 || b[y][x] < 1) newGrid[y][x] = white;
else newGrid[y][x] = black;
}
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
b[y][x] = checkNeighbors(x,y);
if(!run) timer.stop();
}
});
除了这些更改,我删除了Cell类中的setColor()
方法。有人能指出我犯的错误吗?
更新2
我应该早点更新,但简单地说,我发现这样做完全是错误的。与其让面板充满组件并更改其背景,不如用网格绘制面板:
@Override
public void paintComponent(Graphics g){
super.paintComponent(g);
for (int h = 0; h < board_size.height; h++){
for (int w = 0; w < board_size.width; w++){
try{
if (grid[h][w] == BLACK)
g.setColor(BLACK);
else g.setColor(WHITE);
g.fillRect(h * cell_size, w * cell_size, cell_size, cell_size);
} catch (ConcurrentModificationException cme){}
}
}
}
在每个计时器“勾号”上,您首先重新绘制网格,然后处理下一次迭代,以便在下一个勾号上绘制。效率更高,并且可以立即更新。
My我使用了一个经过修改的JPanel作为主要的网格组件,它实现了一个ActionListener来处理用户在gui的其余部分上执行的每个操作以及每个计时器滴答声:
public void actionPerformed(ActionEvent e) {
//Timer tick processing: count surrounding black cells, define next iteration
//using current rule set, update master grid
if (e.getSource().equals(timer)){
//Processing for each tick
}
else if(e.getSource()...
//Process events dispached by other components in gui
}
当然,您必须将主板面板设置为计时器的动作监听器。
我计划稍后添加更多功能,让用户更好地控制各种变量,如网格大小和迭代速度,但我想让显示的核心功能正常工作。我相当确定问题在于我如何使用计时器类,因为它是时间问题。
这是一个很好的策略,程序运行良好,但它可以更高效、更可扩展。
例如,我建议使用自定义的SwingWorker
类来执行计算,然后将消息发送回UI。
下面是一个如何在SwingWorker
中创建它的示例。以下是Oracle资源站点提供的附加信息:http://docs.oracle.com/javase/tutorial/uiswing/concurrency/simple.html
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
public class CA_Driver extends JFrame
{
private JPanel gridPanel, buttonPanel;
private JButton start_pause, pause;
// private static Timer timer;
private Color black = Color.black;
private Color white = Color.white;
static Color[][] currentGrid, newGrid;
static Cell[][] cellGrid;
static boolean stop;
static int height = 20, width = 30, state;
boolean run;
private synchronized boolean getRun()
{
return run;
}
private synchronized void setRun(boolean run)
{
this.run = run;
}
/**
* http://docs.oracle.com/javase/tutorial/uiswing/concurrency/simple.html
*
*/
SwingWorker worker = createNewWorker();
private SwingWorker createNewWorker()
{
return
new SwingWorker<Void, Void>()
{
protected Void doInBackground() throws Exception
{
while(getRun())
{
for (int x = 0; x < cellGrid.length; x++)
{
for (int y = 0; y < cellGrid[x].length; y++)
{
cellGrid[x][y].setColor();
currentGrid[x][y] = newGrid[x][y];
}
}
//Display processing for next frame
for (int x = 0; x < currentGrid.length; x++)
{
for (int y = 0; y < currentGrid[x].length; y++)
{
int b = checkNeighbors(y,x);
if (b > 4 || b < 2)
{
newGrid[x][y] = black;
}
else
{
newGrid[x][y] = white;
}
}
}
try
{
Thread.sleep(1000);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
return null;
}
@Override
protected void done()
{
super.done();
}
};
}
public CA_Driver()
{
stop = false;
setRun(false);
currentGrid = new Color[height][width];
newGrid = new Color[height][width];
cellGrid = new Cell[height][width];
//Initialize grid values
for(int x = 0 ; x < currentGrid.length ; x++)
for(int y = 0 ; y < currentGrid[x].length ; y++)
{
int z = (int) (Math.random() * 2);
if(z == 0)
currentGrid[x][y] = newGrid[x][y] = white;
else
currentGrid[x][y] = newGrid[x][y] = black;
}
//Create grid panel
gridPanel = new JPanel();
gridPanel.setLayout(new GridLayout(height, width));
//Populate grid
for(int x = 0 ; x < newGrid.length ; x++)
for(int y = 0 ; y < newGrid[x].length ; y++)
{
cellGrid[x][y] = new Cell(x, y);
cellGrid[x][y].setBackground(newGrid[x][y]);
int z = (int) Math.random();
if(z == 0)
cellGrid[x][y].setBackground(black);
else
cellGrid[x][y].setBackground(currentGrid[x][y]);
gridPanel.add(cellGrid[x][y]);
}
//Create buttons
state = 0;
start_pause = new JButton();
start_pause.setText("Start");
start_pause.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent arg0)
{
if(state == 0)
{
start_pause.setText("Pause");
setRun(true);
worker = createNewWorker();
worker.execute();
// timer.start();
state += 1;
}
else
{
start_pause.setText("Start");
setRun(false);
// timer.stop();
state -= 1;
}
}
});
buttonPanel = new JPanel(new BorderLayout());
buttonPanel.add(start_pause, BorderLayout.NORTH);
// buttonPanel.add(pause, BorderLayout.EAST);
//Initialize and display frame
this.add(gridPanel, BorderLayout.NORTH);
this.add(buttonPanel, BorderLayout.SOUTH);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
//this.setSize(500, 500);
pack();
this.setVisible(true);
worker.execute();
/*
//Initialize timer
timer = new Timer(1000, new ActionListener()
{
public void actionPerformed(ActionEvent arg0)
{
for(int x = 0 ; x < cellGrid.length ; x++)
for(int y = 0 ; y < cellGrid[x].length ; y++)
{
cellGrid[x][y].setColor();
currentGrid[x][y] = newGrid[x][y];
}
//Display processing for next frame
for(int x = 0 ; x < currentGrid.length ; x++)
for(int y = 0 ; y < currentGrid[x].length ; y++)
{
int b = checkNeighbors(y, x);
if(b > 4 || b < 2)
newGrid[x][y] = black;
else
newGrid[x][y] = white;
}
if(!getRun())
timer.stop();
}
});
*/
}
public static void main(String[] args)
{
new CA_Driver();
}
private int checkNeighbors(int w, int h)
{
int b = 0;
//Top Left
if((w != 0) && (h != 0) && (currentGrid[h - 1][w - 1] == black))
b++;
//Top Middle
if((h != 0) && (currentGrid[h - 1][w] == black))
b++;
//Top Right
if((w != width - 1) && (h != 0) && (currentGrid[h - 1][w + 1] == black))
b++;
//Middle Left
if((w != 0) && (currentGrid[h][w - 1] == black))
b++;
//Middle Right
if((w != width - 1) && (currentGrid[h][w + 1] == black))
b++;
//Bottom left
if((w != 0) && (h != height - 1) && (currentGrid[h + 1][w - 1] == black))
b++;
//Bottom Middle
if((h != height - 1) && (currentGrid[h + 1][w] == black))
b++;
//Bottom Right
if((w != width - 1) && (h != height - 1) &&
(currentGrid[h + 1][w + 1] == black))
b++;
return b;
}
private class Cell extends JPanel
{
private Color c;
private int posx, posy;
public Cell(int x, int y)
{
posx = x;
posy = y;
}
public Point getLocation()
{
return new Point(posx, posy);
}
public void setColor()
{
c = newGrid[posx][posy];
setBackground(c);
}
public Dimension getPreferredSize()
{
return new Dimension(10, 10);
}
}
}
这是一个生活游戏,以传统的Java摇摆方式每半秒钟更新一次屏幕。
添加用于设置网格大小和更新率的控件,以及一个编辑模式,在该模式下动画停止,可以用鼠标设置单元格,这将非常简单。要更改更新速率,请调用lifePane。运行(newUpdateInterval)
或lifePane。运行(0)
暂停。调用lifePane。设置大小(宽度、高度)
以更改网格。
使用一个单独的线程进行生成计算的主要价值(正如所建议的,但我在这里没有做)是,动画将在您操作GUI时继续。例如,如果使用滑块来控制速度,动画将不会暂停,而是在UI线程中计算生成时暂停。
除了grins,我添加了控件,并使用了java。乌提尔斯。计时器
而不是Swing timer,以获得本要点中渲染的额外线程效果。
但是,如果您不介意在操作“鼠标按下”GUI项目时暂停,单线程就可以了。我的旧笔记本电脑在Swing事件线程中以每秒20次更新的速度运行1000x1000代,GUI仍然运行良好。
方法update()
填充当前一代的下一代,然后交换缓冲区。paintComponent
的覆盖只绘制当前一代。通过这种组合,计时器只需执行更新
和重新绘制
。
其他可能对您有用的约定是处理窗口大小调整和组织邻居计算的方法。了解好的习惯用法有助于避免冗长的代码。
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
public class Life {
protected LifePane lifePane;
public static class LifePane extends JComponent {
private int rows, cols;
private byte[][] thisGen, nextGen;
private Timer timer;
public LifePane(int rows, int cols) {
setGenSize(rows, cols);
}
public final void setGenSize(int rows, int cols) {
this.rows = rows;
this.cols = cols;
thisGen = new byte[rows][cols];
nextGen = new byte[rows][cols];
Random gen = new Random();
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
thisGen[i][j] = toByte(gen.nextBoolean());
}
}
}
@Override
protected void paintComponent(Graphics g) {
int width = getWidth();
int height = getHeight();
// Clear the background.
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
// Set the 1-valued cells black.
g.setColor(Color.BLACK);
int y0 = 0;
for (int i = 1; i < rows; i++) {
int y1 = i * height / (rows - 1);
int x0 = 0;
for (int j = 1; j < cols; j++) {
int x1 = j * width / (cols - 1);
if (thisGen[i][j] != 0) {
g.fillRect(x0, y0, x1 - x0, y1 - y0);
}
x0 = x1;
}
y0 = y1;
}
}
/**
* Make the next generation current.
*/
private void swapGens() {
byte [][] tmp = thisGen;
thisGen = nextGen;
nextGen = tmp;
}
private static byte toByte(boolean booleanVal) {
return booleanVal ? (byte) 1 : (byte) 0;
}
// Implementation of Conway's Game of Life rules.
private void updateCell(int x0, int x, int x1, int y0, int y, int y1) {
int n = thisGen[y0][x0] + thisGen[y0][x] + thisGen[y0][x1] +
thisGen[y] [x0] + thisGen[y] [x1] +
thisGen[y1][x0] + thisGen[y1][x] + thisGen[y1][x1];
nextGen[y][x] =
(thisGen[y][x] == 0) ? toByte(n == 3) : toByte(n >> 1 == 1);
}
private void updateRow(int y0, int y, int y1) {
updateCell(cols - 1, 0, 1, y0, y, y1);
for (int j = 1; j < cols - 1; ++j) {
updateCell(j - 1, j, j + 1, y0, y, y1);
}
updateCell(cols - 2, cols - 1, 0, y0, y, y1);
}
// Update the grid as a toroid and swap buffers.
public void update() {
updateRow(rows - 1, 0, 1);
for (int i = 1; i < rows - 1; i++) {
updateRow(i - 1, i, i + 1);
}
updateRow(rows - 2, rows - 1, 0);
swapGens();
}
/**
* Run the life instance with given update interval.
*
* @param updateInterval interval in milliseconds, <= 0 to stop
* @return this
*/
public LifePane run(int updateInterval) {
if (timer != null) {
timer.stop();
timer = null;
}
if (updateInterval > 0) {
timer = new Timer(updateInterval, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
update();
repaint();
}
});
timer.start();
}
return this;
}
}
public void run(int width, int height, int updateInterval) {
JFrame frame = new JFrame("Life");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
lifePane = new LifePane(width, height).run(updateInterval);
frame.setContentPane(lifePane);
frame.setPreferredSize(new Dimension(1024, 800));
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Life().run(100, 100, 500);
}
});
}
}
你在问题的第一部分中使用的定时器
类看起来确实正确。一个java正在发生什么。摆动Timer
是在事件调度线程上以特定的间隔触发ActionListener
,该间隔由延迟参数指定。
这也意味着您放在ActionListener
中的代码应该快速执行。当您的ActionListener
代码正在执行时,用户界面不能更新,因为用户界面线程(事件调度线程)被占用执行ActionListener
代码。这在该类的javadoc中有明确的记录。
虽然所有计时器都使用一个共享线程(由第一个执行的计时器对象创建)执行等待,但计时器的操作事件处理程序在另一个线程上执行——事件调度线程。这意味着计时器的操作处理程序可以安全地在Swing组件上执行操作。然而,这也意味着处理程序必须快速执行以保持图形用户界面响应。
这正是您在第一次更新中遇到的情况
new Timer(500, new ActionListener(){
public void actionPerformed(ActionEvent e){
//...
while (c_tick != tick){}
//...
}
});
在这里使用while循环可以阻止事件分派线程。c_tick!=勾选
check将永远不会更改,因为相关变量仅在EDT上调整,并且您正在用循环阻塞它。
您的第二次更新似乎表明,通过从面板切换,现在一切正常。然而,有两件看起来很奇怪的事情:
>
ConcurrentModificationException
。记住Swing是单线程的。所有可能与Swing组件交互的操作都应该在EDT上执行,与多线程应用程序相比,遇到ConcurrentModificationException
的机会要小得多
你说
当然,您必须将board panel设置为计时器的动作侦听器
这似乎是不真实的。无论附加到计时器
的ActionListener
需要交换当前网格和下一个网格,并计算下一个网格。一旦计算了下一个网格,它需要安排网格面板的重新绘制。无论这个ActionListener
是一个匿名/内部/单独的类还是网格面板本身都无关紧要(至少在功能和设计方面,我永远不会选择让网格面板成为监听器)。
旁注:当您需要交换当前网格和新网格时,请使用以下代码
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
currentGrid[y][x] = newGrid[y][x];
}
}
如果仍然存在性能问题,可以尝试使用系统。arrayCopy
可能比手动在阵列上循环快得多。
我在Swing和设置角色动画方面有一些问题,我有一个带有关键侦听器的JFrame,当用户点击时,它在这里调用我的JPanel方法 这个动画我的角色,但这么快,我们可以看到一个东西,我怎么才能看到动画?
我在刷新gridlayout的值时遇到问题。 因此,我在JFrame中有一个JPanel,在这个JPanel中,一旦我输入了两个值(一个用于行,一个用于列),然后单击validate,我就得到了一个包含JButtons之前值的GridLayout。例如,如果我输入(2,2),我会得到一个包含4个按钮的GridLayout,每个按钮中都有一个图像。 所以我的问题是,每次我想通过改变值来刷新GridL
问题内容: 我在游戏中使用了摇摆计时器,但是当游戏运行时,它似乎有平稳运行的时刻和减速的时刻。 为什么时间在波动? 我该如何解决? 这是我的代码示例。在我的实际程序中,我正在绘制图像,而不仅仅是矩形。还有很多碰撞检测和其他小的计算正在发生。 另外,这是游戏的Jar文件的链接,因此您可以运行它,(满是)明白我的意思。http://dl.dropbox.com/u/8724803/Get%20To%2
我觉得摇摆计时器有问题。我写了一些运行良好的代码,然后将其全部移动到我的新计算机上,但它很快就无法工作。我用这个方法编写了一个GUI类(基于JFrame): 该方法是从另一个类调用的。真是太夸张了。splash guy是一个只运行splash动画的JPanel类。它通过摆动计时器来实现。在splash类中,侦听器通过以下代码调整我的帧的内容: 现在,如果第一个块中的print语句被注释掉,我的程序
我正在用摇摆计时器打卡NetBeans: 我只是用System.out.print来测试程序,它不是真正程序的一部分。我调用停止()方法,但计时器继续计数。此外,我通过但它的计数速度是原来的两倍。有人能帮忙吗? 编辑: 这是我的计时器(有点像SSCCE): 它不能正常工作,因为秒没有出现,但它确实显示了20次,这就是我想要的。这只是在它自己的应用程序中,在我的实际程序中更容易看到问题。 我注意到游
我首先想到的是:我认为有点非常准确,给出了一个值,其中as请求一个参数;上面写着什么?不要认为<代码>摆动定时器<代码>是准确的吗? 2) 假设用计时一个单词需要x毫秒;如果我们在重复一项任务(突出显示一个单词,如卡拉OK应用程序),我会包含以下代码: 安静地工作很好,但我肯定我不能依靠这一点,因为时间,长度可能会改变!如何克服这个问题?因为几毫秒内的变化可能会给我带来灾难性的后果。 3) 同时,