当你有一个循环缓冲区表示为一个数组,你需要索引环绕时(即,当你达到最高可能的索引并增加它时),它是否“更好”:
return (++i == buffer.length) ? 0: i;
或
return ++i % buffer.length;
使用模运算符有任何缺点吗?它的可读性是否低于第一个解决方案?
修改
当然它应该是++ i而不是i ++,改变了它。
编辑2:
一个有趣的注意事项:我在Doug Lea的ArrayBlockingQueue实现中找到了第一行代码。
答案 0 :(得分:10)
更新:OP已在评论中承认应已预先增加。大多数其他答案错过了这个。有证据表明,这种情况的增加导致可怕的可读性:有一个错误,大多数人都看不到它。
最易阅读的版本如下:
return (i == buffer.length-1) ? 0 : i+1;
使用++
会给支票带来不必要的副作用(更不用说我强烈认为你应该使用预增量)
原始代码有什么问题?我们来看看吧?
return (i++ == N) ? 0 : i; // OP's original, slightly rewritten
所以我们知道:
i
发布 -incremented,因此当i == N-1
语句之前的return
时,这将返回N
而不是立即换行到0
N
作为独占上限i
通过命名约定建议局部变量,但它真的是吗?
相比之下:
return (i == N-1) ? 0 : i+1; // proposed alternative
我们知道:
i
未被修改,无论是本地变量还是字段i == N-1
时,返回的值为0
,这是更典型的情况%
方法或者,您也可以使用%
版本,如下所示:
return (i+1) % N;
%
有什么问题?好吧,问题是即使大多数人认为它是模数运算符,它也不是!它是余数运算符(JLS 15.17.3)。很多人经常对此感到困惑。这是一个典型的例子:
boolean isOdd(int n) {
return (n % 2) == 1; // does this work???
}
那段代码坏了!!!对于所有负值,它返回false
!问题在于-1 % 2 == -1
,尽管是数学-1 = 1 (mod 2)
。
%
可能很棘手,这就是我推荐使用三元运算符版本的原因。但最重要的是消除增量的副作用。
答案 1 :(得分:5)
不要让我在两个包含后增量(*)和表达评估的选项之间进行选择。我会说“没有”。
(*)更新:后来被修复为preincrement。
答案 2 :(得分:3)
i++ % buffer.length
版本不会有继续递增i的缺点,这可能会导致它达到某种max_int / max_long / max_whatever限制吗?
另外,我会把它分成
i = (i++ == buffer.length) ? 0 : i;
return i;
因为否则你很可能有一个错误。
答案 3 :(得分:3)
即使我们使用无符号类型,我更喜欢条件方法,模运算有缺点。当测试数量回滚到零时,使用模数会产生不良副作用
示例:
255 % 7 == 3
因此,如果您使用字节(无符号字符),例如,当数字在255之后滚动(即零)时,它将不会导致4.应该导致4(当256%7时),因此它正确旋转。因此,只需使用测试(if
和ternary operator
)构造来确保正确性
如果要获得性能,和如果数字是2的倍数(即2,4,8,16,32,64 ......),请使用&
运算符。
因此,如果缓冲区长度为16,请使用:
n & 15
如果缓冲区长度为64,请使用63:
n & 63
即使数字回零,它们也能正确旋转。顺便说一下,如果数字是2的倍数,即使模/余值方法也适合账单,即它将正确旋转。但我可能会猜测&
操作比%
操作更快。
答案 4 :(得分:3)
第一个会给你一个ArrayIndexOutOfBoundsException,因为我实际上从未重置为0。
当i == Integer.MAX_VALUE时,第二个(可能)会给你一个溢出错误(或相关的不良影响)(在你的情况下可能不会实际发生,但这不是好习惯,恕我直言)。
所以我会说第二个是“更正确”,但我会使用类似的东西:
i = (i+1) % buffer.length;
return i;
我认为这两个问题都没有。
我继续和测试每个人的代码,并伤心地发现,只有以前的职位(在这篇文章的写作的时间)的作品之一。 (哪一个?试着全部找出来!你可能会感到惊讶!)
public class asdf {
static int i=0;
static int[] buffer = {0,1,2};
public static final void main(String args[]){
for(int j=0; j<5; j++){
System.out.println(buffer[getIndex()]);
}
}
public static int getIndex(){
// return (++i == buffer.length) ? 0: i;
// return ++i % buffer.length;
// i = (i++ == buffer.length) ? 0 : i;
// return i;
// i++;
// if (i >= buffer.length)
// {
// i = 0;
// }
// return i;
// return (i+1 == buffer.length) ? 0 : i+1;
i = (i+1) % buffer.length;
return i;
}
}
预期产量为: 1 2 0 1 2
如果我的编码错误而且我不小心侮辱某人,请提前道歉! x.x中
PS:前一条关于不使用后期增量进行平等检查的评论+1(我实际上还不能upmod发布= /)
答案 5 :(得分:2)
我认为第二种解决方案具有明显的优势,即它有效,而第一种解决方案没有。当i
变得大于buffer.length
时,第一个解决方案将始终返回零,因为i
永远不会重置。
模运算符没有缺点。
答案 6 :(得分:2)
使用if:
肯定会更具可读性i++;
if (i >= buffer.length)
{
i = 0;
}
return i;
如果buffer.length发生变化,则取决于该位。
答案 7 :(得分:1)
这是非常主观的,取决于您的同事习惯看到的内容。我个人更喜欢第一个选项,因为它明确表达了代码的作用,即如果达到缓冲区长度,则重置为0.您不必执行任何数学思考,甚至不知道模数的作用(当然,应该!:)
答案 8 :(得分:1)
就个人而言,我更喜欢模数方法。当我看到模数时,我立即想到范围限制和循环,但是当我看到三元运算符时,我总是想更仔细地思考它,因为有更多的术语要看。可读性是主观的,正如您在标记中已经指出的那样,我怀疑大多数人会不同意我的看法。
然而,表现并非主观。 Modulo意味着一个divison操作,它通常比对零的比较慢。显然,这在Java中更难确定,因为在抖动开始之前我们不会编译本机代码。
我的建议是写出哪些你认为最合适(只要它有效!)并让同事(假设你有一个)来评估它。如果他们不同意,请问另一位同事 - 然后以多数票投票。 #codingbydemocracy
答案 9 :(得分:1)
值得注意的是,如果我们的缓冲区具有2的幂的长度,那么非常有效的位操作将起作用:
idx = (idx + 1) & (length - 1)
答案 10 :(得分:0)
我更喜欢模运算符,原因很简单。任何程序都应该能够以模数进行梦想,因为它几乎和加号运算符一样常见。
答案 11 :(得分:0)
您也可以使用位操作:
idx = idx & ((idx-length)>>31)
但它并不比我机器上的if-variant快。
以下是一些比较C#中运行时间的代码:
Stopwatch sw = new Stopwatch();
long cnt = 0;
int k = 0;
int modulo = 10;
sw.Start();
k = 0;
cnt = 0;
for ( int j=0 ; j<100000000 ; j++ ) {
k = (k+1) % modulo;
cnt += k;
}
sw.Stop();
Console.WriteLine( "modulo cnt=" + cnt.ToString() + " " + sw.Elapsed.ToString() );
sw.Reset();
sw.Start();
k = 0;
cnt = 0;
for (int j = 0; j < 100000000; j++) {
if ( ++k == modulo )
k = 0;
cnt += k;
}
sw.Stop();
Console.WriteLine( "if cnt=" + cnt.ToString() + " " + sw.Elapsed.ToString() );
sw.Reset();
sw.Start();
k = 0;
cnt = 0;
for (int j = 0; j < 100000000; j++) {
++k;
k = k&((k-modulo)>>31);
cnt += k;
}
sw.Stop();
Console.WriteLine( "bit cnt=" + cnt.ToString() + " " + sw.Elapsed.ToString() );
输出:
modulo cnt=450000000 00:00:00.6406035
if cnt=450000000 00:00:00.2058015
bit cnt=450000000 00:00:00.2182448