Хотелось бы раз и навсегда выяснить, что лучше и целесообразнее использовать в виде
ключа в хэш-коллекциях String или UUID?
Map map = ...
Map map = ...
Ответы
Ответ 1
Можно посмотреть на реализации функций hashCode в классах String и UUID.
Для String:
/**
* Returns a hash code for this string. The hash code for a
* String object is computed as
*
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
*
* using int arithmetic, where s[i] is the
* ith character of the string, n is the length of
* the string, and ^ indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
Для UUID:
/**
* Returns a hash code for this {@code UUID}.
*
* @return A hash code value for this {@code UUID}
*/
public int hashCode() {
long hilo = mostSigBits ^ leastSigBits;
return ((int)(hilo >> 32)) ^ (int) hilo;
}
Можно видеть, что для UUID вычисления хэша происходит очень быстро, с помощью битовых
операций над полями.
Для String хэш вычисляется путём прохода по каждому символу строки. Но это происходит
для каждого объекта только один раз. После того, как хэш уже был однажды вычислен,
он просто хранится в поле класса и возвращается, если он нужен.
При этом нужно отметить разницу в подходах хранения данных в этих классах. В классе
String строка хранится как массив значений char. А в классе UUID в двух полях типа
long. Вследствие этого, объект типа String, хранящий UUID, будет занимать куда больше
места, чем сам UUID. В строке будет около 36 символов, т.е. 72 байта, плюс расходы
на хранение, например, длины строки. В то время как в UUID это будет всего 16 байт.
Таким образом, если вы хотите использовать не очень много ключей, но при этом обращение
по ним может быть частым, можно использовать String. В остальных случаях, особенно,
если вам нужно хранить очень много разных ключей, однозначно лучше использовать UUID.
Энтони Хоар, человек который ввёл в употребление NULL-указатель высказал следующую мысль:
I call it my billion-dollar mistake. It was the invention of the null reference in
1965. At that time, I was designing the first comprehensive type system for references
in an object oriented language (ALGOL W). My goal was to ensure that all use of references
should be absolutely safe, with checking performed automatically by the compiler. But
I couldn't resist the temptation to put in a null reference, simply because it was
so easy to implement. This has led to innumerable errors, vulnerabilities, and system
crashes, which have probably caused a billion dollars of pain and damage in the last
forty years.
Простите, что не по-русски, не нашёл качественного перевода.
Суть в том, что он считает введение NULL ошибкой, которая стоила многих сил, и что
вместо введения NULL необходимо было ввести дополнительные проверки во время компиляции.
Мне интересно, возможна ли жизнь без NULL, и как это особенно согласуется с динамическими
языками (быть может для языков со статической типизацей этого и можно было избежать,
а для динамических -- нет?). Вообще, как много есть языков, которые обходятся без NULL
или эквивалента?
Как минимум один мне известен: Haskell.
UPD:
Для языков со статической типизацией можно проанализировать код и увидеть все ли
переменные инициализированны. (Попробовать обойтись без NULL)
Для динамических языков (не только с динамической типизацией, а таких, где есть eval
или похожий инструмент) такого гарантированно сделать нельзя.
Ответы
Ответ 1
Проблема заключается не в самом null, его использование абсолютно легально, т.к.
является по сути не артефактом конкретного языка программирования, а вычислительным
приемом, паттерном, - общим для всей теории программирования. Всегда существуют вычислительные
процессы (функции), работу которых можно оценивать с двух позиций: 1) есть результат;
2) нет результата. C этой точки зрения значение null есть унифицированный способ кодирования
ситуации "нет результата".
Проблема заключается в способе интеграции этого паттерна в систему типов языка. Значение
null в большинстве языков не имеет типа, точнее, null является значением некоторого
специального типа, являющегося подтипом ВСЕХ типов нашего языка. Поэтому null может
быть числом, строкой, кнопкой пользовательского интерфейса, и вообще принимать любую
форму. По сути дела, null - это "хак", непонятно как вписавшийся в статическую типизацию
артефакт динамической типизации. Именно здесь начинаются проблемы, о которых пишет
Хоар, и с которыми я согласен на 200%. Такой способ интеграции значения null означает,
что в ЛЮБОМ месте, где мы ожидаем некоторое значение, мы можем получить null. И для
нас как программистов нет способа гарантированно узнать, получим ли мы его или нет,
а для компилятора гарантированно проверить, что в коде мы учли ту ситуацию, когда вместо
ожидаемого результата получен null. Ситуацию могли бы несколько поправить (но не спасти!)
хорошая документация и дисциплинированность программистов. Если бы не тот факт, что
именно этих "добродетелей" в реальности почти не встретишь.
Чтобы исправить проблемы null, нужно выполнить два условия:
программисты должны быть лишены возможности использовать null в тех местах, где ЯВНО
не объявили такую возможность;
компилятор должен проверять и гарантировать нам, что клиентский код учел все такие
ситуации, где вместо результата может быть получен null.
Это можно сделать одним способом: сделать null значением некоторого обычного типа.
В Haskell этим типом является Maybe, в Scala - Option, в F# - option. Значения null
для этих языков называются соответственно Nothing, None и снова None. Тогда все встает
на свои места:
Выполнение 1-го условия:
// Ошибка компиляции: мы не указали ЯВНО, что можно использовать None
def notLessThan5(x: Int): Int = if (x >= 5) x else None
// OK
def notLessThan5(x: Int): Option[Int] = if (x >= 5) Some(x) else None
Выполнение 2-го условия:
// Ошибка компиляции: мы не учли, что notLessThan5() может не вернуть результата
(вернуть None)
println(notLessThan5(10) + notLessThan5(3))
// OK
println(notLessThan5(10).getOrElse(5) + notLessThan5(3).getOrElse(5)) // напечатает 15
Во всех случаях соблюдение правильных принципов работы со значениями null проверит
за нас компилятор, не допустив, чтобы NullPointerException "всплыла" в самый неподходящий
момент во время эксплуатации программы. Проблемы null решены. Следует особенно заметить:
мы не отказываемся от использования null. Мы просто интегрируем этот null в систему
типов иначе, чем это сделано сейчас, в Java и им подобных. None, Nothing и проч. -
есть точные эквиваленты null, только интегрированные в систему типов.
Динамические языки программирования не используют преимущества статической типизации,
поэтому все, что описано выше, их не касается. Избежать тех проблем, о которых говорил
Хоар, в динамических языках нельзя в принципе - любая функция может вернуть ЛЮБОЕ значение.
Это касается не только null, но вообще любых значений. Поэтому умные люди, которые
используют динамические языки в повседневной практике, давно уже описали в литературе
защитные практики от гибкости динамических языков. Основной практикой, помимо дисциплины
и документации, является повсеместное и всеобъемлющее тестирование кода, причем как
можно раньше, в идеале, до написания самого кода (TDD). Чудес не бывает: верификацию
при проверке типов приходится заменять верификацией при тестировании. На эту тему рекомендую
послушать великолепный доклад Robert Martin'а, произнесенный на RailsConf'09, "What
Killed Smalltalk Could Kill Ruby, Too".
Ответ 2
Если поставить себе такую цель - написать программу не использующую NULL, скажем
на C++, то это вполне осуществимо. Так что, в общем случае без NULL на C++ жить можно.
На C# сложнее будет это сделать, поскольку большинство методов в .NET могут вернуть
null. И как минимум проверять на null придется. Но это, согласитесь, уже не проблема
языка, а проблема библиотек. Были бы библиотеки не использующие null, и программы можно
было бы писать без него.
К тому же, можно создать классы-обертки для того чтобы они инкапсулировали в себе
работу с NULL и для внешнего наблюдателя никаких нулов не будет. Правильные программы
на C++ пишутся именно так.
Не думаю, что эти рассуждения не верны для других языков.
Ответ 3
Для минимизации вероятности типовых ошибок следует просто использовать стандартные
алгоритмические подходы. ArgumentNullException кошернее, чем NullReferenceException,
так как с большей вероятностью возникает именно там, где ошибка, а не постфактум.
Мне поставили задачу - разобраться с кросскомпиляцией C и C++ приложений из под Windows
для Linux.
Я попробовал использовать Cygwin для этих целей. Установил эту оболочку, запустил
её. Установил компилятор cygwin-gcc-3.3.6-glibc-2.3.2-linux (старый правда). И попробовал
из него скомпилировать обычный HelloWorld и запустить его под Linux всё получилось.
Но задача стоит чтобы из нашего Windows-приложения запускать кросс-компилятор, который
будет компилировать некоторые файлы. Как вообще это можно реализовать? Просто в данном
случае приходится запускать Cygwin, а уже из него gcc-linux или g++-linux. Пока не
вижу путей запуска из нашего приложения Windows Cygwin и далее уже в нём запуска компилятора.
Может быть какие-то есть другие выходы из ситуации? Как-то можно настроить вообще
отдельный компилятор по Windows, который будет компилить бинарники под Linux?
Ответы
Ответ 1
Установить cygwin.
Создать скрипт запускающий сборку.
Вызвать скрипт из приложения.
В скрипте (cmd-файл)
set CYGWIN_BIN=c:\cygwin\bin
CYGWIN_BIN\gcc.exe ...
В приложении
CreateProcess(...)
А еще есть такое средство для кросс-компиляций: crosstool-ng.
Ответ 2
А разве gcc.exe из cygwin не нужны cygwin-овские .dll ? По моему их загружает bash
при старте окошка cygwn.
По крайней мере у меня (Windows-XP) влет не получилось. c:\cygwin\bin\gcc t.c из
cmd потребовал cygwin1.dll
Вам надо разобраться с инициализацией cygwin, с загрузкой dll.
А вот так получилось:
c:\cygwin\bin\bash -c "gcc t.c"
Только абсолютно не уверен, что полученный модуль будет работать в Linux.
Ответ 3
Если я правильно понял вопрос, то можно использовать pipe'ы чтобы скармливать через
них команды в cygwin для g++-linux.
Расскажите, пожалуйста, о способах отладки и тестирования многопоточных приложений.
Есть ли у вас какие-то любимые, проверенные методики, может утилиты, которые будут
полезны (исключая уже много раз мною упомянутый Valgrind)?
На что при тестировании следует обратить особое внимание?
В общем всё, что подойдет относительно начинающему. Работаю в Freebsd с posix_thread.
Ответы
Ответ 1
@margosh, я тупо ставлю printf-ы в которых обязательно печатаю еще и thread id. Если
прога падает (SIGSEGV и т.п.) смотрю gdb. Он сообщает в каком потоке свалилось.
Вообще, все более-менее нетривиальные функции стараюсь сначала отладить в однопоточном
варианте (просто из main).
Многопоточную логику сначала выделяю в некие тестовые куски (без реального наполнения
данными задачи) и просто играюсь с ней (опять printf-ами, слипами, где-то ввод с клавиатуры
и запись в какой-нибудь пайп и т.д.).
Ну, и черкаю ручкой на бумаге линии потоков, какие события и когда происходят и т.п.
--
В целом так, но реально я иногда просто вижу алгоритм в виде каких-то цветных и объемных
фигурок, которые двигаются, сливаются, меняются ... и тогда мне становится ясно, как
это можно программировать.
Ответ 2
Unit и Integration тесты еще никто не отменял, причем они работают вне зависимости
от того, производилась ли параллелизация сразу же или же вы сначала реализовали однопоточный
вариант функции, а лишь затем распаралелили его.
Единственный минус - поднять более-менее адекватную тестовую среду для мультитредового
приложения или его мультитредовых блоков - это задача на порядок сложнее аналогичной
задачи для single-core environment.
И да, Valgrind, а именно DRD вполне себе неплохо справляются с поставленной задачей.
Мне, конечно, средства под Windows кажутся более интуитивными, но, как говорится -
на вкус и цвет.
Ответ 3
Могу в дополнение порекомендовать
ставить побольше assert'ов (прямо
максимум, что удастся выдумать, пусть
даже некоторые окажутся перебором),
при необходимости - делать очень
подробное логирование в память, а в
файл выводить только наиболее важные
сообщения. При ошибке, соответственно, сливать
лог из памяти в файл,
всю логику, связанную с многопоточностью
стараться делать максимально
просто!
Почему не рекомендуется использовать Thread.Resume() и Thread.Suspend()?
Ответы
Ответ 1
Не рекомендуется их использовать неверно.
Но если по правилам, то Thered.Resume() должен вызывать другой тред (по понятным
причинам, тред не может сам себя снять с паузы), а вот Thered.Suspend() должен вызывать
тред только у себя самого. У другого треда вызывать этот метод нельзя, так как непонятно,
где именно он остановится.
А не рекомендуют их, потому что есть много хороших и разных способов синхронизации:
критические секции, мютексы, события, семафоры и так далее.
Ответ 2
Взято отсюда - раздел #Suspend and Resume:
From .NET 2.0, Suspend and Resume have been deprecated, their use discouraged because
of the danger inherent in arbitrarily suspending another thread. If a thread holding
a lock on a critical resource is suspended, the whole application (or computer) can
deadlock.
This is far more dangerous than calling Abort — which results in any such locks being
released (at least theoretically) by virtue of code in finally blocks.
Не могу полностью удалить файл из истории Git.
Файл - бинарник с русским названием, и так получилось что пришлось чуть исправить
его название - изменить регистр пары букв.
Сделал коммит, залил на GitHub. Теперь и в консоли и в gui git'a "висит" сообщение
что файл со старым названием удален. Удалить через консоль нельзя - файл не существует,
отмена изменений в gui ни к чему не приводит - через несколько секунд сообщение снова
появляется. После новых коммитов ничего не меняется. На диске файла нет. ОС - Win8.
Я хотел бы полностью удалить файл из истории (т.е. из всех коммитов, где он упоминается).
Как это сделать?
Ответы
Ответ 1
В документации GitHub отлично описан этот кейс: https://help.github.com/articles/remove-sensitive-data/
Сначала делаем
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch ' \
--prune-empty --tag-name-filter cat -- --all
заменяем на название файла который нужно вычистить из репы. Затем:
git push origin --force --all
git push origin --force --tags
По умолчанию значение padding не включается в высоту и ширину блока. Либо отнимайте
от размеров высоты и ширины значения padding, либо задавайте целевому блоку свойство
box-sizing:border-box;, которое включит внутренние отступы и границы в значения ширины
и высоты элемента
.multi-accordion-title {
box-sizing: border-box;
...
}
Залогинился на удаленную машину. Вижу следующую строку в приветственном сообщении.
There is 1 zombie process.
Насколько я понял, это означает, что какой-то процесс завершил работу, но не закрылся
и не освободил ресурсы. Хотелось бы лучше понять проблему:
Как можно его обнаружить? Сейчас я угадал, но нужен стабильный метод.
Как можно узнать причины его появления? Есть какие-нибудь логи? Может, по имеющемуся
процессу можно что-либо понять?
Если это важно, ubuntu 14.04, sudo есть.
Дополнение: меня не устраивает метод «просто подождать», т.к. есть сервис, который
должен работать всегда — и подвис он или кто-то из его детей. Он подстрахован monit'ом,
но тот не распознал зомбификацию и не перезагрузил.
Ответы
Ответ 1
Зомби в операционных системах UNIX называют завершившиеся процессы, код завершения
которых не забрал родительский процесс. Зомби не потребляют никаких ресурсов, память
и файловые дескрипторы таких процессов уже освобождены. Остается только запись в таблице
процессов, которая занимает несколько десятков байт памяти. Так что единичный зомби
процесс на систему никак не влияет. НО он явный индикатор того, что у какого то процесса
в системе что то пошло не так.
Поиск зомби:
ps -axho state,pid,ppid | grep Z | sed 's/./ps/' | sh
Данная команда покажет все зомби процессы и их родителей (тестировалась под linux,
под другими *nix возможны другие ключи у команды ps).
Убить зомби можно только перезапуском родительского процесса. kill -9 самого процесса-зомби
и чеснок обычно не помогают. Если появление зомби разовое явление, то возможно проще
на факт его появления забить.
Что бы понять почему именно появился зомби надо смотреть исходники породившего его
процесса и возможно самого зомби. Часто это одна и та же программа. Любой процесс,
выполняющий fork, т.е. запускающий дочерние процессы должен уметь забирать код их завершения,
делается это вызовом wait и/или waitpid. Для начала надо просмотреть исходники на наличие
функций группы wait, а так же наличие функции-обработчика сигнала SIGCHLD (обработчик
устанавливается вызовом signal и функций для работы с сигналами потоков). Если родитель
не использует функции группы wait, то возможно при его нормальной работе завершение
дочерних процессов разработчик вообще не ожидал. Если это так - то причина образования
зомби - незапланированное завершение процесса потомка в следствие какой либо ошибки.
Дать рекомендаций как это искать и лечить невозможно. Можно только посоветовать добавить
в родительский поток обработку CHLD, забор кода завершения с помощью wait и логирование
факта завершения потомка. И дальше запуск потомка под отладчик и т.п. ...
Вариант 2: родительский процесс использует wait, но зомби все равно появляются. Копать
в сторону того, какая разновидность wait используется, если waitpid, которая проверяет
завершение конкретных потомков, то смотреть откуда она берет проверяемые pid, возможно
в процессе работы какие то pid потомков теряются и программа про них забывает. Может
программа в какой то момент запрещает обработку сигналов и забывает восстановить обработку,
после прохождения критического участка. Опять же - вариантов очень много, но сосредоточены
они вокруг обработчика SIGCHLD и функций wait.
Вариант 3: родительский процесс умеет обрабатывать и готов правильно обработать завершение
своих потомков. Но зацикливается в другом месте программы или засыпает на системном
вызове, например чтения с сетевого диска, который стал недоступен и при этом прерывание
по SIGCHLD запрещено. В этом случае надо разбираться с причинами его зависания. Кстати,
отсутствие доступа к каким либо ресурсам, типа сетевых дисков (или при выходе из строя
физического диска) - довольно частая причина массового появления зомби.
Каких либо специальных логов в системе, где можно было бы увидеть хоть какую то информацию
по появляющимся зомби не существует. Следы можно найти только в логах той программы,
которая их порождает, при их наличии.
Допустим есть кусок кода:
char *ptr = (char*)malloc(needed_size);
if (!ptr) {
error_handling();
} else {
do_something_without_freeing_ptr();
free(ptr);
ptr = 0;
}
Может ли вызов free не отработать/отработать неправильно и к каким последствиям это
может привести?
Подразумевается, что между вызовом malloc и free выделенная память не освобождается,
значение указателя не изменяется.
Не лучше ли, во избежание случайного изменения значения ptr, писать:
char * const ptr = malloc(needed_size);
Это не избавит от возможности написать free(ptr); free(ptr);, но по крайней мере
убережет от чего-то типа ptr = NULL;
Ответы
Ответ 1
free определена как функция, не возвращающая значений, т.е. возможности вернуть ошибку
у неё нет. Если ей передать указатель полученный не от calloc, malloc, realloc, либо
вызвать повторно для одного и того же указателя или изменить байты за пределами запрошенного
массива, действия зависят от реализации. Обычно это приводит к непредсказуемым ошибкам
в программе, возможно в совершенно другом месте. Такие ошибки трудно локализовать.
К примеру, в начале выделенного куска памяти перед тем, на что указывает указатель,
обычно находится заголовок — структура с информацией об этом куске и о следующем. Кроме
этого, где-то может быть информация о свободных кусках. Если, например, в результате
функции gets() будет испорчен заголовок для следующего блока, то проявиться эта ошибка
может даже не когда этот блок будет выделен malloc, а когда этот блок будет освобождаться.
Есть библиотеки реализующие набор функций calloc, malloc, realloc, free с дополнительными
средствами обнаружения их ошибочного использования.
Написать char * const ptr = malloc(needed_size); можно, но это не гарантирует, что
вы не скопируете указатель в другую переменную или примените индекс за пределами [0;
needed_size[.
Ответ 2
Согласно Стандарту C11 (7.22.3.3/2):
The free function causes the space pointed to by ptr to be deallocated,
that is, made available for further allocation. If ptr is a null
pointer, no action occurs. Otherwise, if the argument does not match a
pointer earlier returned by a memory management function, or if the
space has been deallocated by a call to free or realloc, the behavior is
undefined.
Т.о. если память была корректно выделена и не была после этого ещё освобождена с
помощью free или realloc, всё должно быть хорошо.
Если провести некоторую параллель с c++, то необходимость отсутствия ошибок в функции
free чем-то сродни необходимости не бросать исключения в деструкторах.
На счет дополнительного const к указателю - идея хорошая и правильная. Позволяет
выявлять ситуации непреднамеренной модификации или повторного использования переменной
в разных контекстах. Главное здесь, не переборщить и не завалить код constами там,
где от этого совсем нет профита.
С другой стороны, невозможность изменить значение указателя на NULL будет препятствовать
защите от повторного освобождения. Т.к. если указатель мутабельный, можно писать так
и не бояться последствий:
T* ptr = malloc(/*...*/);
// ...
free(ptr);
ptr = NULL;
free(ptr); // Безопасно
Понятно, что для T* const ptr такое будет невозможно.
Ответ 3
Насколько мне известно, стандартный С не позволяет контролировать неконсистентный
heap и вообще какие-либо ошибки при освобождении памяти.
Поэтому нужно пользоваться или возможностями используемой операционной системы, как
GetProcessHeap, HeapAlloc, HeapFree, HeapValidate под Windows или расширениями используемого
компилятора, см.
здесь и здесь.
В стандартной библиотеке есть большое количество случайных распределений, которые
предполагается использовать совместно с некоторым генератором случайных чисел.
Какой из генераторов в каких случаях следует использовать, и если это псевдослучайный
генератор - то как его инициализировать?
Ответы
Ответ 1
Генераторы псевдослучайных чисел
В стандартной библиотеке есть три генератора псевдослучайных чисел (ГПСЧ, англ. PRNG)
и один почти (sic!) генератор случайных чисел - random_device.
linear_congruential_engine, линейный конгруэнтный генератор - самый простой, быстрый
и низкокачественный. Использует формулу x[i] = x[i - 1] * A + B. Эквивалентен функции
rand(). Внутреннее состояние - это одно случайное число, совершенно предсказуем.
subtract_with_carry_engine, запаздывающий генератор Фибоначчи - немного лучше линейного
конгруэнтного генератора, имеет состояние в несколько случайных чисел.
mersenne_twister_engine, Вихрь Мерсена - наиболее качественная выдача случайных чисел
с практически бесконечным периодом, большое состояние - 624 числа (2.5 Кб).
Рекомендуемый ГПСЧ - это std::mt19937 - один из вариантов Вихря Мерсена.
Все ГПСЧ являются детерминированными, поэтому для того чтобы выдача не повторялась,
их состояние надо инициализировать какой-то уникальной последовательностью чисел.
Для этого можно использовать текущее время, чем точнее - тем лучше, например:
auto now = std::chrono::high_resolution_clock::now();
std::mt19937 random_generator(now.time_since_epoch().count());
В некоторых случаях может оказаться что текущего времени не достаточно, и нужен дополнительный
источник энтропии. Для этого можно использовать утилиту seed_seq, которая выравнивает
данные нескольких источников энтропии:
auto now1 = (uint64_t)std::chrono::high_resolution_clock::now().time_since_epoch().count();
uint64_t thread_id = std::hash{}(std::this_thread::get_id());
std::random_device random_device;
uint64_t entropy = random_device();
auto now2 = (uint64_t)std::chrono::high_resolution_clock::now().time_since_epoch().count();
std::seed_seq seeds{now1, now2, thread_id, entropy};
std::mt19937 random_generator(seeds);
(seed_seq принимает initializer_list, по этому аргументы должны быть однотипными).
Зная что у Вихря Мерсена такое больше состояние, может возникнуть желание полностью
заполнить его случайными числами, например вызвав random_device() 624 раза. Но это
не имеет никакого практического смысла. Цель - это получить уникальную последовательность
случайных чисел, и на практике для этого достаточно просто использовать текущее время.
Надо понимать, что ни один из стандартных ГПСЧ не является криптографически стойким,
даже выдачу Вихря Мерсена можно предсказать получив всего 624 числа.
Поэтому использование большой инициализирующей последовательности не дает никаких
преимуществ в плане безопасности.
std::random_device
random_device - это генератор случайных чисел, реализация которого зависит от стандартной
библиотеки.
В зависимости от платформы и реализации стандартной библиотеки, это может быть:
криптографически стойкий системный генератор действительно случайных чисел, в т.ч.
инструкция процессора rdrand (libc++)
чтение /dev/random или /dev/urandom (libstdc++)
вызов RtlGenRandom() на Windows (vc++)
обычный ГПСЧ который всегда выдает одни и те же числа (MinGW)
При этом если реализация использует /dev/(u)random и этот файл не удалось открыть,
то будет брошено исключение.
Также чтение /dev/random может заблокировать работу программы до момента когда система
накопит достаточно энтропии.
Поэтому std::random_device следует использовать только на тех системах и компиляторах
про которые известно что она там работает как ГСЧ, и быть готовым к тому что эта функция
очень медленная по сравнению с ГПСЧ.
(У random_device есть функция entropy(), но она бесполезна, т.к. на многих платформах
она всегда возвращает 0 даже если используется ГСЧ а не ГПСЧ.)
Распределения
Классы распределений используют выдачу генераторов случайных чисел чтобы преобразовать
ее в последовательность имеющую нужное распределение.
Некоторые классы распределений, например нормальное распределение и производные от
него, имеют внутреннее состояние, поэтому не надо создавать новый объект распределения
ка каждое случайное число.
std::mt19937 generator;
std::normal_distribution distribution(0, 1);
std::vector data;
for (auto i = 0; i != 100; ++i) {
data.push_back(distribution(generator)); // OK
}
for (auto i = 0; i != 100; ++i) {
// ПЛОХО, не используется состояние распределения
data.push_back(std::normal_distribution(3, 1)(generator));
}
Впрочем даже если и создавать новое распределение на каждое число, форма распределения
меняется незначительно.
Примеры
Заменой кода с ::rand() из
::srand(::time(0));
for (int i = 0; i != n; i++) std::cout << ::rand() << '\0';
будет либо использование mt19937
std::mt19937 rand(::time(0)); // Лучше использовать high_resolution_clock
for (int i = 0; i != n; i++) std::cout << rand() << '\0';
либо использование random_device (это нормально для программ уровня "Hello World")
std::random_device rand;
for (int i = 0; i != n; i++) std::cout << rand() << '\0';
Более сложный пример с функтором-генератором случайных чисел:
#include
#include
#include
#include
#include
#include
struct GaussGenerator
{
GaussGenerator(double mean, double stddev, std::uint32_t seed)
: engine_(seed), distribution_(mean, stddev) {}
GaussGenerator(double mean, double stddev)
: distribution_(mean, stddev)
{
using namespace std;
seed_seq seeds{
(uint64_t)chrono::high_resolution_clock::now().time_since_epoch().count(),
(uint64_t)chrono::system_clock::now().time_since_epoch().count(),
(uint64_t)hash{}(this_thread::get_id()),
};
engine_.seed(seeds);
}
double operator()() { return distribution_(engine_); }
std::mt19937 engine_;
std::normal_distribution distribution_;
};
int main()
{
GaussGenerator rand(0, 1);
for (int i = 0; i != 5; ++i)
std::cout << rand() << '\n';
}
Ответ 2
Видимо, многим людям не дает покоя все нарастающая сложность софта и они (в меру
своих сил) пытаются с ней совладать.
Недолгий поиск по сети реализаций ГПСЧ принес свои плоды -- PCG, A Family of Better
Random Number Generators.
О своих недостатках они пишут, что
New
Although it is less trivial to predict than many mainstream generators, that does
not mean it should be considered
crypographically secure. Exactly how hard it is to break different
members of the PCG family is unknown at this time.
Естественно, пройдя по ссылке можно скачать готовые библиотеки и документацию для
нескольких языков.
А вот и образец кода одного из генераторов:
// *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org
// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website)
typedef struct { uint64_t state; uint64_t inc; } pcg32_random_t;
uint32_t pcg32_random_r(pcg32_random_t* rng)
{
uint64_t oldstate = rng->state;
// Advance internal state
rng->state = oldstate * 6364136223846793005ULL + (rng->inc|1);
// Calculate output function (XSH RR), uses old state for max ILP
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
uint32_t rot = oldstate >> 59u;
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}
Как указать в regexp символ перевода строки и еще несколько символов #, !, =, :, пробел.
Как-то так?
(" #!=:\n\r")
Ответы
Ответ 1
\n - перевод строки
\s - один из символов пробела Unicode и эквивалент [\t\n\v\f\r \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]
https://stackoverflow.com/a/2429180
\u0020 - сам символ пробела https://www.cs.tut.fi/~jkorpela/chars/spaces.html
\n\#\!\=\:\s
В самом выражении спец.символы могут быть записаны так (если не ошибаюсь): [@#$%^&+=]
https://htmlweb.ru/java/regexp.php
Пример: https://stackoverflow.com/questions/10664434/escaping-special-characters-in-java-regular-expressions
Ответ 2
Ну, во первых в джаве два слэша ставится, в данном случае \\s+ будет обозначать любое
количество пробельных символов начиная с 1-го, тут и перевод строки пробелы тоже входят.
Конкретно для этой цели : "#|!|=|:|\\s".
Как это можно сделать, без "ручной" простановки знака +
Ответы
Ответ 1
Самый простой путь — воспользоваться кастомным форматированием:
var x = 4;
var s = x.ToString("+#;-#;0");
Три случая, разделённые ;, отвечают числу больше нуля, меньше нуля и нулю. Если вы
хотите, можно выводить и ноль со знаком: "+#;-#;+0".
Если вы используете string.Format, сработает string.Format("{0:+#;-#;+0}", x). Для
интерполированных строк $"{x:+#;-#;+0}".
Для второй секции нужен явный минус, т. к. в этом случае он убирается. Для третьей
секции нужен 0 вместо #, т. к. # не учитывает незначащие нули.
Как составить такое регулярное выражение которое проверяет только первую букву в
строке на то является она согласной или гласной?
Тут нужно как-то регулярное выражение написать
if (string.matches("[рег-ое выражение]")) {
return true;
}
Ответы
Ответ 1
Должно быть достаточно такого: str.matches("^(?i:[aeiouy]).*"). Для кириллицы - str.matches("^(?ui:[аеёиоуыэюя]).*").
^ обозначает начало строки
(?i:...) - включает CASE_INSENSITIVE для этой группы, чтобы не перечислять повторно
заглавные буквы.
(?ui:...) Важно! Если вы работаете с юникодом, то вышеуказанный ключ не сработает.
Нужно добавить UNICODE_CASE, т.е. ?ui.
[aeiouy] - все гласные. Если нужно найти все согласные, то нельзя использовать [^aeiouy],
т.к. в эту группу попадают вообще все символы юникода, кроме этих шести. Нужно точно
так же перечислить согласные.
.* - дальше сколько угодно (хоть ноль) любых символов, кроме конца строки.
Демонстрация работы с разными строками:
public class Main {
public static void main(String[] args) {
String[] strs = {"abcd efjk", "hello world", "a", "E", "d", "", "\n", "юникод"};
for (String str: strs) {
boolean test = str.matches("^(?i:[aeiouy]).*");
System.out.println(str + ": " + test);
}
}
}
Результат:
abcd efjk: true
hello world: false
a: true
E: true
d: false
: false
: false
юникод: false
Ответ 2
Чтобы определить, является ли первая буква в строке гласной или согласной, можно
обойтись без регулярного выражения. Достаточно определить строку со всеми гласными
или согласными, а потом проверить наличие в этой строке первого символа тестируемой строки:
// Начинается ли строка с гласной?
String vowelsRussian = "аоуэиыеёяю"; // "aeiouy" (англ.)
String testRussian = "Ёрш сегодня не клюёт";
if (vowelsRussian.indexOf(Character.toLowerCase(testRussian.charAt(0))) != -1) {
System.out.println(testRussian + " начинается с гласной");
}
// Для проверки на согласную букву в начале используйте
// String consonantsRussian = "йцкнгшщзхфвпрлджбтмсч";
// String consonantsEnglish = "qwrtplkjhgfdszxcvbnm"; // "y" можно рассматривать
и как гласную, и как согласную букву
Регулярное выражение имеет смысл использовать в более сложных сценариях. Тогда можно
использовать следующее решение:
// Тоже самое с использованием регулярного выражения
Matcher m = Pattern.compile("^[АОУЭИЫЕЁЯЮаоуэиыеёяю]").matcher(testRussian);
if (m.find()) {
System.out.println(testRussian + " начинается с гласной");
}
// С согласными можно так же, как и с гласными, или можно воспользоваться
// вычитанием символьных классов:
String testRussian2 = "Сегодня ёрш клюёт";
Matcher m2 = Pattern.compile("^[б-щБ-Щ&&[^ОУИЕоуие]]").matcher(testRussian2);
if (m2.find()) {
System.out.println(testRussian2 + " начинается с согласной");
}
См. Java-демо онлайн.
Внимание: find() работает оптимальнее matches(), так как не требует полного совпадения
(не возникнет проблем со строками, содержащими символы переноса строки при меньшей
нагрузке на процессор).
Подробнее о вычитании символьных классов в Java смотрите Регулярные выражения в Java.
Часть 2. В регулярке [б-щБ-Щ&&[^ОУИЕоуие]] нет а, э, ю, я, ё, так как эти гласные просто
не входят в диапазон б-щБ-Щ.
Не стоит использовать флаг UNICODE_CASE без необходимости (так как "Specifying this
flag may impose a performance penalty" (= "При использовании данного флага могут возникнуть
проблемы с производительностью"), лучше просто перечислить необходимые нам в данном
случае диапазоны символов.
Ответ 3
Я нашел ответ, вроде бы.
String string = "touch";
if (string.matches("^[^aeiouyAEIOUY]\\w++")) {
System.out.println("гуд");
} else if (string.matches("^[aeiouyAEIOUY]\\w++")) {
System.out.println("бэд");
}
Ответ 4
Выше было отвечено как определить гласную букву (через перечисление [aeiouy]), как
все остальные символы ([^aeiouy]). Согласную же букву можно определить не через полное
их (согласных) перечисление, а через гласные с использованием позитивного просмотра вперед:
(?i)^(?=[^aeiouy])[a-z].*
(?i) - игнорируем регистр;
^ - начало строки;
(?=[^aeiouy]) - позитивный просмотри вперед, проверяем первый символ на несовпадение
с гласными буквами без смещения указателя;
[a-z] - первый символ должен быть латинской буквой;
.* - все остальные символы в строке, если они есть.
Думаю так короче будет, чем перечислять все гласные буквы.
Есть такая табличка статусов слонов, то есть у слона может быть несколько статусов
его местонахождения.
status:
id int auto_increment pk
slon_id int
status varchar
date datetime
При программном баге (точнее недоработке) появились дубликаты записей вида
1 1 Розовый слон в Африке 18.11.2016...
2 1 Розовый слон в Африке 18.11.2016...
3 1 розовый слон уже в Зимбабве 19.11.2016...
4 2 Зеленый слон в Европе 15.11.2016...
Интересует SQL решение. Можно ли как-то удалить дубликаты записей? Можно ли удалить
дубликаты записей оставив по одному чтобы не потерять историю? (То есть если про Африку
и Розового слона несколько записей - удалить все кроме одной).
Ответы
Ответ 1
Алгоритм действий:
понять, по каким критериям надо считать записи идентичными
написать запрос на поиск дублирующихся наборов данных (можно не строк, только наборов
данных)
понять, как решить, какую из записей надо оставить
соответственно откорректировать запрос, чтобы он выводил все дублирующиеся записи,
кроме той, которую надо оставить
написать запрос delete where id in (subquery).
В итоге получается что-то вроде такого, если считать uniqfield полем, которое должно
стать уникальным, оставлять строку с минимальным id:
delete from tablename where id in (
select id from (
select id from tablename join (
select min(id) as firstdup, uniqfield
from tablename
group by uniqfield
) duplicates using(uniqfield)
where id != firstdup
) subqueryhack
)
Для комбинации из двух полей, чтобы было видно, что меняется
delete from tablename where id in (
select id from (
select id from tablename join (
select min(id) as firstdup, uniqfield1, uniqfield2
from tablename
group by uniqfield1, uniqfield2
) duplicates using(uniqfield1, uniqfield2)
where id != firstdup
) subqueryhack
)
delete в mysql не даст напрямую удалять из той таблицы, которую читает подзапрос,
но это обходится при необходимости ещё одним подзапросом.
Если неважно, какие именно строки оставлять, а какие удалять и позволяет версия СУБД
- то до mysql 5.7.4 можно было просто повесить уникальный индекс с указанием ignore
ALTER IGNORE TABLE mytable ADD UNIQUE INDEX myindex (A, B, C, D);
Что удалит все дубли по этим полям, кроме какой-то одной строки. В актуальных версиях
ignore поведение удалено и вызывает ошибку. Не вникал, почему удалили, скорей всего
из-за неочевидного поведения, какая именно строка будет удалена и общего курса возвращения
к SQL-стандарту.
Ответ 2
Должен работать этот вариант. TESTTEST нужно заменить на своё имя таблицы.
Данный запрос будет оставлять одну запись для каждого slon_id с максимальной датой.
DELETE FROM TESTTEST WHERE id NOT IN (
SELECT MAX(id)
FROM
(SELECT t.slon_id, MAX(t.[date]) AS [date]
FROM TESTTEST t GROUP BY t.slon_id) t
JOIN TESTTEST t2
ON t.slon_id = t2.slon_id AND t.[date] = t2.[date]
GROUP BY t2.id
)
Интересует такой вопрос. Допустим, у меня есть какая-то кнопка, которая лежит на
столе. Какие микроконтроллеры, устройства и т.п. можно использовать, чтобы реализовать
взаимодействие этой кнопки и java?
Ответы
Ответ 1
Поскольку Java изначально разрабатывалась для программирования холодильников и кофеварок,
было бы странно, если бы возможность работы с железом была утрачена напрочь.
Самый простой вариант подключения кнопки к микроконтроллеру - это порты GPIO, которые
широко распространены в embedded мире. Кнопка одним контактом подключается к выводу
порта, другим - к земле (GND). Дальше микроконтроллер либо сам что-то делает, либо
по UART/USB/Ethernet/WiFi/Bluetooth отправляет сообщение о нажатой кнопке куда-то еще.
Непосредственно по железу варианты есть следующие.
Использовать специализированный микроконтроллер, заточенный под Java.
Да, такие есть. Например семейство 3G-модулей Centerion. Прошивка представляет собой
мидлет Java ME и может заливаться в том числе по воздуху. Прослушивание порта выглядит
как-то так:
Vector inPins = new Vector();
inPins.addElement("GPIO11");
InPort inPort = new InPort(inPins);
inPort.addListener(new InPortListener() {
public void portValueChanged(int val) {
System.out.println("Port value: " + val);
}
});
Использовать полноценную встраиваемую систему общего назначения на базе ARM-процессора,
такую как BeagleBone или RaspberryPi.
Там у вас будет полноценный Linux с возможностью установки полноценной Java SE или
Java SE Embedded. А порты будут доступны через файловую систему как устройства вроде
/sys/class/gpio/gpio49. Можно взаимодействовать с портом обычными средствами файлового
ввода-вывода (что будет относительно медленно, хотя и достаточно для многих задач)
или через прямой досутп к памяти через memory-mapped файл /dev/mem (что будет быстро).
Но куда приятнее будет воспользоваться развесистым API сторонней библиотеки pi4j:
GpioController gpioController = GpioFactory.getInstance();
GpioPinDigitalInput pin02 = gpioController.provisionDigitalInputPin(RaspiPin.GPIO_02,PinPullResistance.PULL_DOWN);
pin02.addListener(new GpioPinListenerDigital() {
@Override
public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent
gpioPinDigitalStateChangeEvent) {
System.out.println("state: " + gpioPinDigitalStateChangeEvent.getState());
}
});
Кроме того, существует еще и Java ME Embedded, предоставляющий нативный API доступа
к портам в пакете com.oracle.deviceaccess:
GPIOPin switchPin = null;
switchPin = (GPIOPin) PeripheralManager.open(1);
switchPin.setInputListener(new PinListener() {
@Override
public void valueChanged(PinEvent event) {
// do something
}
});
К слову, жизнь тут не ограничена ARM, есть и MIPS и Intel Atom. Но подходы там те же.
Очень интересными выглядят сторонние программные платформы на основе Java:
MicroEJ. На Youtube есть ролик, где java-приложение с графикой исполняется на очень
слабом железе (Cortex™-M0+ @48 MHz, 256 KB Flash, 32 KB RAM).
Android Things от Google, ранее известная как Brillo.
Удивительно, но на Java можно писать под 8-битные AVR-микроконтроллеры (те самые,
на которых построена Arduino)!
Это стало возможным благодаря HaikuVM, которая транслирует байткод Java из .class-файлов
в С-структуры, линкует к ним интерпретатор и формирует на выходе обычный для AVR-микроконтроллеров
HEX-файл, который шьется в железку как обычно.
Еще есть:
NanoVM - виртуальная машина подмножества Java, занимающая 8 кБ в флэш-памяти.
uJ - эта VM крупнее (десятки килобайт), но обещает полную поддержку байткода, многопоточность
и synchronized.
Если вы хотите нопку подключить к Arduino, а его в свою очередь подключить к компьютеру
и делать что-то на компьютере по нажатию на кнопку, попробуйте JArduino. Этот API требует
залить на Arduino свою прошивку, после чего вы сможете взаимодействовать с устройством
из обычной Java-программы на своем компьютере.
Попадалось множество принципиальных электрических схем, на устройствах, подключаемых
к компьютеру, где Vcc и Vdd взаимозаменяемы.
Знаю, что Vcc и Vdd используются для положительного напряжения, а Vss и Vee для обозначения
общей линии (земли), но в чем разница между каждыми из двух вариантов в парах?
Означают ли знаки C, D, S и E что-либо?
И в дополнение: почему Vdd а не просто Vd ?
Ответы
Ответ 1
Vcc, Vee, Vdd, Vss - откуда такие обозначения?
Обозначения цепей питания проистекают из области анализа схем на транзисторах, где,
обычно, рассматривается схема с транзистором и резисторами подключенными к нему. Напряжение
(относительно земли) на коллекторе (collector), эмиттере (emitter) и базе (base) обозначают
Vc, Ve и Vb. Резисторы подключенные к выводам транзистора обозначим Rc, Re и Rb. Напряжение
на дальних (от транзистора) выводах резисторов часто обозначают Vcc, Vee и Vbb.
На практике, например для NPN транзистора включенного по схеме с общим эмиттером,
Vcc соответствуют плюсу, а Vee минусу источника питания. Соответственно для PNP транзисторов
будет наоборот.
Аналогичные рассуждения для полевых транзисторов N-типа и схемы с общим истоком дают
объяснение обозначений Vdd и Vss (D — drain, сток; S — source, исток): Vdd — плюс,
Vss — минус.
Обозначения напряжений на выводах вакуумных ламп могут быть следующие: Vp (plate,
anode), Vk (cathode, именно K, не C), Vg (grid, сетка).
Как написано выше, Vcc и Vee используются для схем на биполярных транзисторах (Vcc
- плюс, Vee — минус), а Vdd и Vss для схем на полевых транзисторах (Vdd - плюс, Vss
— минус). Такое обозначение не совсем корректно, так как микросхемы состоят из комплементарных
пар транзисторов. Например, у КМОП микросхем, плюс подключен к P-FET истокам, а минус
к N-FET истокам. Тем не менее, это традиционное устоявшее обозначение для цепей питания
независимо от типа проводимости используемых транзисторов.
Для схем с двух полярным питанием Vcc и Vdd могут интерпретироваться как наибольшее
положительное, а Vee и Vss как самое отрицательное напряжение в схеме относительно земли.
Для микросхем питающихся от одного или нескольких источников одной полярности минус
часто обозначают GND (земля). Земля может быть разной, например, сигнальная, соединение
с корпусом, заземление.
Не знаю разрешено ли указывать сторонние ресурсы, но мне кажется будет нечестным
умалчивать автора. РадиоКот
#include
using namespace std;
int main()
{
int b[10];
int ( *a )[ 10 ]; // эта
a = b; // error
a[ 0 ] = new int; // error
a = new int; // error
}
Пытаюсь методом подбора понять что это и как это используется.
Ответы
Ответ 1
Чтобы проще было понять запись
int ( *a )[ 10 ];
можно ввести определение алиаса для массива
typedef int T[10];
T *a;
то есть объекты, которые может адресовать указатель, представляют собой целочисленные
массивы из десяти элементов.
Если вы хотите, например, чтобы этот указатель адресовал такой массив, вы можете написать
typedef int T[10];
T b;
T *a;
a = &b;
Тогда выражение *a или a[0] представляет собой массив b
Например, вы можете написать
typedef int T[10];
T b;
T *a;
a = &b;
for ( int i = 0; i < 10; i++ ) a[0][i] = i;
Если хотите динамически выделить массив, то вам следует записать
typedef int T[10];
T *a;
a = new T[1];
Это эквивалентно следующему фрагменту кода
int ( *a )[10];
a = new int[1][10];
То есть такой указатель используется обычно при работе с двумерными массивами. Например,
#include
int main()
{
const size_t M = 2;
const size_t N = 10;
int b[M][N] =
{
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
{ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }
};
for ( int ( *a )[N] = b; a != b + M; ++a )
{
for ( int x : *a ) std::cout << x << ' ';
std::cout << std::endl;
}
}
Вывод на консоль
0 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 0
Ответ 2
Формально это - указатель на массив из 10 int.
Так что присваивать нужно как
a = &b;
Если хотите использовать для присваивания элемента - то
(*a)[0] = 12; // Присваивание b[0] = 12
Если же вы имеете в виду массив массивов по 10 элементов, то
a[0][3] = 15; // Присваивание третьему элементу первого массива
// по адресу a значение 15 - то же, что и b[3] = 15;
Ну, а последнее... например, так:
using arr = int[10];
a = new arr[1];
Есть статический класс Helper. Хочу использовать его из другой формы, однако мне
приходится вызывать его через Helper.Val. Можно ли как то сделать using Helper; и потом
просто вызывать Helper
public static class Helper
{
public static double Val(string data)
{
return Convert.ToDouble(data.Replace(".", ","));
}
}
Ответы
Ответ 1
Можно вот как.
В начале файла
using static YourNamespace.Helper;
и в коде просто
double v = Val(data);
(Обратите внимание, не просто using, а using static.)
Работает, начиная с C# 6 (Visual Studio 2015).
Один из методов бизнес-логики начинается с кода валидации:
public void Update(ProductDto dto)
{
if (dto == null)
{
throw new ArgumentNullException(nameof(dto));
}
var validContext = new ValidationContext(dto);
Validator.ValidateObject(dto, validContext);
//...
}
Если в метод передается null, то я выбрасываю стандартное исключение ArgumentNullException
и никакой дополнительной информации как видите не отсылаю.
Мне кажется, что блок проверки на null излишен, потому что в случае dto = null следующее
за ним выражение var validContext = new ValidationContext(dto) так и так выбросит
то же самое исключение:
Будет ли ругаться "практика правильного дизайна", если я сокращу метод так?
public void Update(ProductDto dto)
{
var validContext = new ValidationContext(dto);
Validator.ValidateObject(dto, validContext);
//...
}
Ответы
Ответ 1
Да, такое изменение я бы не назвал правильным.
Дело в том, что вы тем самым вносите в ваш метод связность. Ваш код будет правильным
лишь в том случае, если
конструктор ValidationContext проверяет входящий объект на null, и бросает исключение, и
у вас до вызова этого конструктора нету другого кода, который использует dto или
делает какую-то другую полезную работу.
Каждое из этих предположений нужно будет держать в голове, работая с данным кодом.
Причём в коде придётся ещё и оставлять комментарий, который будет объяснять, что null
не является валидным входным значением, и почему именно в этом месте нету проверки на null.
В противоположность этому проверка в начале является практически самодокументируемым
кодом, она сразу говорит читателю, что нулевое входное значение неверно.
Чем меньше нужно держать в голове, тем лучше, и тем меньше вероятность ошибки.
Ещё одно мелкое соображение в пользу ранней проверки: если вы видите stack trace
упавшего на ArgumentNullException приложения, то ошибка скорее всего находится в методе
на один фрейм выше метода, бросившего исключение. В случае «пропуска» плохого аргумента
в глубину это соображение не работает.
Ответ 2
Внесу краткое дополнение к ответу.
Не сложность заметить, что конструкции вида:
public void Update(ProductDto dto)
{
if (dto == null)
{
throw new ArgumentNullException(nameof(dto));
}
//...
}
выглядят довольно громоздко и не эстетично, так и возникает желание избавиться от
них. А потому одним из способов добиться этого, может стать использование контрактов
(Code Contracts). Например, в случае их использования код выглядел бы так:
public void Update(ProductDto dto)
{
// Определили Предусловие. Нарушение предусловия говорит о том,
// что клиент не прав.
Contract.Requires(dto != null);
//...
}
Это не только сократит его и сделает чище, но а так же более явно выразит ваши требования.
Приложение будет передавать множество файлов, и мне важно, что бы они были переданы
с названиями. Для этого нужно отправлять каждый раз сообщение с названием файла или
это как то можно сделать иначе?
Ответы
Ответ 1
Первым байтом отправляете длину имени файла, следующие 4 байта - длина файла, следом
байты имени файла, а потом байты самого файла. Или, чтобы не заморачиваться с конвертацией
данных, можно поля чуточку расширить:
Server.java
package com.example;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
private static int PORT = 2121;
private static String FOLDER = "./files";
public static void main(String[] args) {
File sourceDir = new File(FOLDER);
try (ServerSocket listener = new ServerSocket(PORT)) {
while (true) {
try (Socket socket = listener.accept();
OutputStream out = socket.getOutputStream()) {
for (String fileName : sourceDir.list()) {
// Преобразовываем строку, содержащую имя файла,
// в массив байт
byte[] name = fileName.getBytes("utf-8");
// Отправляем длину этого массива
out.write(name.length);
// Отправляем байты имени
out.write(name);
File file = new File(FOLDER + "/" + fileName);
// Получаем размер файла
long fileSize = file.length();
// Конвертируем его в массив байт
ByteBuffer buf = ByteBuffer.allocate(Long.BYTES);
buf.putLong(fileSize);
// И отправляем
out.write(buf.array());
try (FileInputStream in = new FileInputStream(file)) {
// Читаем файл блоками по килобайту
byte[] data = new byte[1024];
int read;
while ((read = in.read(data)) != -1) {
// И отправляем в сокет
out.write(data);
}
}
catch(IOException exc) {
exc.printStackTrace();
}
}
}
catch(IOException exc) {
exc.printStackTrace();
}
}
}
catch(IOException exc) {
exc.printStackTrace();
}
}
}
Client.java
package com.example;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.net.Socket;
public class Client {
private static int PORT = 2121;
private static String HOST = "localhost";
private static String FOLDER = "./files";
public static void main(String[] args) {
try (Socket s = new Socket(HOST, PORT);
InputStream in = s.getInputStream()) {
// Читаем размер имени
int nameSize;
while((nameSize = in.read()) != -1) {
// Читаем само имя
byte[] name = new byte[nameSize + 1];
in.read(name, 0, nameSize);
// Преобразовываем обратно в строку
String fileName = new String(name, "utf-8").trim();
System.out.println(fileName);
File file = new File(FOLDER + "/" + fileName);
try (FileOutputStream out = new FileOutputStream(file)) {
// Читаем размер файл
byte[] fileSizeBuf = new byte[8];
in.read(fileSizeBuf, 0, 8);
// Преобразовываем в long
ByteBuffer buf = ByteBuffer.allocate(Long.BYTES);
buf.put(fileSizeBuf);
buf.flip();
long fileSize = buf.getLong();
// Читаем содержимое файла блоками по килобайту
int read = 0;
byte[] data = new byte[1024];
while (read < fileSize) {
read += in.read(data);
// И пишем в файл
out.write(data);
}
}
catch(IOException exc) {
exc.printStackTrace();
}
}
}
catch(IOException exc) {
exc.printStackTrace();
return;
}
}
}
Ответ 2
Есть несколько подходов к решению Вашей проблемы:
Сериализация. Использовать встроенную сериализацию (Java Serialization API), либо
одну из популярных библиотек. То есть вы сериализуете объект, передаете и десериализуете
его на приемной стороне.
Использовать свой протокол обмена данных. Например, после установления соединения,
клиент передает имя файла, получает подтверждение от сервера, затем передает содержимое
файла и снова получает подтверждение о получении файла.
Посылать имя файла и его содержимое одним запросом, тогда необходимо организовывать
формат передачи данных, как уже было описано в этом ответе
Хочу на SVG красиво сделать логотип сайта.
Логотип это слово текста, его необходимо залить фоном. Фон должен состоять из цветных
пятен разного цвета случайно расположенных на холсте, цвета задаются заранее заданной
палитрой цветов.
Необходимо два варианта:
Пятна имеют чёткую границу
Граница пятен уходит в прозрачный цвет через градиент
Ответы
Ответ 1
Вариант с чёткими границами
var svg = document.getElementById("svg1");
var width = svg.clientWidth;
var height = svg.clientHeight;
var sircleNumber = 100;
var minRadius = 20;
var maxRadius = 30;
var colors = ["red", "orange", "yellow", "blue", "green"];
var svgNS = "http://www.w3.org/2000/svg";
function generateBack(id) {
var parent = document.getElementById(id);
parent.innerHTML = "";
for (var i = 1; i <= sircleNumber; i++) {
generateTwo(i);
}
function generateTwo() {
var circle = document.createElementNS(svgNS, "circle");
var x = random(0, width);
var y = random(0, height);
var rad = random(minRadius, maxRadius);
var color = randomColor();
circle.setAttributeNS(null, "cx", x);
circle.setAttributeNS(null, "cy", y);
circle.setAttributeNS(null, "r", rad);
circle.setAttributeNS(null, "fill", color);
parent.appendChild(circle);
}
}
function randomColor() {
return colors[random(0, colors.length - 1)];
}
function random(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
generateBack("svgBack");
@import url(https://fonts.googleapis.com/css?family=Open+Sans:800);
body {
font-family: Open Sans;
}
Вариант с границами градиентно исчезающими
var pattern = ``;
var svg = document.getElementById("svg1");
var width = svg.clientWidth;
var height = svg.clientHeight;
var sircleNumber = 100;
var minRadius = 15;
var maxRadius = 50;
var colors = ["red", "orange", "yellow", "blue", "green"];
var svgNS = "http://www.w3.org/2000/svg";
function generateBack(id) {
var parent = document.getElementById(id);
parent.innerHTML = "";
for (var i = 1; i <= sircleNumber; i++) {
generateOne(i);
}
function generateOne(number) {
var g = document.createElementNS(svgNS, "g");
var x = random(0, width);
var y = random(0, height);
var rad = random(minRadius, maxRadius);
var color = randomColor();
var html = replace(pattern, "number", number);
html = replace(html, "x", x);
html = replace(html, "y", y);
html = replace(html, "rad", rad);
html = replace(html, "color1", color);
g.innerHTML = html;
parent.appendChild(g);
}
}
function replace(str, patternText, value) {
var pattern = new RegExp("\\[" + patternText + "\\]", "g");
var rez = str.replace(pattern, value);
return rez;
}
function randomColor() {
return colors[random(0, colors.length - 1)];
}
function random(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
generateBack("svgBack");
@import url(https://fonts.googleapis.com/css?family=Open+Sans:800);
body {
font-family: Open Sans;
}
Ответ 2
Версия с анимацией с градиентными краями
var pattern = ``;
var svg = document.getElementById("svg1");
var width = svg.clientWidth;
var height = svg.clientHeight;
var sircleNumber = 200;
var minRadius = 15;
var maxRadius = 35;
var minSeconds = 17;
var maxSeconds = 40;
var zazor = 25;
var colors = ["red", "orange", "yellow", "blue", "green", "#ff00a1", "#ffcb00", "#ff8600"];
var svgNS = "http://www.w3.org/2000/svg";
function generateBack(id) {
var parent = document.getElementById(id);
parent.innerHTML = "";
for (var i = 1; i <= sircleNumber; i++) {
generateOne(i);
}
function generateOne(number) {
var g = document.createElementNS(svgNS, "g");
var rad = random(minRadius, maxRadius);
var color = randomColor();
var xx = new Array(3);
var yy = new Array(3);
for (var i = 0; i < 3; i++) {
xx[i] = random(-zazor, width + zazor);
yy[i] = random(-zazor, height + zazor);
}
var seconds = randomFloat(minSeconds, maxSeconds);
var html = replace(pattern, "number", number);
html = replace(html, "rad", rad);
html = replace(html, "color1", color);
for (var i = 0; i < 3; i++) {
var ii = i + 1;
html = replace(html, "x" + ii, xx[i]);
html = replace(html, "y" + ii, yy[i]);
}
html = replace(html, "seconds", seconds);
g.innerHTML = html;
parent.appendChild(g);
}
}
function replace(str, patternText, value) {
var pattern = new RegExp("\\[" + patternText + "\\]", "g");
var rez = str.replace(pattern, value);
return rez;
}
function randomColor() {
return colors[random(0, colors.length - 1)];
}
function random(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function randomFloat(min, max) {
return Math.random() * (max - min) + min;
}
generateBack("svgBack");
@import url(https://fonts.googleapis.com/css?family=Open+Sans:800);
body {
font-family: Open Sans;
}
Версия с анимацией с чёткими краями
var pattern = ``;
var svg = document.getElementById("svg1");
var width = svg.clientWidth;
var height = svg.clientHeight;
var sircleNumber = 180;
var minRadius = 15;
var maxRadius = 25;
var minSeconds = 17;
var maxSeconds = 40;
var zazor = 25;
var colors = ["red", "orange", "yellow", "blue", "green", "#ff00a1", "#ffcb00", "#ff8600"];
var svgNS = "http://www.w3.org/2000/svg";
function generateBack(id) {
var parent = document.getElementById(id);
parent.innerHTML = "";
for (var i = 1; i <= sircleNumber; i++) {
generateOne(i);
}
function generateOne(number) {
var g = document.createElementNS(svgNS, "g");
var rad = random(minRadius, maxRadius);
var color = randomColor();
var xx = new Array(3);
var yy = new Array(3);
for (var i = 0; i < 3; i++) {
xx[i] = random(-zazor, width + zazor);
yy[i] = random(-zazor, height + zazor);
}
var seconds = randomFloat(minSeconds, maxSeconds);
var html = replace(pattern, "number", number);
html = replace(html, "rad", rad);
html = replace(html, "color1", color);
for (var i = 0; i < 3; i++) {
var ii = i + 1;
html = replace(html, "x" + ii, xx[i]);
html = replace(html, "y" + ii, yy[i]);
}
html = replace(html, "seconds", seconds);
g.innerHTML = html;
parent.appendChild(g);
}
}
function replace(str, patternText, value) {
var pattern = new RegExp("\\[" + patternText + "\\]", "g");
var rez = str.replace(pattern, value);
return rez;
}
function randomColor() {
return colors[random(0, colors.length - 1)];
}
function random(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function randomFloat(min, max) {
return Math.random() * (max - min) + min;
}
generateBack("svgBack");
@import url(https://fonts.googleapis.com/css?family=Open+Sans:800);
body {
font-family: Open Sans;
}
Есть понятие "машинного слово", я читала об этом, но везде пишут по разному, на сколько
я понимаю одно машинное слово равно 2 байта, а двойное машинное слово 4 байта, но откуда
оно берётся, почему 2 байта а не один? То есть как я понимаю если процессор 32-битный
он может за один такт принять и обработать с оперативной памяти 32 бита информации,
а это 4 байта значит у него должно быть одно слово 4 байта ну а 64-х битный в 2 раза
больше и его слово будет больше?
Ответы
Ответ 1
Думаю, вся путаница возникла из-за того, что когда то разработчики Интела (я так
думаю) назвали два байта - словом (word) - тогда 16 битные процессоры были прорывом.
Соответственно, 4 байта - двойным словом. (DWORD, double word). Это сохранилось и в
многих языках программирования (в том числе в с/с++). Почему два байта - слово? да
видимо от того, что байт это как буква. А две буквы - это уже слово. (Хотя сейчас
прибегут филологи и скажут, что это скорее всего слог, чем слово). В википедии есть
хорошая фраза "* Для 32-битных процессоров x86: исторически машинным словом считается
16 бит, реально — 32 бита."
А вот словосочетание "двойное машинное слово" я никогда не слышал. Даже гугл мало
находит статей с таким сочетанием. А вот "двойное слово" или "машинное слово" - это
нормально.
То есть как я понимаю если процессор 32-битный он может за один такт принять и обработать
с оперативной памяти 32 бита информации
не все так просто. Далеко не факт, что он может даже принять 32 бита. Современные
процессоры сложные, имеют кеш. Имеют сложные комманды, которые за один раз (не так,
а именно раз) могут обработать до 16 байт памяти (всякие mmx и sse).
Обычно, машинным словом называют "разрядность процессора", так как разрядность обычно
показывает оптимальный размер данных внутри процессора (регистры 32битного процессора
32 битные и с 32битными данными в основном все команды и работают). В некоторых процессорах
слово было 60 бит:)
В любом случае рекомендую всегда всматриваться в контекст. Если о "слове" говорит
программист на С/С++ - это 16 бит, если программист на ассемблере под 32 битные ARM
процессоры - то скорее всего слово - это 32 бита.
Ответ 2
По определению, размер машинного слова равен разрядности регистров процессора и/или
разрядности шины данных. Т.е. для процессоров, поддерживающих систему команд x64 -
это 8 байт.
Но в Windows API, например, повсеместно используется тип WORD как синоним 2-х байт.
Я думаю, это пошло из 16-битных версий Windows - после появления 32-битных версий,
Microsoft было выгодней использовать неправильное название, чем использовать правильное
и получить большие проблемы совместимости при компиляции под Windows.
Вобщем, не заморачивайтесь сильно и смотрите на контекст :)
Есть ли информация о работе C# с коллекциями массивами и тд в плане реализации? Ну,
то есть поиск по сортированному массиву примерно в 2 раза быстрее, чем по несортированному.
А как происходит поиск в Array, List, Dictionary и тд?
Для примера вот задачка. Есть класс аэропорт(название, координаты). Есть класс коллекция
аэропортов. В коллекции есть List<аэропорт>, как хранилище объектов аэропорт. В коллекции
не может быть 2 аэропорта с одинаковым названием или с одинаковыми координатами. Что
быстрее, использовать list.Find(predicate) или добавить к классу коллекции два словаря:
Dictionary<строка, аэропорт>
Dictionary<координаты, аэропорт>
и искать совпадения по этим коллекциям? Если со словарями поиск будет быстрее, то
на сколько?
Ответы
Ответ 1
В .NET 4.5 и выше есть метод List(T).BinarySearch, который использует алгоритм двоичного
поиска. Двоичный поиск намного быстрее, чем простой итеративный поиск.
Вот пример:
var ints = new List { 1, 56, 112 };
ints.BinarySearch(1); // 0
ints.BinarySearch(56); // 1
ints.BinarySearch(112); // 2
ints.BinarySearch(0); // -1
ints.BinarySearch(10); // -2
ints.BinarySearch(100); // -3
ints.BinarySearch(200); // -4
Для List<аэропорт>, в принципе это тоже возможно, но надо создать IComparer для аэропортов.
Что быстрее, использовать list.Find(predicate) или добавить к классу коллекции
два словаря?
Для больших коллекцией быстрее использовать словари.
Согласно документации, временная сложность метода Dictionary.ContainsKey
приближается O(1). Временная сложность метода List.BinarySearch - O(log(n)), и временная
сложность метода List.Find - O(n).
Ответ 2
Описание поиска в отсортированном быстрее чем в неотсортированно обсуждается тут:
https://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-an-unsorted-array
Поиск в Array и List происходит простым циклом и сравниванием членов массива\списка.
Поиск в Dictionary происходит через хэш таблицу.
У List есть метод BinarySearch, он ищет бинарным поиском но только в отсортированном
массиве, то что массив отсортирован вы должны гарантировать самостоятельно.
Для примера с аэропортами лучше использовать Dictionary. Но в данном случае не совсем
понятно какого вида координаты если это числа с плавающей точной то там есть округление,
например (1.0 / 3.0) не равно (1.1 / 3.3)
Поиск в Listе требует итерирования всего списка.
Поиск в Dictionary не требует итерирования коллекции и работает в среднем за 1 операцию.
Ответ 3
Архитектура современного железа такова, что доступ к кэш-памяти во много раз быстрее,
чем к основной памяти. Поэтому в критически-важных по времени алгоритмах следует учитывать
эту разницу.
Существует особый класс алгоритмов и структур данных, осведомлённых о кэше. Смотрите
разделы Cache-Conscious Binary Search и Hot/Cold Data Splitting. Их применение на действительно
больших объёмах данных может быть существенно выгодней.
Ответ 4
В .NET поиск по неотсортированным структурам (те же List, Dictionary, T[]) происходит
простым перебором элементов в цикле (да, в Dictionary тоже происходит перебор ключей).
Для списка это выглядит примерно так:
// разумеется, реальный метод должен
// быть несколько сложнее за счет проверок
// аргументов и прочей логики
public T Find(Predicate pred)
{
for(int i = 0 ; i < data.Length; i++)
{
if(pred(data[i]))
{
return data[i];
}
}
return default(T);
}
для словарей это будет выглядеть похожим образом с тем отличием, что вместо метода
Find и предиката там используется индексатор с обращением по ключу, соответственно,
при поиске будет использоваться не вызов предиката, а сравнение хэш-кодов ключа и перебираемых
ключей. Скорее всего сравнение хэшкодов будет происходить быстрее, особенно если предикат
имеет какую-то сложную логику (не стоит также забывать, что лямбда-выражение предиката
разворачивается в анонимный класс, который также требует создание экземпляра и выделение
под него памяти). Но утверждать это на 146% я бы не стал. Если вам действительно важна
скорость поиска, то я бы на вашем месте вначале провел тесты для сравнения того и другого
вариантов. но, думаю, поиск по словарю будет все же побыстрее
Предположим, есть коллекция или массив значений. Как можно организовать "живой поиск"
в comboBox как в поисковой системе google? Спасибо.
Ответы
Ответ 1
Для этого Вам нужно сделать следующее:
var values = new AutoCompleteStringCollection();
values.AddRange(ВашаКоллекцияСтрок);
_comboBox.AutoCompleteCustomSource = values;
_comboBox.AutoCompleteMode = AutoCompleteMode.Suggest;
_comboBox.AutoCompleteSource = AutoCompleteSource.CustomSource;
Ответ 2
Как-то так:
comboBox1.DataSourse=_dt;
comboBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
comboBox1.AutoCompleteSource = AutoCompleteSource.ListItems;
Это нужно для фильтрования значений в списке в зависимости от вводимого значения.
Еще: http://www.cyberforum.ru/csharp-beginners/thread326296.html
Для внимательных:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace ListBoxSearch
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
///
/// Метод,позволяющий организовать поиск в listBox.
/// Автор: Umnick
///
/// ListBox в котором осуществляется поиск.
public void Search(ListBox listBox, List q)
{
//Объявление переменных и заполнение их для поиска
bool flag = false;
List c = new List(); //Колличество совпадений
List match = new List();//Список с совпадениями
int n = 0; //Сюда запишем наибольшее кол-во совпадений
double p = 0;
int count = 0;
int index = 0; //Сюда запишем индекс элемента в listbox,в котором найдено
наибольшее кол-во совпадений
List s = new List();//будем переписывать все items из listbox сюда
for (int i = 0; i < listBox1.Items.Count; i++) //цикл,в нем заполним наши списки
{
s.Add(listBox.Items[i].ToString());//заполняем items
c.Add(0);//заполняем наш счетчик нулями
}
//Основные циклы по работке с поиском
for (int i = 0; i < textBox1.Text.Length; i++)//Цикл,им проходимся по каждому
символу в строке поиска
{
for (int j = 0; j < s.Count; j++)//Цикл,проходимся по каждому item в
нашей коллекции,ранее мы переписали туда все items
{
for (int k = 0; k < s[j].Length; k++)//Цикл,посимвольно перебираем
значение каждого items и ищем совпадения.
{
if (s[j][k] == textBox1.Text[i] || char.ToLower(s[j][k]) == textBox1.Text[i]
|| char.ToUpper(s[j][k]) == textBox1.Text[i])//Проверка на совпадение.Если один из
символов items`а совпал с одним из символов строки поиска,увеличиваем наш счетчик
{
c[j] = ++c[j];//Увеличиваем счетчик,каждый индекс которого,соответствует
каждому items в listBox1.
count++;
flag = true;
}
}
if (c[j] > n)//Поиск найбольшего счетчика,тоесть items,в котором
было обнаружено наибольшее кол-во совпадений.
{
n = c[j];//...
index = j;//Записываем в переменную,индекс,как и говорилось ранее.
}
}
}
if (flag)
{
listBox.Items.Clear();
for (int i = 0; i < c.Count; i++)
{
p += c[i];
}
p /= c.Count;
for (int i = 0; i < s.Count; i++)
{
if (c[i] != 0)
{
listBox.Items.Add(s[i]);
}
}
}
if (!flag)
{
listBox.Items.Clear();
for (int i = 0; i < q.Count; i++)
{
listBox.Items.Add(q[i]);
}
}
flag = false;
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
Search(listBox1,items);//Вызываем наш метод,и начинаем поиск.
}
public List items = new List();
private void Form1_Load(object sender, EventArgs e)
{
for (int i = 0; i < listBox1.Items.Count; i++)
{
items.Add(listBox1.Items[i].ToString());
}
}
}
}
Ответ 3
На самом деле все решил попроще. Используется стандартный контрол, при изменении
значения выполняется следующий метод:
class Class_CleverSearch
{
public void Search(DataRow[] datarows, //Перечень наименований для отборки
int i_max, //Максимальное количество позиций в списке
ComboBox cb //ComboBox, значение которое набрано
) //Максимальное количество позиций в списке)
{
cb.Items.Clear();
int i_kol = 0;
foreach (DataRow dr in datarows)
{
cb.Items.Add(dr[0].ToString());
i_kol++;
if (i_kol > i_max)
{
break;
}
}
cb.DroppedDown = true; //Принудительно раскрываем ComboBox
cb.SelectionStart = cb.Text.Length;
}
}
Есть боевой сервер на нем висит софт, временами происходит утечка при обработке файла
и в итоге все виснет, я рассматриваю вариант как то отслеживать подвисший процесс и
прибивать его когда процесс переваливает например за какие то пределы. Есть ли какой
то инструмент для реализации этой функции?
Ответы
Ответ 1
ulimit -v - (см. help ulimit). Можно ограничить максимальный размер виртуальной памяти
процесса (в килобайтах) -- распространяется на потомков, но по каждому считается отдельно.
Так же можно ограничить суммарный размер записываемых файлов, процессорное время, количество
нитей.
Если программа слишком нагружает процессор, можно понизить её приоритетность (увеличив
номер уровня приоритета) командами nice или renice. То же самое, но в конкуренции
за доступ к диску меняет ionice (но тут немного сложнее и работает не всегда).
Если программа без особой причины постоянно нагружает процессор, можно ограничить
процент использования процессора с помощью cpulimit. Эта утилита так же может завершать
наблюдаемый процесс вместо притормаживания.
Ответ 2
ПОпробуйте monit https://mmonit.com/monit/
посмотрите https://mmonit.com/wiki/Monit/ConfigurationExamples#apcupsd как пример
Начиная с С++11, в стандарт были добавлены функции std::begin и std::end, которые
можно использовать вместо методов .begin() и .end().
Как и когда их надо использовать?
Ответы
Ответ 1
Наиболее известное свойство функций std::begin и std::end - это то, что они работают
с массивами:
int a[3] = {1, 2, 3};
int sum = std::accumulate(std::begin(a), std::end(a), 0);
Для контейнеров-классов, если тип контейнера известен, единственное преимущество
свободной функции std::begin() перед функцией-членом - это то, что код получается на
один символ короче:
std::vector v = {1, 2, 3};
std::reverse(begin(v), end(v));
std::reverse(v.begin(), v.end());
Здесь префикс std:: указывать не надо, т.к. работает поиск имен, зависящих от аргументов
(ADL): vector находится в пространстве имен std, по этому для него будет найдена функция
std::begin (из того же пространства имен).
В обобщенном коде надо использовать using std::begin;
Так как существует поиск имен (ADL), то нужные функции begin и end могут находиться
в другом пространстве имен (не std). Однако с массивами работают только функции из
std, по этому в шаблонных функциях надо явно добавлять std::begin и std::end в область
видимости:
template
int sum(Container& c) {
using std::begin; // Поиск имен также будет искать std::begin.
using std::end;
return std::accumulate(begin(c), end(c), 0);
}
Если сюда передать контейнер-класс, который находится в каком-либо пространстве имен,
то будут использованы функции begin() и end() из этого пространства имен.
Если же таких функций нет, или Container - это обычный массив, то будут вызваны функции
std::begin и std::end. Для контейнеров-классов будут вызваны методы .begin() и .end().
Это можно продемонстрировать следующим примером (Код полностью)
struct GlobalArray {
...
int* begin() { return arr; }
int* end() { return arr + N; }
};
namespace ns {
struct Array {...};
int* begin(Array& a);
int* end(Array& a);
}
int main() {
ns::Array ns_arr;
for (auto& x : ns_arr) x = 1;
// В sum() будут использованы ns::begin и ns::end
std::cout << sum(ns_arr) << '\n';
std::vector v = {1, 20, 300};
// Будут использованы std::begin и std::end, которые вызовут методы .begin() и .end()
std::cout << sum(v) << '\n';
int c_arr[] = {400, 50, 6};
// Будут использованы std::begin и std::end
std::cout << sum(c_arr) << '\n';
GlobalArray glob_arr;
for (auto& x : glob_arr) x = 1;
// Будут использованы std::begin и std::end, которые вызовут методы .begin() и .end()
std::cout << sum(glob_arr) << '\n';
}
Написал проект. Залил в Google Play, поймал первую ANR, но без файла деобфускации
не могу понять причину сбоя. Где его можно найти ?
Ответы
Ответ 1
Самостоятельно нашел ответ на свой вопрос.
Файл деобфускации проекта Android Studio располагается здесь:
\app\build\outputs\mapping\release\mapping.txt
Допустим, есть некий ./a.out (получен из Си), который выводит адрес, возвращаемый
первым же malloc (естественно, на пути к нему всегда выполняются одни и те же действия).
При каждом запуске мы видим разные числа
- это особенность Linux (м.б. и в других системах тоже реализовано) запускать программу
так, чтобы стек, куча и область для mapping-а файлов размещались по случайным адресам).
А вот если смотреть в gdb или запустить этот ./a.out в valgrind, то увидим один и
тот же адрес.
Вопрос, как запускать (без apt-get install valgrind) ./a.out, чтобы печатаемый адрес
всегда был тем же самым?
Ответы
Ответ 1
Как верно замечено в комментариях, то, с чем вы столкнулись, называется ASLR - рандомизация
адресного пространства. Технология поддерживается во всех современных ОС.
Но, помимо поддержки в ОС, бинарники так же должны быть скомпилированы специальным
образом. К примеру, у GCC есть опции для компиляции с поддержкой ASLR: -fPIC/-fpic
и -fPIE/-fpie.
В Linux, поддержу ASLR на уровне ОС можно отключить руками:
sudo bash -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
и включить назад:
sudo bash -c 'echo 2 > /proc/sys/kernel/randomize_va_space'
И наконец, в Linux есть утилита hardening-check, которая определяет, скомпилирован
бинарник с поддержкой рандомизации или нет.
UPD:
Оказывается, есть целых 5 типов рандомизаций:
Stack ASLR
Libs/mmap ASLR
Exec ASLR - вот этот тип задаётся флагами fPIE при компиляции
brk ASLR
VDSO ASLR
И в доках пишут, что для рандомизации malloc-ов используется brk ASLR.
Т.е. получается, что для каких-то рандомизаций нужно указывать специальные флаги
при компиляции программы, а какие-то работают по-умолчанию "из коробки". Так что, чтобы
полностью исключить всякие рандомизации, нужно отключать ASLR на уровне ОС / в рамках
сессии (setarch $(uname -m) -RL bash).
Я работаю с потоками в своем проекте. Среди прочих потоков, в двух разных классах
создается две HandlerThread таким вот образом:
HandlerThread thread = new HandlerThread("ServiceThread", Process.THREAD_PRIORITY_DEFAULT);
Дальше они стартуются: thread.start();, и я использую их луперы.
И только что я обнаружил, что они создаются с абсолютно одинаковыми именами (первый
параметр в конструкторе - имя).
Мой вопрос состоит в том, могут ли быть проблемы из за того, что у потоков одинаковые
имена? Являются ли имена какими-то идентификаторами для потоков?
Ответы
Ответ 1
Не будет проблем.
При создании нового HandlerThread вызывается конструктор:
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
Идем вглубь и видим
if (threadName == null) {
this.name = "Thread-" + id;
} else {
this.name = threadName
}
Что и есть практическим подтверждением написанного в Javadoc:
Каждый поток имеет имя в целях идентификации. Более чем один поток может иметь
такое же имя . Если имя не указано , когда создается поток, новое имя генерируется
для него.
Ответ 2
Нет, имена в thread не имеют никакого значения кроме удобства тестирования и логирования.
Уникальным идентификатором thread будет pid.
Ответ 3
Из оффдока
Every thread has a name for identification purposes. More than one thread may have
the same name. If a name is not specified when a thread is created, a new name is generated
for it.
По идее про проблемы ничего не сказано, значит это нормально