Author Archive

Удаление числа из строки

Иногда мы пользуемся трюком, сохраняя числа (ID из базы данных) в строке через запятую.
Например: «1024,15,8,0,55».
И теперь, допустим, какое-то число из этого списка не нужно, и его необходимо убрать из исходной строки. Как это реализовать?

Первый вариант:
1) разбить строку по запятой
2) отфильтровать удаляемое число
3) собрать строку заново

Второй вариант (допустим, удаляем число 55):
1) вырезать из строки подстроки вида: ,55,
2) удаляем подстроки вида 55, и ,55

Реализация 1-го варианта (Java):

String val = "55";
String collect = Arrays.stream(
        "55,1024,55,15,8,0,55".split(","))
        .filter(v -> !v.equals(val))
        .collect(Collectors.joining(","));
System.out.println(collect);//1024,15,8,0

Реализация 2-го варианта (Java):

int del = 55;
String result = "55,1024,55,15,8,0,55"
        .replaceAll("," + del + ",", ",")
        .replaceAll(",?" + del + ",?", "");
System.out.println(result);//1024,15,8,0

Некоторые вещи про закачку файлов

В любом web-проекте в определенное время возникает вопрос как загружать/скачивать файлы. В этой статье рассмотрим важные аспекты этих процедур:

  • Можно загружать файлы не только в виде байт, но и формате base64.
  • Файл загружается на веб-сервер и сохраняется как временный файл
  • При передаче файла на сервер используется кодирование multipart/form-data
  • Из временного файла можно получить оригинальное название файла, байты файла, тип содержимого(ContentType), размер.
  • На основе байтов можно рассчитать хэш. Например, алгоритмом MD5, SHA-1 или SHA-256.
  • Хэш файла может пригодиться при отдаче файла, чтобы проверить целостность.
  • При отдаче файла следует указать HTTP-заголовок Content-Disposition. Параметр inline сообщает браузеру, что файл можно открыть непосредственно в браузере, если конечно позволяет тип файла. Параметр attachment сообщает, что файл должен скачиваться. Также в Content-Disposition передается наименование файла, которое лучше кодировать с помощью URL Encoding.
  • Если в браузере вы хотите отобрать процесс загрузки файла, например, в процентах, то проще всего это сделать через javascript, который может отслеживать кол-во отправленных байтов.

Бинарный алгоритм вычисления НОД

Наибольшим общим делителем (НОД) для двух целых чисел называется наибольший из их общих делителей.
Например: для чисел 8 и 12 наибольший общий делитель равен 4.

Ранее мы уже рассматривали нахождение НОД с помощью Алгоритма Евклида:

long gcd(long a,long b){
	return b == 0 ? a : gcd(b,a % b);		
}

Сейчас рассмотрим Бинарный алгоритм Евклида, который быстрее обычного алгоритма Евклида.
Бинарный алгоритм основан на следующих свойствах НОД:

НОД(2m, 2n) = 2 НОД(m, n),
НОД(2m, 2n+1) = НОД(m, 2n+1),
НОД(-m, n) = НОД(m, n)

Теперь реализуем этот алгоритм на Java:

static int gcd(int m, int n){
    if(m == 0) return n;
    if(n == 0) return m;
    if(n == m) return n;
    if(m == 1) return 1;
    if(n == 1) return 1;
    boolean em = (m & 1) == 0;
    boolean en = (n & 1) == 0;
    if(em && en) return gcd(m >> 1, n >> 1) << 1;
    if(em) return gcd(m >> 1, n);
    if(en) return gcd(m, n >> 1);
    if(n > m) return gcd((n - m) >> 1, m);
    return gcd((m - n) >> 1, n);
}

Для ускорения деления на два используется сдвиг битов на позицию вправо(>> 1), для умножения на два используется сдвиг на позицию влево (<< 1). Проверка четности числа осуществляется проверкой последнего бита числа (m & 1), если выражение равно 0, то число четно, иначе нечетно.

Как из Java обратиться к сервису по протоколу HTTPS

Это юбилейная 100-я статья!!!

Уже не знаю в какой раз приходится обращаться к сервису по протоколу HTTPS, и каждый раз уходит время, чтобы воспроизвести шаги. Сегодня решил все-таки написать шпаргалку и больше не тратить время на такую проблему.

