То, когда Вам представили кольцевой буфер как массив, и Вам нужен индекс для повторений (т.е. когда Вы достигаете максимально возможного индекса и увеличиваете его), является им "лучше" к:
return (++i == buffer.length) ? 0: i;
Или
return ++i % buffer.length;
Имеет использование оператора по модулю какие-либо недостатки? Действительно ли это менее читаемо, чем первое решение?
Править:
Конечно, это должно быть ++ я вместо меня ++, изменил это.
РЕДАКТИРОВАНИЕ 2:
Одно интересное примечание: Я нашел первую строку кода в реализации ArrayBlockingQueue Doug Lea.
Обновление : OP признал в комментарии, что он должен был быть предварительно инкрементным. В большинстве других ответов это не было. Есть доказательство того, что инкремент в этом сценарии приводит к ужасной читабельности: есть ошибка, и большинство людей ее не видят.
Самая удобная для чтения версия следующая:
return (i == buffer.length-1) ? 0 : i+1;
Использование ++
добавляет ненужный побочный эффект к проверке (не говоря уже о том, что я твердо уверен, что вам следовало использовать вместо этого предварительное приращение)
В чем проблема с исходным кодом? Давайте посмотрим, не так ли?
return (i++ == N) ? 0 : i; // OP's original, slightly rewritten
Итак, мы знаем, что:
i
- это post -увеличено, поэтому, когда 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)
.
%
может быть сложной задачей, поэтому я рекомендую вместо этого версию с тернарным оператором. Однако наиболее важной частью является устранение побочного эффекта приращения.
Разве версия i ++% buffer.length
не имеет недостатка в том, что она продолжает увеличивать i, что может привести к достижению какого-то ограничения max_int / max_long / max_whatever?
Кроме того, я бы разделил это на
i = (i++ == buffer.length) ? 0 : i;
return i;
, иначе у вас, скорее всего, будет ошибка.
Не просите меня выбирать между двумя вариантами, оба из которых содержат постинкремент (*), смешанный с вычислением выражения. Я скажу «нет».
(*) Обновление: Позже исправили преинкремент.
Я думаю, что второе решение имеет явное преимущество в том, что оно работает, а первое - нет. Первое решение всегда будет возвращать ноль, когда i
становится больше, чем buffer.length
, потому что i
никогда не сбрасывается.
Оператор по модулю не имеет недостатков.
Конечно, было бы более читабельно использовать if:
i++;
if (i >= buffer.length)
{
i = 0;
}
return i;
Немного зависит от того, изменится ли когда-либо длина buffer.length.
Лично я предпочитаю подход по модулю. Когда я вижу по модулю, я сразу думаю об ограничении диапазона и зацикливании, но когда я вижу тернарный оператор, я всегда хочу подумать об этом более внимательно просто потому, что есть больше терминов, на которые нужно смотреть. Тем не менее, удобочитаемость является субъективной, как вы уже указали в своих тегах, и я подозреваю, что большинство людей не согласится с моим мнением.
Однако производительность не субъективна. Modulo подразумевает операцию деления, которая часто выполняется медленнее, чем сравнение с нулем. Очевидно, это труднее определить в Java, поскольку мы не компилируем собственный код до тех пор, пока не возникнет дрожание.
Я бы посоветовал написать то, , которое вы сочтете наиболее подходящим (при условии, что это работает!) и попросите коллегу (если он у вас есть) оценить это. Если они не согласны, спросите другого коллегу - тогда проголосуйте большинством. #codingbydemocracy
Это очень субъективно и зависит от того, что привыкли видеть ваши коллеги. Я бы лично предпочел первый вариант, так как он явно выражает то, что делает код, т.е. если длина буфера достигнута, сброс на 0. Вам не нужно проводить математические вычисления или даже знать, что делает модуль (конечно, нужно! :)
.Первый вызовет исключение ArrayIndexOutOfBoundsException, потому что i никогда не сбрасывается в 0.
Второй (вероятно) даст вам ошибку переполнения (или связанный с этим нежелательный эффект), когда i == Integer.MAX_VALUE (что может и не произойти в вашем случае, но это не хорошая практика, IMHO).
Итак, я бы сказал, что второй вариант «более правильный», но я бы использовал что-то вроде:
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 {{ 1}} 2
Заранее приносим свои извинения, если с моей стороны произошла ошибка кода и я случайно кого-то оскорбил! xx
PS: +1 за предыдущий комментарий об отказе от постинкремента с проверками равенства (я пока не могу обновить сообщения = /)
Я предпочитаю подход с условием даже если мы используем тип беззнаковый , операция по модулю имеет недостатки. Использование по модулю имеет плохой побочный эффект, когда проверяемое число возвращается к нулю
Пример:
255 % 7 == 3
Таким образом, если вы используете байт (беззнаковый символ), например, когда число перекатывается после 255 (т. Е. Нуля), это не приведет к до 4. Должно получиться 4 (при 256% 7), поэтому он вращается правильно. Поэтому просто используйте конструкции тестирования ( if
и тернарный оператор
) для проверки правильности
Если для достижения производительности, и , если число кратно 2 (т.е. 2 , 4, 8, 16, 32, 64, ...) используйте оператор и
.
Таким образом, если длина буфера равна 16, используйте:
n & 15
Если длина буфера равна 64, используйте 63:
n & 63
Они вращаются правильно, даже если число возвращается к нулю. Между прочим, если число кратно 2, даже подход по модулю / остатку также будет соответствовать счету, т.е. он будет вращаться правильно. Но я могу предположить, что операция &
быстрее, чем операция %
.
Я предпочитаю оператор по модулю по той простой причине, что он короче. И любая программа должна иметь возможность мечтать по модулю, поскольку он почти так же распространен, как оператор плюс.