Общее·количество·просмотров·страницы

Java Dev Notes - разработка на Java (а также на JavaScript/Python/Flex и др), факты, события из АйТи

пятница, 8 мая 2009 г.

Простой пример использования JNI

JNI - Java Native Interface - технология, которая позволяет из программы на Java вызывать функции, написанные на других языках, и обратно: из native-функций вызывать методы Java.

Рассмотрим простейший пример, в котором из программы на Java вызывается функция на Си, печатающая в консоли "Hello, World!".

ШАГ 1. Создание Java-программы

Напишем класс, который содержит один native-метод, а в методе main происходит вызов этого метода.

A.java:


public class A {

public static native void displayHelloWorld();

static {
System.loadLibrary("hello");
}

public static void main(String[] args) {
displayHelloWorld();
}
}


Native-методы обозначаются ключевым словом native. В данном классе объявлен один native-метод: displayHelloWorld(). Он не принимает никаких аргументов, и также не возвращает значения. Этот метод находится в динамически загружаемой библиотеке "hello" (для Windows это будет hello.dll).

Загрузка библиотеки осуществляется вызовом метода System.loadLibrary. Не нужно передавать расширение файла этому методу. Для Windows это расширение предполагается dll по умолчанию. Вызов метода System.loadLibrary помещен в статический блок. Выполнение этого блока осуществляется при загрузке класса.

ШАГ 2. Компиляция

Откомпилируем файл A.java командой:

javac A.java

После этого следует создать заголовочный файл (файл с расширением ".h"), в котором содержится объявление вызываемой функции. Для генерации заголовочного файла следует использовать утилиту javah:

javah -jni A

В результате будет создан следующий заголовочный файл A.h:


/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class A */