Для начала открываем сайт в браузере:

В адресной строке браузера рядом с текстом https:// есть иконка, нажав на которую появится возможность просмотреть / скачать / экспортировать сертификат.
Нужно экспортировать сертификат в файл test.crt

Файл будет иметь примерно такое содержание:

-----BEGIN CERTIFICATE-----
MIIEdjCCA16gAwIBAgIUGyZeQnd4LMFso5FQwrzjHmrNWVswDQYJKoZIhvcNAQEF
utCwINC/0L7QtNC70LjQvdC90L7RgdGC0Lgg0YHQtdGA0LLQtdGA0LAwMgYDVR0f
KqOTBEhH50jwo6WaQIUrD54ElD5gVO3VIT+eAMZm0HzXF+NKpkNaiR1b
-----END CERTIFICATE-----

Далее необходимо выполнить следующую команду:

sudo keytool -import -alias yourname -file test.crt
 -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit

-alias — произвольное уникальное название
-file — путь к файлу
-keystore — путь к хранилищу сертификатов Java
$JAVA_HOME — путь к домашней директории Java, которую вы используете для запуска вашего приложения
-storepass — пароль к хранилищу, по умолчанию changeit

Получение содержимого текстового файла (Spring Boot)

Вот уже несколько месяцев последние два проекта делаю с использованием Spring Boot.
Бывает так, что нужно интегрироваться с внешней системой, но эта система не готова по каким-то причинам, а нужно показать свой функционал, в этом случае пишем заглушку. Т.к. в моем случае внешний сервис давал данные в формате JSON, я решил положить пример ответа сервиса в ресурсы (resource) по пути files/cities.json.

Далее встал вопрос как его вытянуть. Погуглил, в итоге собрал такую функцию (сперва получаем контекст приложения, из которого получаем ресурс):

