- grails-app - родительский каталог для исходников приложения
- conf - конфиги
- controllers - контроллеры - буковка "C" в аббревиатуре MVC.
- domain - доменные объекты. Т.е. здесь лежат энтити, по которым будет создаваться схема БД
- i18n - поддержка интернационализации (i18n).
- services - сервисы
- taglib - библиотеки тегов
- utils - Grails -специфичные утилиты
- views - Groovy Server Pages - вьюшки, аналог JSP, буковка "V" в аббревиатуре MVC
- scripts - Gant-скрипты.
- src - вспомогательные исходники
- groovy - исходники на Groovy
- java - исходники на Java
- test - юнит-тесты, интеграционные тесты
Общее·количество·просмотров·страницы
Java Dev Notes
Java Dev Notes - разработка на Java (а также на JavaScript/Python/Flex и др), факты, события из АйТи
четверг, 27 марта 2014 г.
Grails - Convention over Configuration
суббота, 15 марта 2014 г.
Семафоры в Java: реализация простого семафора
Семафор используется для обмена сигналами между потоками, или же для охраны критической секции. Их также можно использовать и вместо локов. Несмотря на то, что в JDK уже реализован семафор (java.util.concurrent.Semaphore), полезно будет самим реализовать этот объект.
Итак, у нашего семафора будет всего лишь два метода: take, release. Соответственно, простейшая реализация будет такой:
public class SimpleSemaphore { boolean taken = false; public synchronized void take() { this.taken = true; this.notify(); } public synchronized void release() throws InterruptedException { while (!this.taken) wait(); this.taken = false; } }
Теперь давайте рассмотрим программу, которая использует семафор для обмена сигналами. У нас будет два потока: SignalSender, SignalReceiver, которые будут посылать друг другу сигналы. Вот код:
public class Main { public static void main(String[] args) throws InterruptedException { SimpleSemaphore semaphore = new SimpleSemaphore(); new Thread(new SignalSender(semaphore)).start(); Thread.currentThread().sleep(2000); new Thread(new SignalReceiver(semaphore)).start(); } static class SignalSender implements Runnable { private final SimpleSemaphore semaphore; public SignalSender(SimpleSemaphore semaphore) { this.semaphore = semaphore; } @Override public void run() { System.out.println("[SignalSender] run"); while (true) { try { doSomeWork(); semaphore.take(); } catch (InterruptedException e) { e.printStackTrace(); } } } private void doSomeWork() throws InterruptedException { System.out.println("[SignalSender] do some work"); Thread.sleep(500); } } static class SignalReceiver implements Runnable { private final SimpleSemaphore semaphore; public SignalReceiver(SimpleSemaphore semaphore) { this.semaphore = semaphore; } @Override public void run() { System.out.println("[SignalReceiver] run"); while (true) { try { semaphore.release(); doSomeWork(); } catch (InterruptedException e) { e.printStackTrace(); } } } private void doSomeWork() throws InterruptedException { System.out.println("[SignalReceiver] do some work"); Thread.sleep(700); } } }
Посмотрим на вывод консоли:
[SignalSender] run [SignalSender] do some work [SignalSender] do some work [SignalSender] do some work [SignalSender] do some work [SignalSender] do some work [SignalReceiver] run [SignalReceiver] do some work [SignalSender] do some work [SignalReceiver] do some work [SignalSender] do some work [SignalReceiver] do some work [SignalSender] do some work [SignalSender] do some work [SignalReceiver] do some work [SignalSender] do some work [SignalReceiver] do some work [SignalSender] do some work [SignalSender] do some work [SignalReceiver] do some work [SignalSender] do some work [SignalReceiver] do some work [SignalSender] do some work [SignalReceiver] do some work [SignalSender] do some work [SignalSender] do some work [SignalReceiver] do some workМы видим, как потоки обмениваются сигналами, при этом они ждут друг друга, и начинают работу, только получив соответствующий сигнал от семафора.
пятница, 14 марта 2014 г.
Элементарная реализация BlockingQueue на Java
- поток пытается получить элементы из пустой очереди
- поток пытается положить элементы в полную очередь
Когда поток пытается получить элементы из пустой очереди, он ставится в ожидание до тех пор, пока какой-нибудь другой поток не положит элементы в очередь. Аналогично, когда поток пытается положить элементы в полную очередь, он ставится в ожидание до тех пор, пока какой-нибудь другой поток не возьмет элементы их очереди и таким образом не освободит место в ней. Естественно, введение понятия "полная очередь" подразумевает, что очередь имеет ограниченный размер, который обычно задается в конструкторе.
В Java есть интерфейс BlockingQueue, а также множество реализаций этого интерфейса:
- ArrayBlockingQueue
- DelayQueue
- LinkedBlockingDeque
- LinkedBlockingQueue
- LinkedTransferQueue
- PriorityBlockingQueue
- SynchronousQueue
Тем не менее, было бы полезно самим реализовать простейшую блокирующую очередь для понимания ее работы. Причем наша реализация (для начала) должна быть самой простой. Реализации в JDK намного более сложные, чем наша примитивная реализация, которая приведена ниже. Затем можно было бы детально разобрать реализации блокирующих очередей в Java. Впрочем, разбор реализаций в Java мы оставим когда-нибудь на потом, а сейчас займемся собственной простейшей реализацией блокирующей очереди.
public class BlockingQueue { private List queue = new LinkedList(); private int limit = 10; public BlockingQueue(int limit){ this.limit = limit; } public synchronized void put(Object item) throws InterruptedException { while (this.queue.size() == this.limit) { wait(); } if (this.queue.size() == 0) { notifyAll(); } this.queue.add(item); } public synchronized Object take() throws InterruptedException{ while (this.queue.size() == 0){ wait(); } if (this.queue.size() == this.limit){ notifyAll(); } return this.queue.remove(0); } }
Рассмотрим метод put:
public synchronized void put(Object item) throws InterruptedException { while (this.queue.size() == this.limit) { wait(); } if (this.queue.size() == 0) { notifyAll(); } this.queue.add(item); }Если очередь заполнена, то поток ставится в ожидание. Если же очередь пустая, то вызывается метод notifyAll, который пробуждает потоки, которые поставлены в ожидание при получении элементов из очереди. Аналогично работает метод take.
Обратите внимание, что в нашей простейшей реализации методы put, take являются synchronized в отличие от реализаций в JDK. Это сделано из двух соображений:
- простота
- желание обойтись только элементарными примитивами синхронизации в JDK, а именно методами wait, notifyAll
Давайте промоделируем работу очереди размеров в 5 элементов. Пусть у нас есть один поток (Producer), который, скажем, каждые 2 миллисекунды создает и кладет новый объект в очередь (пока что не подключаем поток, который забирает элементы из очереди). Очевидно, что через 10 миллисекунд, после того как очередь вся будет заполнена, при попытке положить шестой по счету элемент в очередь, этот поток будет поставлен в ожидание вот этим кусом кода из метода put:
while (this.queue.size() == this.limit) { wait(); }Теперь подключим второй поток, который будет забирать элементы из очереди (Consumer). Он определит, что очередь полная и пошлет notification всем ожидающим потокам. Это произойдет в следующем куске кода метода take:
if (this.queue.size() == this.limit){ notifyAll(); }После этого поток заберет первый элемент из списка, и вернет его в методе take, выйдя таким образом из этого (synchronized!!!) метода и освободив монитор, ассоциированный c очередью. Затем монитор захватит поток Producer, пробудившийся от ожидания, и продолжит класть элементы в очередь.
Теперь приведем полный код демо-примера:
import java.util.LinkedList; import java.util.List; public class BlockingQueue{ private List queue = new LinkedList (); private int limit = 10; public BlockingQueue(int limit){ this.limit = limit; } public synchronized void put(T item) throws InterruptedException { System.out.println("[BlockingQueue] try to put: " + item ); while (this.queue.size() == this.limit) { System.out.println("[BlockingQueue] queue is full, waiting until space is free"); wait(); } if (this.queue.size() == 0) { System.out.println("[BlockingQueue] queue is empty, notify"); notifyAll(); } this.queue.add(item); System.out.println("[BlockingQueue] put ok: " + item ); } public synchronized T take() throws InterruptedException{ System.out.println("[BlockingQueue] try to take"); while (this.queue.size() == 0){ System.out.println("[BlockingQueue] queue is empty, waiting until smth is put"); wait(); } if (this.queue.size() == this.limit){ System.out.println("[BlockingQueue] queue is full, notify"); notifyAll(); } T item = this.queue.remove(0); System.out.println("[BlockingQueue] take ok: " + item ); return item; } }
import java.util.Random; public class Main { public static void main(String[] args) throws InterruptedException { BlockingQueueВывод консоли будет такой (первая часть, где Producer кладет первые пять элементов в очередь):queue = new BlockingQueue (5); new Thread(new Producer(queue)).start(); Thread.currentThread().sleep(1000); new Thread(new Consumer(queue)).start(); } static class Producer implements Runnable { private final BlockingQueue queue; public Producer(BlockingQueue queue) { this.queue = queue; } @Override public void run() { System.out.println("[Producer] run"); while (true) { try { queue.put(produce()); Thread.currentThread().sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } private Integer produce() { Integer i = new Random().nextInt(100); System.out.println("[Producer] produce: " + i); return i; } } static class Consumer implements Runnable { private final BlockingQueue queue; public Consumer(BlockingQueue queue) { this.queue = queue; } @Override public void run() { System.out.println("[Consumer] run"); while (true) { try { consume(); Thread.currentThread().sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } } } private void consume() throws InterruptedException { Integer i = queue.take(); System.out.println("[Consumer] consumed: " + i); } } }
[Producer] run [Producer] produce: 86 [BlockingQueue] try to put: 86 [BlockingQueue] queue is empty, notify [BlockingQueue] put ok: 86 [Producer] produce: 73 [BlockingQueue] try to put: 73 [BlockingQueue] put ok: 73 [Producer] produce: 20 [BlockingQueue] try to put: 20 [BlockingQueue] put ok: 20 [Producer] produce: 84 [BlockingQueue] try to put: 84 [BlockingQueue] put ok: 84 [Producer] produce: 73 [BlockingQueue] try to put: 73 [BlockingQueue] put ok: 73 [Producer] produce: 67 [BlockingQueue] try to put: 67 [BlockingQueue] queue is full, waiting until space is freeЗатем вступает в работу Consumer:
[Consumer] run [BlockingQueue] try to take [BlockingQueue] queue is full, notify [BlockingQueue] take ok: 86 [Consumer] consumed: 86 [BlockingQueue] put ok: 67 [Producer] produce: 63 [BlockingQueue] try to put: 63 [BlockingQueue] queue is full, waiting until space is free [BlockingQueue] try to take [BlockingQueue] queue is full, notify [BlockingQueue] take ok: 73 [Consumer] consumed: 73 [BlockingQueue] put ok: 63 [Producer] produce: 90 [BlockingQueue] try to put: 90 [BlockingQueue] queue is full, waiting until space is free [BlockingQueue] try to take [BlockingQueue] queue is full, notify [BlockingQueue] take ok: 20 [Consumer] consumed: 20 [BlockingQueue] put ok: 90 [Producer] produce: 57 [BlockingQueue] try to put: 57 [BlockingQueue] queue is full, waiting until space is freeНа этом куске вывода хорошо видно, как Consumer и Producer попеременно используют очередь для обмена данными.
пятница, 7 марта 2014 г.
Демо-проект: Apache CXF, Jetty, MySQL
Начальная задача: сделать сервер, который позволяет читать состояние счета по его ID, а также добавлять/снимать средства со счета. Состояния счетов хранятся с БД. Сервер должен работать в нагруженное среде, поэтому нужно добавить кеширование. Транспортный протокол может быть любой. Я выбрал веб-сервисы, реализация веб-сервисов была сделана с помощью Apache CXF. После запуска сервера WSDL можно посмотреть по адресу: http://localhost:9000/accountService?wsdl
Вторая часть задачки: сделать клиента, который в несколько потоков читает/пишет значения счетов. Кол-во читателей/писателей, а также множество идентификаторов счетов задается параметрами клиента (например, из командной строки).
Третья часть задачки: сервер должен статистику работы скидывать в файл. Статистика должна быть такой: общее кол-во обработанных запросов, а также удельное кол-во запросов в ед. времени (т.е. rps - requests per second). Нужно предусмотреть возможность сброса статсы в ноль. Сброс статсы в ноль я сделал с помощью вызова HTTP-хендлера, который и обрабатывается Jetty. Сброс статсы осуществляется HTTP GET-ом урлы http://localhost:9001/zero. По урлу http://localhost:9001/getStats получаем текущее значение rps.
Несколько моментов про реализацию. Итак, рассмотрим реализацию AccountServiceImpl:
@WebService(endpointInterface = "net.iryndin.clientserverdemo1.commons.api.AccountService",serviceName = "AccountService") public class AccountServiceImpl implements AccountService { private MapВидно, что чтение и запись идет прямиком к кеш, т.е. скорость чтения и записи определяется скоростью работы кеша. Запись в базу запускается в отдельном потоке. Если бы она запускалась из этого же потока, то и производительность записи была бы совсем другой. На самом деле, такое решение, конечно, не всегда приемлемо, но поскольку это тестовый проект, я сделал именно так.cache = new ConcurrentHashMap<>(); @Override public Long getAmount(Integer id) { AtomicLong balance = cache.get(id); statsHelper.incrQueries(); if (balance == null) { return 0L; } else { return balance.get(); } } @Override public void addAmount(final Integer id, Long value) throws Exception { AtomicLong balance = cache.get(id); if (balance == null) { balance = new AtomicLong(0); cache.put(id, balance); } balance.addAndGet(value); final AtomicLong finalBalance = balance; executorService.submit(new Runnable() { @Override public void run() { try { accountDao.save(id, finalBalance.get()); } catch (SQLException e) { e.printStackTrace(); } } }); statsHelper.incrQueries(); } }
Теперь рассмотрим моменты реализации клиента. См. файл MainClient:
private static void runReaders(int count) { if (count <=0) { System.out.println("No readers run"); return; } ExecutorService executorService = Executors.newFixedThreadPool(count); System.out.println("Run readers: " + count); while (count > 0) { executorService.submit(new Runnable() { @Override public void run() { while (running) { accountService.getAmount(idsHelper.getRandomId()); } } }); count--; } } private static void runWriters(int count) { if (count <=0) { System.out.println("No writers run"); return; } ExecutorService executorService = Executors.newFixedThreadPool(count); System.out.println("Run writers: " + count); while (count > 0) { executorService.submit(new Runnable() { @Override public void run() { while (running) { boolean positive = new Random().nextBoolean(); int balance = new Random().nextInt(100000); balance = positive ? balance : -balance; try { accountService.addAmount(idsHelper.getRandomId(), (long) balance); } catch (Exception e) { // } } } }); count--; } }Мы запускаем потоки по числу rCount, wCount и крутимся там в бесконечном цикле (в бесконечном, так как переменную running в коде никто не меняет). Все этот создает неплохую нагрузку на сервер.
Последнее, рассмотрим скрипт на Python 3 (measure.py), который запускает клиент с различными значениями параметров rCount, wCount и измеряет производительность (просто считывает данные с урла http://localhost:9001/getStats):
import subprocess import time import urllib.request def main(): readers = [0, 20, 40, 60, 80, 100] writers = [0, 20, 40, 60, 80, 100] output = open('rps.txt','w', 1) for r in readers: for w in writers: run_client(r,w,output) print('Done reader=%d, writer=%d' % (r,w)) output.close() def run_client(readers_qty, writers_qty, output): if readers_qty==0 and writers_qty==0: return proc = subprocess.Popen(['java', '-jar', '-Xms2G', '-Xmx8G', 'client.jar', str(readers_qty), str(writers_qty)], shell=False) time.sleep(1) f = urllib.request.urlopen('http://localhost:9001/zero') time.sleep(30) f = urllib.request.urlopen('http://localhost:9001/getStats') s = f.read() rps = int(s) print('rps=%d' % rps) output.write('(%d,%d): %d\n' % (readers_qty,writers_qty,rps)) proc.terminate() time.sleep(5) if __name__ == "__main__": main()
В результате, мы получаем следующую табличку (по горизонтали: число wCount, по вертикали: rCount, значения в ячейках: requests-per-second):
rCount/wCount | 0 | 20 | 40 | 60 | 80 | 100 |
---|---|---|---|---|---|---|
0 | - | 12197 | 12319 | 12865 | 12648 | 13614 |
20 | 13324 | 13240 | 13049 | 12657 | 12499 | 12858 |
40 | 13198 | 12249 | 12676 | 13112 | 12882 | 12442 |
60 | 12420 | 12250 | 12348 | 12928 | 12534 | 13013 |
80 | 12531 | 12934 | 12304 | 12428 | 13133 | 12978 |
100 | 12972 | 13204 | 11869 | 11541 | 12781 | 12496 |
понедельник, 30 сентября 2013 г.
Создание базы, пользователя в MySQL, настройки доступа
mysql> CREATE DATABASE `mydb` CHARSET utf8 COLLATE utf8_general_ci; mysql> GRANT USAGE ON *.* TO 'webapp'@'localhost' IDENTIFIED BY 'SecretP@ssw0rd'; mysql> GRANT ALL PRIVILEGES ON `mydb`.* TO 'webapp'@'localhost'; mysql> flush privileges; mysql> SHOW GRANTS FOR webapp@localhost; +---------------------------------------------------------------------------------------------------------------+ | Grants for webapp@localhost | +---------------------------------------------------------------------------------------------------------------+ | GRANT USAGE ON *.* TO 'webapp'@'localhost' IDENTIFIED BY PASSWORD '*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19' | | GRANT ALL PRIVILEGES ON `mydb`.* TO 'webapp'@'localhost' | +---------------------------------------------------------------------------------------------------------------+ 2 rows in set (0.00 sec)
понедельник, 23 сентября 2013 г.
Управление проектами - правила Ашманова
JokerConf
- Скрипты в Java-приложениях
- Компромиссы, или Как проектируются языки программирования
- Разработка API в Java-проекте: как оказывать влияние на людей и не приобрести врагов
- Факты и заблуждения о Java-сериализации
- Invokedynamic: Роскошь или необходимость?
- Extreme Programming practices for your team
- Спорим, в твоем приложении есть утечка памяти?
- Understanding Java Garbage Collection and what you can do about it
- Как я создал desktop-приложение на Java, скачанное 9 000 000 раз
- The (not so) dark art of Performance Tuning
- О чём молчит профайлер
- О чём молчат Heap Dump-ы
- JDK8: Stream style
- Занимательные истории из жизни технической поддержки JVM
- Project Jigsaw. Take 2
- Unlocking the Java EE Platform with HTML5
- Spring 4.0: новое поколение
- В поисках Tommy Hilfiger
- Аварийный дамп — «черный ящик» упавшей JVM
- Java mapping для прагматичных программистов
Курсы по финансовой математике
- Coursera - Mathematical Methods for Quantitative Finance
- Coursera - Computational Investing, Part I
- CQI Global - Introduction to Quantitative Investment
- Coursera - Financial Engineering and Risk Management Part I
- Coursera - Financial Engineering and Risk Management Part II
- University of Toronto- Stochastic Methods for Actuarial Science
- University of Toronto- Pricing Theory I / Applied Probability for Mathematical Finance
- University of Toronto- High Frequency & Algorithmic trading
- MIT Open Courseware- http://ocw.mit.edu/courses/sloan-school-of-management/15-450-analytics-of-finance-fall-2010/
пятница, 31 августа 2012 г.
Разбивка PDF на страницы
import java.io.FileOutputStream; import com.itextpdf.text.Document; import com.itextpdf.text.pdf.PdfCopy; import com.itextpdf.text.pdf.PdfImportedPage; import com.itextpdf.text.pdf.PdfReader; /** * See * http://stackoverflow.com/questions/5736675/itext-split-a-pdf-into-several-pdf-1-per-page * * Pdf to image: * http://stackoverflow.com/questions/4886042/pdf-to-image-using-java * * Display PDF into flash:http://www.swftools.org/ * http://stackoverflow.com/questions/580807/how-can-i-show-doc-or-rtf-or-pdf-in-flash-player-or-in-none-editble-format * * Display PDF in html5 * https://github.com/mozilla/pdf.js * http://www.pdftron.com/pdfnet/webviewer/demo.html * * http://stackoverflow.com/questions/3113334/is-there-any-way-to-embed-a-pdf-file-into-an-html5-page * * pdf.js: Rendering PDF with HTML5 and JavaScript * http://andreasgal.com/2011/06/15/pdf-js/ * http://habrahabr.ru/post/122034/ * * google on * html5 view PDF * */ public class PdfIntoPagesMain { public static void main(String[] args) { try { //String inFile = args[0].toLowerCase(); String inFile = "book1/book1.pdf"; System.out.println ("Reading " + inFile); PdfReader reader = new PdfReader(inFile); int n = reader.getNumberOfPages(); System.out.println ("Number of pages : " + n); int i = 0; while ( i < n ) { String outFile = inFile.substring(0, inFile.indexOf(".pdf")) + "-" + String.format("%03d", i + 1) + ".pdf"; System.out.println ("Writing " + outFile); Document document = new Document(reader.getPageSizeWithRotation(1)); PdfCopy writer = new PdfCopy(document, new FileOutputStream(outFile)); document.open(); PdfImportedPage page = writer.getImportedPage(reader, ++i); writer.addPage(page); document.close(); writer.close(); } } catch (Exception e) { e.printStackTrace(); } /* example : java SplitPDFFile d:\temp\x\tx.pdf Reading d:\temp\x\tx.pdf Number of pages : 3 Writing d:\temp\x\tx-001.pdf Writing d:\temp\x\tx-002.pdf Writing d:\temp\x\tx-003.pdf */ } }
понедельник, 27 февраля 2012 г.
OpenMQ database persistence in Glassfish
imq.brokerid=imqbroker imq.persist.store=jdbc imq.persist.jdbc.dbVendor=mysql imq.persist.jdbc.mysql.property.url=jdbc:mysql://localhost:3306/mqdb imq.persist.jdbc.mysql.createdburl=jdbc:mysql://localhost:3306/mqdb imq.persist.jdbc.mysql.needpassword=true imq.persist.jdbc.mysql.user=USERNAME imq.persist.jdbc.mysql.password=PASSWORDТаким образом, мы настроили сохранение JMS-данных в БД. Нужно еще положить JAR-файл с драйвером БД (mysql-connector-java-5.1.17-bin.jar) в каталог GLASSFISH_HOME/mq/lib/ext. Теперь создадим БД. Для этого воспользуемся утилитой imqdbmgr, которая находится в каталоге GLASSFISH_HOME/mq/bin. Пишем команду:
./imqdbmgr create all -b imqbroker
и получаем следующие таблицы в БД:
MQBKR41Simqbroker MQCON41Simqbroker MQCONSTATE41Simqbroker MQCREC41Simqbroker MQDST41Simqbroker MQJMSBG41Simqbroker MQMSG41Simqbroker MQPROP41Simqbroker MQSES41Simqbroker MQTMLRJMSBG41Simqbroker MQTXN41Simqbroker MQVER41SimqbrokerТеперь можно запускать сам Глассфиш - сохранение JMS-данных пойдет в БД, а не в файлы. Когда создавалась БД, я столкнулся с одной проблемой. Дело в том, что у меня в MySQL кодировкой по умолчанию является UTF-8. Это проблема для OpenMQ, т.к. при создании таблицы MQTMLRJMSBG41Simqbroker появляются ошибки. Но эта проблема решается очень просто - для этого для данной Бд нужно объявить кодировку latin1: ALTER DATABASE mqdb DEFAULT CHARACTER SET latin1 COLLATE latin1_general_ci; Тогда все пройдет без ошибок. Полезные ссылки: OpenMQ, the Open source Message Queuing, for beginners and professionals (OpenMQ from A to Z) Browse OpenMQ source code Oracle GlassFish Message Queue 4.4.2 Technical Overview Oracle GlassFish Message Queue 4.4.2 Administration Guide
среда, 18 января 2012 г.
Доступ к Wikipedia на время действия экрана SOPA
var jq = document.createElement('script'); jq.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"; document.getElementsByTagName('head')[0].appendChild(jq); $('#mw-sopaOverlay').remove(); $('#content').show();Все элементарно: первые три строчки подключают jquery, 4-я строчка удаляет слой SOPA, 5-я строчка показывает слой с контентом. Можно этот скрипт повесить на Greasemonkey, и тогда автоматически вся википедия будет нормально показываться, как и прежде. И да, я поддерживаю их протест против SOPA. Просто иногда нужно срочно получить информацию, даже если она скрыта.
среда, 2 ноября 2011 г.
Скрипт для подсчета числа файлов java, xml и др.
import os startPath = os.getcwd() java = 0 xmlxsd = 0 def findFiles(path): global java global xmlxsd files = os.listdir(path) for f in files: p = os.path.join(path, f) if os.path.isdir(p): findFiles(p) else: fn, fe = os.path.splitext(p) print fe if fe == '.java': java = java+1 elif fe in ['.xsd', '.xml', '.html', '.xhtml']: xmlxsd = xmlxsd + 1 def main(): findFiles(startPath) print 'java = %d ' % java print 'xmlxsd = %d' % xmlxsd if __name__ == "__main__": main()
четверг, 20 октября 2011 г.
Кодировки в MySQL
[client] default-character-set=utf8 [mysqld] default-character-set=utf8That's all!
пятница, 14 октября 2011 г.
MapReduce и MongoDB
Я это делал на реальном примере по одному проекту - нужно обработать данные по пользовательским покупкам, которые хранятся в монге. Нас интересует два поля: ID пользователя и сумма покупки. В качестве ID пользователя выступает GUID.
Нужно сделать следующий отчет: показать пользователей с наибольшим количеством потраченных денег.
Если бы мы были в SQL, то табличка с данными о покупках была бы следующие:
CREATE TABLE sales( guid varchar(32) NOT NULL PRIMARY KEY, price float NOT NULL );Запрос, который выводит данный отчет, был бы следующий:
SELECT guid, sum(price) AS summa FROM sales GROUP BY guid ORDER BY summa DESC;В монге данные хранятся в коллекции stat, вид данных следующий:
> db.stat.findOne(); { "_id" : ObjectId("4e4c73eb41e5c790a7391848"), "guid" : "346fbb3968c9453d9dc8f8ebe0fa6763", "item" : "Apple MacBook Air MC968LL/A 11.6-Inch", "price" : 78.64, "retailerId" : "COST", "ip" : "0:0:0:0:0:0:0:1", "datetime" : "2011-07-28 10:12:38", "timezone" : "-1", "createDate" : "Thu Aug 18 2011 06:07:39 GMT+0400 (MSK)", }Что ж, приступим в мап-редьюсу...
function salesMap() { emit(this.guid, {price: this.price }); } function salesReduce(key, values) { var result = { count: 0, summa: 0.0 }; values.forEach(function(v){ if (v.price) { result.summa += v.price; result.count++; } }); return result; } db.userrep.drop(); db.stat.mapReduce(salesMap,salesReduce,{out:'userrep', verbose: true}); db.userrep.find();Функция emit(key, value), которую мы использовали в функции salesMap, подает на вход редьюсу пару ключ - значение. Ключом мы выбрали GUID, т.к. по нему идет группировка, а в значение кладем цену товара. Функция salesReduce получает на вход набор объектов, сгруппированных по ключу.
Внутри функции все тривиально:
1) создаем объект с результатами, пока что содержащий нулевые значения var result = { count: 0, summa: 0.0 };
2) пробегаемся по коллекции и заполняем результат
3) возвращаем результат.
4) PROFIT!!!
Вид результатов следующий:
> db.userrep.find().limit(5) { "_id" : "01dc299862094450ac232d384d883a5f", "value" : { "count" : 2, "summa" : 245.19 } } { "_id" : "0a1afef3b8b27942d9a8d02903ca2c28", "value" : { "count" : 1, "summa" : 30.43 } } { "_id" : "0c9d6a05458313b85706548c290991e9", "value" : { "count" : 0, "summa" : 0 } } { "_id" : "0f4e595202884408ae4c4c6306d764f9", "value" : { "count" : 1, "summa" : 88.43 } } { "_id" : "17a57229a9c14b2cb46337a3d196051c", "value" : { "count" : 1, "summa" : 49.54 } }Какая жалость - данные не отсортированы.
Монго не умеет сортировать коллекцию данным из составных объектов, т.е. мы не можем написать что-то вроде:
db.userrep.find().sort({value.summa: -1});Поэтому придется немножко извратиться:
db.userrep.find().forEach(function(v) { var s = v.value.summa; var c = v.value.count; var id = v._id; db.userrep.update({_id: id},{$set:{summa:s, count: c}}, true, true); });Мы просто скопировали поля summa, count из объекта value в объект-контейнер коллекции. Теперь данные выглядят так:
> db.userrep.find().limit(5); { "_id" : "01dc299862094450ac232d384d883a5f", "count" : 2, "summa" : 245.19, "value" : { "count" : 2, "summa" : 245.19 } } { "_id" : "0a1afef3b8b27942d9a8d02903ca2c28", "count" : 1, "summa" : 30.43, "value" : { "count" : 1, "summa" : 30.43 } } { "_id" : "0c9d6a05458313b85706548c290991e9", "count" : 0, "summa" : 0, "value" : { "count" : 0, "summa" : 0 } } { "_id" : "0f4e595202884408ae4c4c6306d764f9", "count" : 1, "summa" : 88.43, "value" : { "count" : 1, "summa" : 88.43 } } { "_id" : "17a57229a9c14b2cb46337a3d196051c", "count" : 1, "summa" : 49.54, "value" : { "count" : 1, "summa" : 49.54 } }Теперь, наконец, можно получить отсортированные данные:
> db.userrep.find().sort({summa:-1}).limit(5); { "_id" : "cb1332aed05d48fd9c51d0c5be584156", "count" : 4, "summa" : 496.72999999999996, "value" : { "count" : 4, "summa" : 496.72999999999996 } } { "_id" : "ade1efffe9d3fb116f33c320bf9b447e", "count" : 3, "summa" : 412.45000000000005, "value" : { "count" : 3, "summa" : 412.45000000000005 } } { "_id" : "ef98c2c430c4486394bc2b85ec5b1a77", "count" : 3, "summa" : 408.5, "value" : { "count" : 3, "summa" : 408.5 } } { "_id" : "5295f9b927c59dacdd3fb20d8173a19b", "count" : 4, "summa" : 367.08, "value" : { "count" : 4, "summa" : 367.08 } } { "_id" : "01dc299862094450ac232d384d883a5f", "count" : 2, "summa" : 245.19, "value" : { "count" : 2, "summa" : 245.19 } }
четверг, 1 сентября 2011 г.
Установка MongoDB на RHEL
sudo touch /etc/yum.repos.d/10gen.repoи в файл добавляем следующее:
[10gen] name=10gen Repository baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64 gpgcheck=02) Устанавливаем
sudo yum install mongo-10gen mongo-10gen-server3) Редактируем конфиг:
sudo vi /etc/mongod.confНапример, можем отредактировать порт, расположение логов и файлов с данными:
logpath=/var/log/mongo/mongod.log port=27017 dbpath=/var/lib/mongo4) Запускаем!
sudo /etc/init.d/mongod start
среда, 31 августа 2011 г.
java.text.SimpleDateFormat и потоки
Например, вот такой код:
import java.text.*;
class MiscUtils {
public final static DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
public static String formatDate(Date date) {
return fmt.format(date);
}
}
//
// usage:
//
void anyMethod() {
Date date = getDate();
String s = MiscUtils.formatDate(date);
// now we have formatted date here!
}
может вызвать проблемы, если метод MiscUtils.formatDate будет одновременно вызван из нескольких потоков.
Вот выдержка со страницы Java Docs SimpleDateFormat:
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
Решение 1.
Всякий раз, когда нужно отформатировать (или пропарсить) дату, создавать новый экземпляр java.text.SimpleDateFormat
Решение 2.
Создать свою, потокобезопасную реализацию форматтера
public class ThreadSafeSimpleDateFormat {
private DateFormat df;
public ThreadSafeSimpleDateFormat(String format) {
this.df = new SimpleDateFormat(format);
}
public synchronized String format(Date date) {
return df.format(date);
}
public synchronized Date parse(String string) throws ParseException {
return df.parse(string);
}
}
Решение 3.
Заюзать какую-нибудь стороннюю бибилиотечку наподобие JodaTime или класс FastDateFormat из Apache Commons.
пятница, 19 августа 2011 г.
Эффективное копирование файлов в NIO
// Getting file channels
FileChannel in = new FileInputStream(source).getChannel();
FileChannel out = new FileOutputStream(target).getChannel();
// JavaVM does its best to do this as native I/O operations.
in.transferTo (0, in.size(), out);
// Closing file channels will close corresponding stream objects as well.
out.close();
in.close();
четверг, 11 августа 2011 г.
Установка Postgres-9.0 на Ubuntu 10.10
1) В репах по-умолчанию нет пакета Postgres-9.0, поэтому добавим репозиторий, откуда будем качать пакеты:
deb http://ppa.launchpad.net/pitti/postgresql/ubuntu maverick main
deb-src http://ppa.launchpad.net/pitti/postgresql/ubuntu maverick main
добавляем в файл /etc/apt/sources.list. В итоге должны получить что-то вроде
$ cat /etc/apt/sources.list
deb http://ru.archive.ubuntu.com/ubuntu/ maverick main restricted
deb http://ru.archive.ubuntu.com/ubuntu/ maverick multiverse
deb http://archive.canonical.com/ubuntu maverick partner
deb http://archive.canonical.com/ maverick partner
deb http://ru.archive.ubuntu.com/ubuntu/ maverick-updates restricted main multiverse universe
deb http://security.ubuntu.com/ubuntu/ maverick-security restricted main multiverse universe
deb http://extras.ubuntu.com/ubuntu maverick main #Third party developers repository
# for postgres 9.0
deb http://ppa.launchpad.net/pitti/postgresql/ubuntu maverick main
deb-src http://ppa.launchpad.net/pitti/postgresql/ubuntu maverick main
:~$
2) Идем сюда: https://launchpad.net/~pitti/+archive/postgresql, смотрим значение ключа (сейчас это 8683D8A2) и добавляем ключ:
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 8683D8A2
3) Апдейтим репы:
sudo apt-get update
4) Останавливаем сервис Postgres 8:
~$ sudo service postgresql-8.4 stop
5) Пуржим пакеты:
sudo apt-get purge postgresql*
6) Устанавливаем postgres 9.0
sudo apt-get install postgresql-9.0
7) Заходим в psql:
sudo -u postgres psql
Ссылки:
Install PostgreSQL 9 on Ubuntu Linux
Postgres 9.x installation in Ubuntu
Установка Postgresql 9, pgAdmin III в Ubuntu 10.04
Installing PostgreSQL 9.0 on Ubuntu 10.04
Как узнать, какую версию Ubuntu вы используете
user1:~$ cat /etc/issue
Ubuntu 10.10 \n \l
user1:~$
или через GUI: System -> Administration -> System Monitor
или:
user1:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 10.10
Release: 10.10
Codename: maverick
Установка пароля админа в Glassfish 3
- Запустить Glassfish - GF_HOME/glassfish/bin/startserv
- Вызвать скрипт asadmn: GF_HOME/bin/asadmin change-admin-password
~/dev/glassfishv3/bin$ ./asadmin change-admin-password
Enter admin user name [default: admin]> admin
Enter admin password>
Enter new admin password>
Enter new admin password again>
Command change-admin-password executed successfully.
~/dev/glassfishv3/bin$