#ifndef _Included_A
#define _Included_A
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: A
* Method: displayHelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_A_displayHelloWorld
(JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif


ШАГ 3. Создание и компиляция native-функции

Напишем следующий код на Си, файл A.c:


#include <jni.h>
#include "A.h"
#include <stdio.h>

JNIEXPORT void JNICALL
Java_A_displayHelloWorld(JNIEnv *env, jobject obj)
{
printf("Hello, world!\n");
return;
}


После этого откомилируем и создадим dll-библиотеку следующей командой:

cl -I"C:\Program Files\Java\jdk1.6.0_12\include"
-I"C:\Program Files\Java\jdk1.6.0_12\include\win32"
-I"C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\include"
-LD A.c
-Fehello.dll

Опция -I компилятора cl определяет директории с заголовочными файлами. Здесь определено три директории: две первые содержатся в JDK, а третья содержится в MS Visual Studio, и она содержит заголовочный файл stdio.h. Опция -LD говорит компилятору, что нужно создать DLL-библиотеку, а опция -Fe определяет имя этой библиотеки.

В результате должна быть создана библиотека hello.dll.

ШАГ 4. Выполнение

Команда на выполнение java-программы:

java A

В результате на консоль должно быть выведено сообщение
"Hello, world!".

среда, 6 мая 2009 г.

Создание водяных знаков на изображениях

Здесь речь пойдет о создании водяных знаков (watermarks) на изображениях. Водяные знаки (чаще всего это Copyright) часто размещают на фотографиях с целью предотвратить их незаконное копирование. Это достаточно распространенный прием, и он может быть проделан без привлечения дополнительных библиотек, средствами одного только JDK 1.6. Вот пример фотографии с надписью "Copyright (C) 2009":



Последовательность действия такая:

  1. Создаем объект класса javax.swing.ImageIcon, в который загружаем файл с изображением.
  2. Создаем java.awt.image.BufferedImage, размеры которого устанавливаем равными размеру загруженного изображения. Цветовая модель изображения устанавливается RGB. Пока что это пустое изображение. Нам надо разместить на нем картинку, а также надпись. Для рисования получаем объект класса java.awt.Graphics2D.
  3. Размещаем изображение, это делает следующая строка кода:
    g2d.drawImage(photo.getImage(), 0, 0, null);

  4. Затем устанавливаем настройки для надписи, и рисуем саму надпись.
  5. После чего освобождаем графические ресурсы, сохраняем получившийся файл на диск. Все.

Пример кода:

import java.io.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import java.awt.geom.Rectangle2D;

public class Watermark {

public static void main(String[] args) {
try {
final String filenameIn = args[0];
final String filenameOut = args[1];

ImageIcon image = new ImageIcon(filenameIn);
BufferedImage bufferedImage = new BufferedImage(
image.getIconWidth(),
image.getIconHeight(),
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = (Graphics2D) bufferedImage.getGraphics();
g2d.drawImage(image.getImage(), 0, 0, null);

AlphaComposite alpha =
AlphaComposite.getInstance(AlphaComposite.SRC_OVER,0.5f);
g2d.setComposite(alpha);

g2d.setColor(Color.white);
g2d.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

g2d.setFont(new Font("Arial", Font.BOLD, 30));
String copyright = "Copyright (C) 2009";

FontMetrics fontMetrics = g2d.getFontMetrics();
Rectangle2D rect = fontMetrics.getStringBounds(copyright, g2d);
g2d.drawString(copyright,
(image.getIconWidth() - (int) rect.getWidth()) / 2,
(image.getIconHeight() - (int) rect.getHeight()) / 2);

g2d.dispose();

OutputStream out = new PrintStream(filenameOut);
ImageIO.write(bufferedImage, "jpg", out);
out.close();
} catch (IOException ioe) {
System.err.println(ioe);
}
}
}

Выполняем следующей командой:

java Watermark 1.jpg 2.jpg

Изображение с водяными знаками будет записано в файл "2.jpg".

Для создания полноценной утилиты следует добавить задание самой надписи через параметр командной строки, а также обработку расширений файлов, в текущей версии жестко задан формат "jpg":

ImageIO.write(bufferedImage, "jpg", out);

вторник, 5 мая 2009 г.

Генерация уникальных идентификаторов

В Java есть способ генерации уникальных идентификаторов - UUID (Universally Unique Identifier). Как говорит Wikipedia:
Основное назначение UUIDs — это позволить распределенным системам уникально идентифицировать информацию без центра координации. Таким образом, любой может создать UUID и использовать его для идентификации чего-либо с приемлемым уровнем уверенности, что данный идентификатор непреднамеренно никогда не будет использован для чего-то еще. Информация, помеченная с помощью UUID, может, поэтому, позже быть помещена в общую базу данных, без необходимости разрешения конфликта имен.

Начиная с Java 5, был введен класс java.util.UUID, который содержит методы для генерации UUID.
Пример кода:

import java.util.UUID;

public class GenerateUUID {

public static final void main(String[] args) {
//generate random UUIDs
UUID id1 = UUID.randomUUID();
UUID id2 = UUID.randomUUID();
System.out.println(id1);
System.out.println(id2);
}
}

На печать выводится следующее:
deea44c7-a180-4898-9527-58db0ed34683
596befcd-fc85-487e-9dbf-9739240d0fc7

Ясно, что UUID в Java аналогичны майкрософтовскому GUID.

понедельник, 4 мая 2009 г.

Массивы в Java - Часть 3 - Копирование и клонирование - Сравнение производительности

В предыдущем сообщении было рассмотрено 4 способа копирования массивов:

  • используя метод System.arraycopy
  • используя метод clone
  • используя методы Arrays.copyOf или Arrays.copyOfRange
  • провести копирование вручную в цикле

Какой способ следует выбрать с точки зрения производительности? Для ответа на этот вопрос проведем небольшое тестирование.

Для тестирования нам понадобится таймер. Я использовал следующий класс таймера:

public class Stopwatch {

private long startTime = 0;
private long stopTime = 0;
private boolean running = false;

public void start() {
startTime = System.currentTimeMillis();
running = true;
}

public void stop() {
stopTime = System.currentTimeMillis();
running = false;
}

//elapsed time in milliseconds
public long getElapsedTime() {
long elapsed;
if (running) {
elapsed = (System.currentTimeMillis() - startTime);
} else {
elapsed = (stopTime - startTime);
}
return elapsed;
}

public String toString() {
String result = "" + getElapsedTime() + " ms";
return result;
}
}

Его использование простое: перед началом измеряемой операции вызываем метод start(), после завершения операции вызываем метод stop(). Время, прошедшее между вызовами этих двух методов и является временем, затраченным на выполнение измеряемой операции.

Теперь рассмотрим сам тест. Для сравнения производительности будем вызывать большое количество раз (например, миллион раз) операции копирования, реализованные четырьмя рассмотренными выше способами. После этого замерим время, затраченное на выполнение операции, и сможем сделать вывод, какой способ наиболее производительный. И, важный момент, для оценки будем использовать только производительность интерпретатора Java, т.е. отключим исполнение native code (это достигается опцией java -Xint). Итак, вот код тестового класса:

import java.util.*;

public class ArrayCopyTest {

static void useArraycopy(int[] a, int n) {
int[] copy = new int[a.length];
for (int i=0; i<n; i++)
System.arraycopy(a,0,copy,0,a.length );
}

static void useClone(int[] a, int n) {
int[] copy;
for (int i=0; i<n; i++)
copy = (int[])a.clone();
}

static void useCopyOf(int[] a, int n) {
int[] copy;
for (int i=0; i<n; i++)
copy = Arrays.copyOf(a,a.length);
}

static void useLoop(int[] a, int n) {
int[] copy = new int[a.length];
for (int i=0; i<n; i++)
for (int j=0; j<a.length; j++)
copy[j] = a[j];
}

public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
Stopwatch stopwatch = new Stopwatch();
int[] a = {1,2,3,4,5,6,7,8,9,10};

stopwatch.start();
useClone(a,n);
stopwatch.stop();
System.out.println("Using clone: " + stopwatch);

stopwatch.start();
useArraycopy(a,n);
stopwatch.stop();
System.out.println("Using System.arraycopy: "
+ stopwatch);

stopwatch.start();
useCopyOf(a,n);
stopwatch.stop();
System.out.println("Using Arrays.copyOf: "
+ stopwatch);

stopwatch.start();
useLoop(a,n);
stopwatch.stop();
System.out.println("Using for loop: "
+ stopwatch);
}
}

Выполнение данного кода производится следующей командой (задан миллион итераций):
java -Xint ArrayCopyTest 1000000

Результаты, полученные на моей машине, следующие:

Using clone: 360 ms
Using System.arraycopy: 203 ms
Using Arrays.copyOf: 812 ms
Using for loop: 735 ms

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

System.arraycopy
clone
loop
Arrays.copyOf

ВЫВОД: Копирование массивов с использованием System.arraycopy оказывается наиболее производительным методом.

Массивы в Java - Часть 2 - Копирование и клонирование

Существует несколько способов провести копирование массива:

  • используя метод System.arraycopy
  • используя метод clone
  • используя методы Arrays.copyOf или Arrays.copyOfRange
  • провести копирование вручную в цикле

Рассмотрим их по порядку.

КОПИРОВАНИЕ С ИСПОЛЬЗОВАНИЕМ System.arraycopy

Массивы копируются с помощью метода System.arraycopy().
Сигнатура метода следующая:

public static void arraycopy(Object src,
int srcPos,
Object dest,
int destPos,
int length)

Данный метод, после всех проверок, копирует length элементов массива src, начиная с позиции srcPos, в массив dest, начиная с позиции destPos. Его удобно использовать, когда нужно скопировать лишь часть массива. Пример использования:

String[] a = {"abc", "def", "ghs"};
String[] b = new String[5];
System.arraycopy(a,0,b,0,a.length);
for (int i=0; i<b.length; i++)
System.out.println(b[i]);

На печать будет выведено следующее:
abc
def
ghs
null
null


КОПИРОВАНИЕ С ИСПОЛЬЗОВАНИЕМ МЕТОДА clone

По умолчанию, все массивы являются объектами, т.е. поддерживают методы класса java.lang.Object.
Массивы также наследуют интерфейсы java.lang.Cloneable, java.io.Serializable. Для массивов переопределен метод clone(), который проводит поэлементное копирование. Пример использования:

String[] a = {"abc", "def", "ghs"};
String[] b = (String[])a.clone();
a[0] = "changed";
for (int i=0; i<b.length; i++)
System.out.println(b[i]);

На печать будет выведено следующее:
abc
def
ghs


КОПИРОВАНИЕ С ИСПОЛЬЗОВАНИЕМ Arrays.copyOf

Сигнатура метода Arrays.copyOf для чисел типа int имеет следующий вид:

public static int[] copyOf(int[] original,
int newLength)

Производится копирование массива original, возвращается массив длиной newLength. Если original.length < newLength, то оставшаяся часть массива заполняется нулями. Пример использования:

int[] a = {1,2,3};
int[] b = Arrays.copyOf(a,5);
for (int i=0; i<b.length; i++)
System.out.println(b[i]);

На печать будет выведено следующее:
1
2
3
0
0


КОПИРОВАНИЕ ВРУЧНУЮ в ЦИКЛЕ

Простенький пример:

int[] a = {1,2,3};
int[] b = new int[a.length];
for (int i=0; i<a.length; i++)
b[i] = a[i];

Массивы в Java - Часть 1 - Основы

Массивы являются самым эффективным средством хранения и произвольного доступа ссылок на объекты. Массив представляет собой простую линейную последовательность, благодаря чему время доступа к элементу массива постоянно (т.е. не зависит от размера массива) и равно О(1). За скорость приходится расплачиваться тем, что размер массива фиксируется во время создания и не может изменяться все время жизни массива. Коллекция java.util.ArrayList, часто используемая вместо массива, способна изменять свой размер во время выполнения, но она значительно уступает по эффективности массиву. Повторюсь и подчеркну, что реальным преимуществом массива перед коллекциями является его эффективность.

Размер массива записан в переменную length. Нумерация элементов массива начинается с нуля. Поэтому индекс последнего элемента равен length-1. И массивы, и коллекции защищены от выхода за границу диапазона. Если случается выход за границу массива, то генерируется исключение ArrayIndexOutOfBoundsException.
Его перехватывать нежелательно, так как это RuntimeException, и оно сигнализирует о том, что программа работает неверно.

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

int[] a = new int[5];
for (int i=0; i<a.length; i++)
System.out.println(a[i]);

В данном примере создается массив целых чисел, и его элементы выводятся на печать. При создании массива все его элементы инициализируются значением по умолчанию. Для примитивных числовых типов (int,long и т.д.) этим значением является 0 (ноль), для типа boolean значением по умолчанию является false, для ссылок значение по умолчанию - null.
Можно запомнить значения по умолчанию в следюущем виде: "0/false/null".

В следующем примере массив инициализируется сразу при создании:

int[] a = {
2,
5,
(int)Math.pow(4,8)
};

Инициализирующее выражение может быть любым, в том числе это может быть вызов метода.

Массив объектов содержит не сами объекты, а ссылки на них. По умолчанию ссылка инициализируется null-значением. См. следующий пример:

String[] a = new String[5];
for (int i=0; i<a.length; i++)
System.out.println(a[i]);
Как и следует ожидать, на печать будет выведен столбик из null-значений. Если попытаться обратиться к элементам массива сразу после создания, без инициализации, то будет выкинуто NullPointerException.

Другое дело, если после создания в массиве будут сохранены ссылки на объекты. См. пример:

String[] a = new String[5];
a[0] = "abc";
a[1] = "def";
a[2] = "ghi";
a[3] = "jkl";
a[4] = "mno";
for (int i=0; i<a.length; i++)
System.out.println(a[i]);

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

В Java возможно создание массивов нулевой длины. Например:

String[] a = new String[0];
System.out.println(a.length);

Иногда это бывает полезно.

Постоянные читатели