private static String getFileContent(String filePath) throws IOException {
    ApplicationContext appContext =
        new ClassPathXmlApplicationContext(new String[] {});
 
    Resource resource = appContext.getResource(filePath);
 
    StringBuilder sb = new StringBuilder();
    BufferedReader br = null;
    try{
        br = new BufferedReader(
            new InputStreamReader(resource.getInputStream(), "UTF-8"));
        String line;
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
    }finally {
        if(br != null) try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return sb.toString();
}

Пример вызова функции:

try {
    String text = getFileContent("files/cities.json");
} catch (IOException e) {
    e.printStackTrace();
}

Я использовал чтение файла для временной заглушки, но можно использовать и для других целей.
p.s. Обратите внимание, что в функции указана кодировка UTF-8. Можно её не указывать, тогда возьмется кодировка системы. Кодировку можно указать и при запуске приложения:

java -Dfile.encoding=UTF8

Совет: лучше в функции явно прописать UTF-8, если, конечно, она вам нужна.

Замер времени (Java)

Иногда возникает потребность измерить время работы определенного фрагмента кода в вашем проекте.
Можно, конечно, писать так:

long start = System.currentTimeMillis();
//тут замеряемый код
System.out.println(System.currentTimeMillis() - start);

Но такой подход быстро надоедает. Я выделил замер времени в отдельный класс TimeMeter, теперь можно удобно останавливать несколько раз таймер и измерять время, если нужно измерить промежуточные значения. Можно также использовать несколько экземпляров таймера.
Реализация:

public class TimeMeter {
    private long start;
	private long stop;
 
    public TimeMeter(){
        start = now();
    }
 
    public void stop(){
        stop = now();
    }
 
    @Override
    public String toString() {
        return getDuration() + " msec";
    }
 
    public long getDuration(){
        return (stop == 0 ? now() : stop) - start;
    }
 
    private long now(){
        return System.currentTimeMillis();
    }
}

Самый простой вариант использования:

TimeMeter timeMeter = new TimeMeter();
//тут замеряемый код
System.out.println(timeMeter);

Таймер с несколькими остановками и замерами времени:

TimeMeter timeMeter = new TimeMeter();
//тут замеряемый код 1
timeMeter.stop();
System.out.println(timeMeter);
//тут замеряемый код 2
timeMeter.stop();
System.out.println(timeMeter);

Глобальный и внутренние таймеры:

TimeMeter totalTimeMeter = new TimeMeter();
for (int i = 0; i < 100; i++) {
    TimeMeter timeMeter = new TimeMeter();
    //тут замеряемый код
    System.out.println("time " + i + ": " + timeMeter);
}
System.out.println("total time: " + totalTimeMeter);

p.s. Можете придумать и сделать свою версию, здесь простор для творчества.

$(…).size is not a function

Второй раз за последние месяцы прикручиваю тему Metronic и ловлю такую ошибку:

$(...).size is not a function

В первый раз я потратил достаточно времени, чтобы понять в чем причина, а во второй раз было дело техники.

Всё дело в том, что Metronic использует 1-ю версию jQuery (v1.12.4), а в проекте bower брал последную 3-ю версию jQuery (v3.1.0).
В 3-ей версии почему-то убрали эту функцию.

Все решается использованием 1-й версии jQuery. В bower.json я исправил версию так:

"jquery": "1.12.4"

Запустил платное приложение Bilemin Cards в Google Play

Наконец-то, 31 октября 2016 я запустил своё первое платное приложение в Google Play под названием «Bilemin Cards».
Вот ссылка на приложение: https://play.google.com/store/apps/details?id=kz.bilemin.cards. Стоимость приложения 3$, что составляет 990 тенге.
Вообще, я настроен скептически, кажется, я вообще не заработаю денег на этом приложении, но посмотрим
В этой статье я буду через определенные промежутки времени писать о всём, что касается приложения, и что я буду делать для продвижения приложения.

Ноябрь, 2016
В ноябре я начал рекламировать приложение через adwords. Кампания длилась неделю, бюджет был 1$/день.

Показы

24 894

Взаимодействия

437 кликов

Коэффициент взаимодействия

1,76 % CTR

Средн. цена

0,02 $ за клик

Стоимость

9,60 $

Итого за 7 дней взяли 9,60$. Почему не 7$ спросите вы? Показ рекламы не может сразу остановиться, поэтому каждый день уходило более 1$.

Вывод: Реклама не сработала, никто не купил приложение. Любой бюджет можно скликать 🙂

01.12.2016
Отправил приложение на конкурс award.kz в номинацию «Мобильные приложения».
Было более 350 просмотров и один лайк.
Предварительные результаты голосования:

p.s. пока 11-е место, даже не в первой десятке 🙂
p.p.s. организаторы запросили 7 промо-кодов для оценки приложения, я же им передал 10 кодов! При своей оценке они не активировали ни одного кода!! Вообще, до этого я сгенерил 500 промо-кодов, теперь думаю как их распространить.
13.12.2016
Запустил бесплатную версию приложения: https://play.google.com/store/apps/details?id=kz.bilemin.freecards
p.s. Не пощупав, никто ничего не купит, по крайней мере, наши люди 🙂
15.12.2016
Состоялась первая покупка приложения. Неожиданно, если честно 🙂
20.12.2016
Освежил группу вконтакте по новогоднему: https://vk.com/bilemin
Создал запись: https://vk.com/bilemin?w=wall-44243064_8 и начал её рекламу через VK:

25.12.2016
На прошлой неделе я запускал рекламу ВКонтакте. Сперва рекламировал запись в сообществе:

Статистика следующая:

В итоге показы почему-то упали.
Далее замутил схему с промо-кодами, но рекламу не приняли модераторы.
В итоге решил рекламировать приложения напрямую:

Тут уже надо было платить не за показы, а за переходы.

В итоге всего 8 человек на сегодняшний день установили бесплатное приложение. Короче, реклама в VK не дала результатов.

25.12.2016
Решил рекламировать своими силами. Взял базу пользователей сайта bilemin.kz, что примерно 1100 человек и начал делать рассылку.
03.01.2017
Год завершился с такими показателями:

Если с 1000 отправок писем целевой аудитории будет хотя бы одна установка, то можно отправлять миллионы писем.
Сегодня с mail.ru выудил 438 email’ов из Казахстана и сделал рассылку.
05.01.2017
Разослал еще 4000 писем. Статистика еще не обновилась, что-то Google Play запаздывает.
Наконец-то, обновилась статистика:

14.01.2017
Что-то застопорились скачивания. Прошло более недели, а прирост мизерный.

24.01.2017
Прошло 10 дней. Несколько дней назад вновь запускал рассылку по 4000 адресам (другая выборка) и ждал пока обновиться статистика.

p.s. Уныло ждем 50 установок 🙂

Подбор окончания в соответствии с количеством

Шлифуя очередную программу и вкладывая в нее души, хочется выводить не:
«1 карточек», «2 карточек» и «5 карточек», а «1 карточка», «2 карточки» и 5 «карточек». Т.е. к слову, связанному с целым количественным числительным хочется подобрать правильное окончание.

Можно заменить, что для целых количественных числительных максимально можно выделить три различных окончания.
Эти рассуждения привели меня к написанию следующей функции:

public static String pickPhrase(int count, String word0,String word1,String word2) {
    int rem = count % 100;
    if(rem < 11 || rem > 14){
        rem = count % 10;
        if(rem == 1) return word1;
        if(rem >= 2 && rem <= 4) return word2;
    } return word0;
}

Функция pickPhrase принимает количество (count) и три вида окончаний (word0, word1, word2).
Мысленно можно выделить следующие группы:
0 карточек, 5 карточек, 6 карточек и т.д.
1 карточка, 21 карточка, 31 карточка и т.д.
2 карточки, 3 карточки, 4 карточки и т.д.
Для удобства я взял окончания для 0, 1, 2, т.к. у них троих различные окончания.
Вам только остается подобрать окончания для 0, 1, 2, а функция по переданную количеству подберет требуемое окончание.
word0 — соответствует окончанию для 0-го количества;
word1 — соответствует окончанию с количеством в 1 штуку;
word2 — соответствует окончанию с количеством в 2 штуки.

Например:
Пусть дано кол-во карточек от 0 до 100 включительно и нужно к слову «карточка» подобрать окончание в соответствии с количеством.
Это делается так:

for (int i = 0; i <= 100; i++) {
    System.out.println(i + " карточ" + pickPhrase(i, "ек", "ка", "ки"));
}

Мы выделили неизменяемую часть слова «карточ» и далее нашей функцией подобрали окончание.

0 карточек
1 карточка
2 карточки
3 карточки
4 карточки
5 карточек
6 карточек
7 карточек
8 карточек
9 карточек
10 карточек
11 карточек
12 карточек
...
96 карточек
97 карточек
98 карточек
99 карточек
100 карточек

Чтобы не выделить неизменяемую часть слова и не допустить ошибку при этом, можно вместо окончаний использовать сами слова. Например:

for (int i = 0; i <= 100; i++) {
    System.out.println(i + " " + pickPhrase(i, "штук", "штука", "штуки"));
}
0 штук
1 штука
2 штуки
3 штуки
4 штуки
5 штук
6 штук
7 штук
8 штук
9 штук
10 штук
11 штук
12 штук
13 штук
...
90 штук
91 штука
92 штуки
93 штуки
94 штуки
95 штук
96 штук
97 штук
98 штук
99 штук
100 штук

Парсинг DN (Distinguished Names)

В ЭЦП данные о её владельце и изготовителе хранятся в виде Distinguished Names (DN).
Например:

CN=БЛОГОВ БЛОГ,SURNAME=БЛОГОВ,SERIALNUMBER=IIN123456789012,C=KZ,L=АСТАНА,S=АСТАНА,G=БЛОГОВИЧ,O=ТОО \"Рога\, копыта 24\\7\",OU=BIN000111222333

Атрибуты отделяются между собой запятыми, каждый атрибут представляет из себя ключ и значение, разделенные символом равно (=). Так же некоторые символы экранируются, например: \" \,
Предлагаю следующую функцию, которая принимает строку в формате DN и возвращается объект с соответствующими полями и значениями (я использовал angular’овский цикл, но можно заменить на обычный).

function parseDN(value){
    var parts = value
        .replace(/\\,/g,'<comma>')
        .replace(/,/g,'<delim>')
        .replace(/<comma>/g,',')
        .replace(/\\\\/g,'<backslash>')
        .replace(/\\/g,'')
        .replace(/<backslash>/g,'\\')
        .split('<delim>');
    var map = {};
    angular.forEach(parts, function(part){
        var split = part.split('=');
        map[split[0]] = split[1];
    });
    return map;
}

Пример использования:

    var map = parseDN("YOUR_DN_STRING");
    console.log(map.CN);
    console.log(map.SERIALNUMBER);
    console.log(map.O);