GilgaLab

19Sep/090

Radix Sort

Estive lendo sobre alguns algoritmos de organização e me deparei com o Radix Sort.
Pela definição da NIST:


Definition: A multiple pass distribution sort algorithm that distributes each item to a bucket according to part of the item's key beginning with the least significant part of the key. After each pass, items are collected from the buckets, keeping the items in order, then redistributed according to the next most significant part of the key.

Tradução livre:
Definição: Um algorítmo de organização de vários passos que distribui cada item para um bucket de acordo com parte da chave do item, começando pela parte menos significativa da chave. Após cada passo, os items são recolhidos dos buckets, mantendo os itens em ordem e então redistribuindo de acordo com a próxima parte mais significativa da chave.

Pelas leituras que fiz por aí, um método que me pareceu bastante interessante é aplicar esse algorítmo a cada bit da nossa informação. É claro que isso vai depender da aplicação do algorítmo. No caso que irei trabalhar aqui, estaremos ordenando um vetor com números inteiros positivos.

Vamos supor o seguinte array:

int vetor = {12,22,13,16,31}

Descrevendo cada um desses valores em binário:
12 - 01100
22 - 10110
13 - 01101
16 - 10000
31 - 11111

Pela descrição do algorítmo e pelo método adotado, devemos começar organizando nossa informação pelo bit menos significativo de cada um dos números em nosso array, ou seja, pegamos o bit mais a direita do array e distribuímos os valores em 2 buckets. O bucket onde o bit que estamos olhando é 0 ou ou bucket onde o bit que estamos olhando é 1. Ilustrando isso, temos:

01100 - 12
10110 - 22
01101 - 13
10000 - 16
11111 - 31

Começamos olhando para os bits que estão em negrito. Quando encontrarmos um bit 0, jogamos aquele número para o bucket0. Quando encontrarmos um bit 1, jogamos aquele número para o bucket1. Nossos buckets estão dessa forma agora então:

bucket0 = {12, 22, 16}
bucket1 = {13, 31}

Agora nós devolvemos os valores para dentro do nosso vetor na ordem em que se encontram dentro dos bukets. Primeiro colocamos dentro do vetor todos os valores do bucket0 e depois todos os valores do bucket1. Nosso vetor ficará da seguinte maneira:

vetor = {12, 22, 16, 13, 31}

Agora nós repetimos o processo para o nosso novo vetor, utilizando o próximo bit mais significativo.

01100 - 12
10110 - 22
10000 - 16
01101 - 13
11111 - 31

Nossos buckets ficam:

bucket0 = {12, 16, 13}
bucket1 = {22, 31}

Nosso novo vetor:

vetor = {12, 16, 13, 22, 31}

Repetimos o processo para o próximo bit mais significativo em nosso novo vetor:

01100 - 12
10000 - 16
01101 - 13
11010 - 22
11111 - 31

Nossos buckets ficam:

bucket0 = {16, 22}
bucket1 = {12, 13, 31}

Nosso novo vetor:

vetor = {16, 22, 12, 13, 31}

Repetimos o processo para o próximo bit mais significativo em nosso novo vetor:

10000 - 16
11010 - 22
01100 - 12
01101 - 13
11111 - 31

Nossos buckets ficam:

bucket0 = {16}
bucket1 = {22, 12, 13, 31}

Nosso novo vetor:

vetor = {16,22,12,13,31}

Até então não parece nada arrumado não é mesmo? Mas o útimo passo vai nos revelar algo fantástico! :D

Repetimos o processo para o próximo bit mais significativo em nosso novo vetor:

10000 - 16
11010 - 22
01100 - 12
01101 - 13
11111 - 31

Nossos buckets ficam:

bucket0 = {12, 13}
bucket1 = {16, 22, 31}

Nosso novo (e último) vetor:

vetor = {12, 13, 16, 22, 31}

Aha! Fantástico!

O mais interessante disso tudo é a velocidade desse algorítmo. Em alguns testes que fiz aqui, consegui organizar 20 milhoes de números em apenas 9,3 segundos!
Irei demonstrar a seguir uma versão um pouco mais lenta do algorítmo que desenvolvi porém de mais fácil entendimento. Logo após irei demonstrar a versão do mesmo algorítmo que foi capaz de organizar 100 milhões de números em 15,56 segundos.

O algorítmo 'simples'

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
typedef struct lista {
    int num;
    struct lista *prox;
} lista;
 
void radix_sort(int *, int);
void insere(lista **, int);
int pop(lista **);
void imprime(int *vetor, int tamanho);
 
int main()
{
    int tamanho = 20;
    int vetor[tamanho];
    int i;
 
    srand(time(NULL));
    for(i = 0; i < tamanho; i++) {
        vetor[i] = rand() % tamanho;
    }
 
    imprime(vetor, tamanho);
    printf("\n");
 
    radix_sort(vetor, tamanho);
 
    imprime(vetor, tamanho);
    printf("\n");
 
    return 0;
}
 
void insere(lista **l, int valor)
{
    lista *aux = *l;
 
    if(*l == NULL) {
        *l = (lista *)malloc(sizeof(lista));
        (*l)->num = valor;
        (*l)->prox = NULL;
    } else {
        while(aux->prox != NULL) aux = aux->prox;
        aux->prox = (lista *)malloc(sizeof(lista));
        aux = aux->prox;
        aux->num = valor;
        aux->prox = NULL;
    }
}
 
int pop(lista **l)
{
    int ret;
    lista *aux;
 
    ret = (*l)->num;
    aux = *l;
    *l = (*l)->prox;
    free(aux);
 
    return ret;
}
 
void radix_sort(int *vetor, int tamanho)
{
 
    lista *bucket0 = NULL;
    lista *bucket1 = NULL;
    int i, j;
    int numero_bits;
 
    numero_bits = sizeof(int) * 8;
 
    for(i = 0; i < numero_bits; i++) {
        for(j = 0; j < tamanho; j++) {
            if((((vetor[j] >> i) & 0xf) % 2) == 0)
                insere(&bucket0, vetor[j]);
            else
                insere(&bucket1, vetor[j]);
        }
 
        j = 0;
        while(bucket0 != NULL) {
            vetor[j] = pop(&bucket0);
            j++;
        }
 
 
        while(bucket1 != NULL) {
            vetor[j] = pop(&bucket1);
            j++;
        }
 
    }
 
}
 
void imprime(int *vetor, int tamanho)
{
    int i;
 
    for(i = 0; i < tamanho; i++)
        printf("%d ", vetor[i]);
}

[radix.c]

Essa é a versão 'simples' do algorítmo. Ele utiliza listas para ficar mais simples a inserção e remoção dos registros de nossos buckets. O problema com isso é que conforme aumentamos a quantidade de elementos que queremos organizar o programa fica mais lento. A verdadeira função de ordenação aqui é a função 'radix_sort'. As outras funções fazem parte da implementação da lista.
Vamos então dar uma olhada no programa.

...
    int tamanho = 20;
    int vetor[tamanho];
    int i;
 
    srand(time(NULL));
    for(i = 0; i < tamanho; i++) {
        vetor[i] = rand() % tamanho;
    }
...

Simplesmente declaramos um array com 20 posições e colocamos números randomicos nele que vão de 0 a 19. Esse é o vetor que desejamos ordenar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void radix_sort(int *vetor, int tamanho)
{
 
    lista *bucket0 = NULL;
    lista *bucket1 = NULL;
    int i, j;
    int numero_bits;
 
    numero_bits = sizeof(int) * 8;
 
    for(i = 0; i < numero_bits; i++) { // Primeiro loop
        for(j = 0; j < tamanho; j++) { // Segundo loop
            if((((vetor[j] >> i) & 0xf) % 2) == 0)
                insere(&bucket0, vetor[j]);
            else
                insere(&bucket1, vetor[j]);
        }
 
        j = 0;
        while(bucket0 != NULL) {
            vetor[j] = pop(&bucket0);
            j++;
        }
 
 
        while(bucket1 != NULL) {
            vetor[j] = pop(&bucket1);
            j++;
        }
    }
}

Como na explicação sobre o algorítmo, estou usando aqui 2 buckets e o vetor com os dados. O bucket0 recebe os números cujo bit atual sendo verificado é 0, e o bucket 1 recebe os números cujo bit atual sendo verificado é 1.
A variável 'numero_bits' se refere a quantos bits nós iremos verificar para realizar a ordenação. No caso como estamos organizando números inteiros, em teoria (mais tarde discutirei uma maneira melhor de determinar esse número) faz sentido percorremos todos os bits do mesmo. sizeof(int) retorna o número de bytes de um int, se multiplicamos esse valor por 8 temos o número de bits.
O primeiro loop se refere a qual bit estamos olhando e o segundo loop passa por cada elemento para determinar se o bit é 0 ou 1 e jogar o número em seu respectivo bucket.
Acredito que a linha que mais chama a atenção aqui é a linha 13. Vamos a explicação então! Imagine que estou trabalhando com um número de 4 bits apenas que se encontra em vetor[0]. O que essa linha faz é pegar o número inteiro e fazer uma lógica AND com o valor 0xf (eu poderia ter usado 0x1). Suponhamos que o numero no nosso vetor é 4. Então a lógica fica assim:

Deslocar o valor dentro de vetor[0] para a direita em 0 bits (primeira interação).
Em hexa: 0x4 & 0xf
Em binario: 0100 & 1111
Resultado: 0100

Deslocar o valor dentro de vetor[0] para a direita em 1 bit (segunda interação).
Em hexa: 0x2 & 0xf
Em binario: 0010 & 1111
Resultado: 0010

Deslocar o valor dentro de vetor[0] para a direita em 2 bits (terceira interação).
Em hexa: 0x1 & 0xf
Em binario: 0001 & 1111
Resultado: 0001

Deslocar o valor dentro de vetor[0] para a direita em 3 bits (quarta interação).
Em hexa: 0x0 & 0xf
Em binario: 0000 & 1111
Resultado: 0000

O que nos interessa aqui é o bit menos significativo, ou seja, o bit mais a direita. Quando realizamos essa lógica AND, o último bit será 0 ou 1. Se o último bit for 0, temos um número par e então o número deve ser colocado no bucket0. Se o último bit for 1, temos um número ímpar e então o número deve ser colocado no bucket1. O deslocamento que fazemos para a direita é apenas para colocar o bit que desejamos verificar na posição menos significativa do número para que possamos saber se ele deve ir ao bucket0 ou ao bucket1.
A lógica que segue é apenas referente a 'remontar' o vetor com todos os valores que estão no bucket0 e depois todos os valores que estão no bucket1.

O algorítmo um pouco menos simples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
 
void radix_sort(int *, int, int);
void imprime(int *vetor, int tamanho);
 
int main()
{
    int tamanho = 20;
    int *vetor;
    int i;
    int maior = 0;
    clock_t inicio, fim;
    float periodo;
 
    vetor = (int *)malloc(sizeof(int)*tamanho);
 
    srand(time(NULL));
    for(i = 0; i < tamanho; i++) {
        vetor[i] = (rand() % tamanho)+1;
        if(vetor[i] > maior)
            maior = vetor[i];
    }
 
    imprime(vetor, tamanho);
    printf("\n");
 
    inicio = clock();
    radix_sort(vetor, tamanho, maior);
    fim = clock();
    periodo = (float)(((float)fim-(float)inicio)/CLOCKS_PER_SEC);
 
    printf("Tempo: %.15f\n", periodo);
    imprime(vetor, tamanho);
    printf("\n");
 
    return 0;
}
 
void radix_sort(int *vetor, int tamanho, int maior)
{
 
    int i, j, k, l;
    int numero_bits;
 
    int **temp;
 
    temp = (int **)malloc(sizeof(int *)*2);
    temp[0] = (int *)malloc(sizeof(int)*tamanho);
    temp[1] = (int *)malloc(sizeof(int)*tamanho);
 
    numero_bits = (log(maior)/log(2))+1;
 
    for(i = 0; i < numero_bits; i++) {
        k = 0;
        l = 0;
 
        for(j = 0; j < tamanho; j++) {
            if((((vetor[j] >> i) & 0xf) % 2) == 0) {
                temp[0][k++] = vetor[j];
            } else {
                temp[1][l++] = vetor[j];
            }
        }
 
        temp[0][k] = '0';
        temp[1][l] = '0';
 
        j = 0;
        while(temp[0][j] != '0') {
            vetor[j] = temp[0][j];
            j++;
        }
 
        k=0;
        while(temp[1][k] != '0') {
            vetor[j] = temp[1][k];
            j++;
            k++;
        }
 
    }
}
 
void imprime(int *vetor, int tamanho)
{
    int i;
 
    for(i = 0; i < tamanho; i++)
        printf("%d ", vetor[i]);
}

[radix2.c]

Considerações:

  • Essa implementação do algorítmo é apenas para números maiores que 0
  • Para compilar com o gcc nçao esqueça de compilar o código com -lm para a biblioteca math

As chamadas feitas para a função clock() antes e depois da chamada de radix_sort() são para que possamos visualizar quanto tempo a execução do algorítmo levou.
A primeira pequena diferença que vemos nesse código é na linha 12, onde agora declaramos nossa variável vetor como um ponteiro para que possamos alocar o espaço necessário para ela em runtime. Isso se faz necessário pois quando executarmos o programa para organizar 20 milhões de registros, não seria possível alocar o espaço necessário na stack.
A segunda pequena diferença que vemos nesse código está na linha 22, onde garantimos que o número sorteado é no mínimo 1.
A terceira pequena diferença é que agora nós já identificamos qual é o maior número do nosso vetor. Isso será útil mais a frente.
As grandes diferenças começam dentro da função radix_sort().
A primeira coisa para se notar é que não usamos mais listas. Inserir e retirar items de um array é mais rápido, logo é isso que usaremos. Nosso bucket agora é a variável temp; temp[0] é o bucket0 e temp[1] é o bucket1.
A segunda coisa a se notar é a forma de como calculamos o numero de bits. Essa linha é de extrema imoprtância pois ela reflete diretamente na perfomance do algorítmo. Suponha que estamos trabalhando em uma plataforma onde a variável int tem 32 bits. Suponha também que o maior número que se encontra em nosso array é o número 230.
Vamos representar então o número 230 em binário:
00000000000000000000000011100110

São 32 bits, apenas 8 bits significativos e 24 bits não significativos. A partir do momento que organizamos os números usando todos os bits significativos é inútil que façamos a verificação para os dígitos não significativos pois eles irão sempre ser colocados no bucket0 e sempre na mesma ordem.
Para entender o cálculo utilizado, precisamos lembrar um pouco da matemática da escola.
2^x = 16
Quanto vale x?
log(base 2) de 16 = x
x = 4

Ou seja, para representarmos 16 números precisamos de 4 bits. Sabemos que o a contagem para os números binários começa em 0, logo com esses 4 bits podemos contar de 0 a 15. Para que o número 16 fosse considerado precisamos adicionar mais 1 bit.
Logo, para representar o número 16 temos: (log(base 2) de 16)+1
Como em C não temos uma forma de calcular log em base 2. nós usamos uma propriedade matemática que diz:

log em base x de y = (log y) / (log x)
Para os que não se lembram log sem menção de base é log em base 10.

Voltando então ao nosso exemplo do número 230. Se calcularmos:
log(base 2) de 230 = x
x = 7,85

Não há possibilidade de termos 0.85 bit. Em C variáveis int se arredondam pra baixo. Logo em nosso caso x vale 7.
2^7 = 128
Não é o suficiente, e matemáticamente falando 7,85 seria arredondado de fato para 8. É por essa razão que somamos 1.
2^8 = 256
Agora sim temos espaço o bastante para o valor 230.

Caso não tenha ficado muito clara a explicação, tente ler novamente e se precisar relembre um pouco de logarítmos. Tenho certeza que ficará mais claro.

O resto da lógica que segue é basicamente o mesmo. Como precisamos de uma forma de identificar quando os números de nosso bucket acabaram, eu optei por dizer que o último número do bucket é 0. É por essa razão que o esse algorítmo só é capaz de organizar números maiores que 0.
Todo o resto é basicamente o mesmo já apresentado antes.

Observações
Se você testou esses algorítmos deve ter percebido que são bastante rápidos. Eu quis determinar o quão rápida essa segunda implementação apresentada é quando comparada ao quicksort e os resultados são impressionantes.

Eis então os algorítmos usados:

Quicksort

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
 
void quicksort(int left, int right, int *v);
void swap(int i, int j, int **v);
 
int main(int argc, char **argv)
{
 
	int MAX;
 
	if(argc < 2) {
		printf("Use: %s numero\n", argv[0]);
		return -1;
	}
 
	MAX= atoi(argv[1]);
 
	int i;
	int *vetor;
    clock_t inicio, fim;
    float periodo;
 
	vetor = (int *)malloc(sizeof(int)*MAX);
 
	srand(time(NULL));
	for(i = 0; i < MAX; i++) {
		vetor[i] = rand() % MAX;
	}
 
	inicio = clock();	
	quicksort(0, MAX-1, vetor);
	fim = clock();
 
    periodo = (float)(((float)fim-(float)inicio)/CLOCKS_PER_SEC);
    printf("Tempo: %.15f\n", periodo);
 
	return 0;
}
 
void quicksort(int left, int right, int *v)
{
	int pivot = v[(left+right)/2];
	int i = left;
	int j = right;
 
	if ( left < right ) {
 
		do {
 
			while (v[i] < pivot) i++;
			while (v[j] > pivot) j--;
 
			if( i <= j ) {
				swap(i, j, &v);
				i++;
				j--;
			}
 
 
		} while( i <= j );
 
		quicksort(left, j, v);
		quicksort(i, right, v);
	}
}
 
void swap(int i, int j, int **v) {
	int aux;
 
	aux = (*v)[j];
	(*v)[j] = (*v)[i];
	(*v)[i] = aux;
 
}

Radixsort

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
 
void radix_sort(int *, int, int);
void imprime(int *vetor, int tamanho);
 
int main(int argc, char **argv)
{
    int tamanho;
    int *vetor;
    int i;
    int maior = 0;
    clock_t inicio, fim;
    float periodo;
 
	if(argc < 2) {
		printf("Use: %s numero\n", argv[0]);
		return -1;
	}
 
	tamanho = atoi(argv[1]);
 
    vetor = (int *)malloc(sizeof(int)*tamanho);
 
    srand(time(NULL));
    for(i = 0; i < tamanho; i++) {
        vetor[i] = (rand() % tamanho)+1;
        if(vetor[i] > maior)
            maior = vetor[i];
    }
 
    //imprime(vetor, tamanho);
    //printf("\n");
 
    inicio = clock();
    radix_sort(vetor, tamanho, maior);
    fim = clock();
    periodo = (float)(((float)fim-(float)inicio)/CLOCKS_PER_SEC);
 
    printf("Tempo: %.15f\n", periodo);
    //imprime(vetor, tamanho);
    //printf("\n");
 
    return 0;
}
 
void radix_sort(int *vetor, int tamanho, int maior)
{
 
    int i, j, k, l;
    int numero_bits;
 
    int **temp;
 
    temp = (int **)malloc(sizeof(int *)*2);
    temp[0] = (int *)malloc(sizeof(int)*tamanho);
    temp[1] = (int *)malloc(sizeof(int)*tamanho);
 
    numero_bits = (log(maior)/log(2))+1;
 
    for(i = 0; i < numero_bits; i++) {
        k = 0;
        l = 0;
 
        for(j = 0; j < tamanho; j++) {
            if((((vetor[j] >> i) & 0xf) % 2) == 0) {
                temp[0][k++] = vetor[j];
            } else {
                temp[1][l++] = vetor[j];
            }
        }
 
        temp[0][k] = '0';
        temp[1][l] = '0';
 
        j = 0;
        while(temp[0][j] != '0') {
            vetor[j] = temp[0][j];
            j++;
        }
 
        k=0;
        while(temp[1][k] != '0') {
            vetor[j] = temp[1][k];
            j++;
            k++;
        }
 
    }
}
 
void imprime(int *vetor, int tamanho)
{
    int i;
 
    for(i = 0; i < tamanho; i++)
        printf("%d ", vetor[i]);
}

[Radixsort]
[Quicksort]

Dados da máquina onde o teste foi feito:

$ uname -a
Linux myhost 2.6.30-ARCH #1 SMP PREEMPT Mon Aug 17 16:06:45 CEST 2009 x86_64 Intel(R) Core(TM)2 Duo CPU E7400 @ 2.80GHz GenuineIntel GNU/Linux
4GB Ram DDR2 800MHz

Eis então uma tabela comparativa do tempo de execução dos algorítmos para quando o maior valor que pode existir dentro do vetor é o próprio tamanho do vetor.
O tempo é dado em segundos

Numero elementos Radix Sort (s) Quick Sort (s)
10 0.000000000000000 0.000000000000000
1000 0.000000000000000 0.000000000000000
100 mil 0.019999999552965 0.009999999776483
1 milhão 0.310000002384186 0.219999998807907
10 milhões 3.759999990463257 2.549999952316284
100 milhões 42.380001068115234 28.530000686645508

Ao que parece, quicksort vence em todos os testes.
Agora façamos a seguinte alteração nos dois códigos:

No código do quicksort, trocamos

		vetor[i] = rand() % MAX;

por

		vetor[i] = rand() % 1023;

No código do radixsort, trocamos

		vetor[i] = (rand() % tamanho)+1;

por

		vetor[i] = (rand() % 1022)+1;

Eis os novos resultados:

Numero elementos Radix Sort (s) Quick Sort (s)
10 0.000000000000000 0.000000000000000
1000 0.000000000000000 0.000000000000000
100 mil 0.009999999776483 0.009999999776483
1 milhão 0.159999996423721 0.180000007152557
10 milhões 1.559999942779541 2.059999942779541
100 milhões 15.560000419616699 22.809999465942383

O Radixsort vence todas as comparações!
Logo, dependendo do tipo de dados que se deseja ordenar (caracteres por exemplo), o Radixsort pode ter uma performance superior a do Quicksort.

As implementações do Radixsort aqui apresentadas ainda podem ser otimizadas eu acredito, podendo apresentar uma performance ainda maior!

Referências

http://www.itl.nist.gov/div897/sqg/dads/HTML/radixsort.html
http://www.jimloy.com/computer/radix.htm

Tagged as: , , No Comments
18Sep/090

Lancamento: Dive Into Python 3

Hoje foi lançado o livro 'Dive Into Python 3' de Mark Pilgrim em formato digital . Ao que parece ele também será comercializado a partir de 16 de outubro.
Dei uma lida no primeiro capítulo do livro e me pareceu bom. O autor escreve de forma simples de se entender e começa mostrando código ao invés de gastar 75 páginas contando histórinha sobre a linguagem.

Link: Dive Into Python 3

Fonte: Dzone

Filed under: Python No Comments
13Sep/093

Construindo um editor de texto com Java + Swing – Parte 1

É, eu sei que faz tempo que não escrevo. Faltou paciência/tempo/paciência/inspiração/paciência e por ai vai...
Bom, estou de volta e atualmente brincando um pouco com Java para construir GUIs (Graphical User Interfaces). Num tutorial que terá sabe-se lá quantas partes vou abordar como criar um editor de texto simples em Java. Esse editor de texto se propõe (pelo menos por enquanto) a possuir as seguintes funcionalidades:

  • Edição de arquivos em abas
  • Salvar documentos
  • Copiar texto através de um menu
  • Colar texto através de um menu
  • Mostrar uma caixinha de "Sobre o Editor"

É possível que conforme eu vá mexendo no editor e aprendendo truques novos eu adicione coisas novas.
Nessa primeira parte do tutorial irei mostrar os seguintes conceitos:

  • Como criar uma janela com Swing
  • Como adicionar componentes a janela
  • Como criar um menu para a janela
  • Como tratar eventos no seu menu
  • Como criar um painel com abas

Wow! Não imaginei que eram tantos assuntos hehehe. Bom, a intenção aqui é de ser mais um guia e não uma referência completa. Conforme discuto cada assunto, colocarei informações básicas e links apontando para lugares onde pode-se obter maiores informações (basicamente para a Javadoc).
Tendo escrito tanto, hora de ir para a parte interessante da coisa... a programação!

Como criar uma janela com Swing
Rapidamente antes de começar, preciso dizer uma coisinha. Eu estou assumindo que você, leitor, tem ao menos um leve conhecimento de Java (sintaxe e um pouquinho de nada de orientação a objetos). Caso não tenha, pode seguir em frente, mas recomendo ler algum material falando um pouco sobre a linguagem.

import javax.swing.JFrame;
 
public class JanelaSimples extends JFrame {
 
	public JanelaSimples() {
		this.setTitle("Minha primeira janela");
		this.setSize(640,480);
		this.setVisible(true);
		this.setDefaultCloseOperation(EXIT_ON_CLOSE);
	}
 
	public static void main(String[] args) {
		new JanelaSimples();
	}
}

[JanelaSimples.java]

Para compilar:
javac JanelaSimples.java

Para executar:
java JanelaSimples

Não coloque a extensão .class ou .java quando for executar a aplicação pois não irá funcionar.

Deu pra ver que é uma janela bastante útil. Você consegue redimensionar, fechar e... e é isso :)
Mesmo não servindo para muita coisa já deu um gostinho, então vamos as partes desse programa.

	public class JanelaSimples extends JFrame {

Aqui estamos criando a nossa classe JanelaSimples e herdando toda a funcionalidade de JFrame, que é a classe responsável pelos métodos que criam a nossa janela.

	public JanelaSimples() {
		this.setTitle("Minha primeira janela");
		this.setSize(640,480);
		this.setVisible(true);
		this.setDefaultCloseOperation(EXIT_ON_CLOSE);
	}

Em primeiro lugar, todas os métodos que estão sendo invocados foram herdados da classe JFrame, e é por isso que eu os chamo com 'this.'.
O que estes métodos estão fazendo? Na respectiva sequencia:
Definindo o titulo da janela para "Minha primeira janela"
Definindo o tamanho da janela para 640x480
Definindo que a janela deve ser exibida
Definindo que a operação padrão para quando você fechar a janela é a de que o programa deve terminar

Tudo muito simples.

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

Como esse é o nosso programa principal ele precisa de uma função main que serve de ponto de entrada para ele. Nossa função main simplesmente instancia a classe JanelaSimples. Quando a instancia é criada, o construtor (explicado logo acima) é chamado e a nossa janela se faz visível.

Como adicionar componentes a janela

Agora que já sabemos criar a janela, vamos adicionar uma caixa de texto a ela. Para isso, usaremos o mesmo código da JanelaSimples.java e adicionaremos alguns comandos no construtor da classe. O construtora ficará da seguinte maneira:

import javax.swing.JFrame;
import javax.swing.JTextPane;
 
public class JanelaSimples extends JFrame {
 
	public JanelaSimples() {
		JTextPane texto = new JTextPane();
 
		this.setTitle("Minha primeira janela");
		this.setSize(640,480);
		this.add(texto);
		this.setVisible(true);
		this.setDefaultCloseOperation(EXIT_ON_CLOSE);
	}
 
	public static void main(String[] args) {
		new JanelaSimples();
	}
}

[JanelaSimples-2.java]

Se quiser compilar este exemplo, renomeie o arquivo para JanelaSimples.java e use as instruções fornecidas acima.
Daqui pra frente eu evitarei re-escrever todo o código o tempo todo e colarei apenas as novas partes que foram adicionadas. Para acessar o código todo, por favor use os arquivos que estão linkados em cada seção :)
Este novo código não mudou muito. As novidades aqui são:

import javax.swing.JTextPane;
...
		JTextPane texto = new JTextPane();
		...
		this.add(texto);

Adicionei o import referente a classe JTextPane que nos fornece uma caixa de texto. Dentro do construtor eu simplesmente crio uma instancia da class JTextPane e adiciono essa instancia a minha janela. O método 'add()' também foi herdado de JFrame.
Você vai perceber que o componente está usando a janela toda. Isso pode ser mudado, e será discutido quando estivermos falando de layouts da janela.
Todo componente que você desejar criar, basta instancia-lo e então adiciona-lo a janela utilizando o método 'add()'.
Se você deseja maiores informações sobre os métodos que a classe JFrame implementa e seus respectivos protótipos, de uma olhada na documentação do JFrame

Como criar um menu para a janela

Até então tudo muito interessante. Vamos adicionar um pouco de emoção agora e criar um menu para a nossa linda janela que não faz muita coisa.

import javax.swing.JFrame;
import javax.swing.JTextPane;
import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
...
 
	public JanelaSimples() {
		JMenuBar barra = new JMenuBar();
		JMenu menuArquivo = new JMenu("Arquivo");
		JMenuItem arqSair = new JMenuItem("Sair");
 
		menuArquivo.add(arqSair);
		barra.add(menuArquivo);
		this.setJMenuBar(barra);
		...
	}
 
}

[JanelaSimples-3.java]

E o nosso programa está crescendo!
Só para esclarecer; um menu é feito de basicamente três partes:
A barra de menus
Os menus
Os items dos menus
Como pode-se perceber nós adicionamos três novos imports em nosso código que se referem a cada um desses items. Em nosso construtor então nós instanciamos uma barra de menu (JMenuBar), instanciamos um menu (JMenu) e finalmente instanciamos um item para o menu (JMenuItem). Depois de cada um dos items instanciados e configurados conforme desejamos, basta associa-los conforme feito no código.
Adicionamos o item 'arqSair' dentro do menu 'menuArquivo'. Adicionamos o menu 'menuArquivo' a barra de menu 'barra' e por fim chamamos o método setJMenuBar para adicionar o menu a nossa janela.
Para adicionar o menu nós utilizamos o método especial setJMenuBar pois ele já configura o nosso menu para ser mostrado da maneira esperada no topo da janela.

Como tratar eventos no seu menu

Eu estou achando esse programa cada vez mais empolgante! :D (espero que vocês também)
Como deu pra notar, o nosso menu 'Sair' não faz absolutamente nada :(
Iremos abordar agora um tipo simples de evento, e falar um pouquinho sobre alguns tipos de eventos que existem. Ao longo dos próximos tutoriais de como criar o nosso editor de texto iremos falando mais sobre esse assunto e explorando novos eventos.
Vamos então colocar uma funcionalidade nesse item 'Sair' para que ele faça aquilo que ele promete :)

...
 
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
 
...
 
		JMenuItem arqSair = new JMenuItem("Sair");
 
		arqSair.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		});
 
		menuArquivo.add(arqSair);
		barra.add(menuArquivo);
		this.setJMenuBar(barra);
 
...

[JanelaSimples-4.java]

Mais dois imports para nos auxiliar e um trecho de código um tanto quanto diferente. Deixe-me falar um pouco sobre eventos e listeners e especialmente sobre o ActionListener e o ActionEvent.
Eventos, como o nome sugere, são coisas que acontecem ao seu programa. Quando o usuário interage com o programa ao clicar em algo, digitar algo, movimentar o mouse ou coisas do genêro, um evento está acontecendo. O nosso programa sabe desses eventos através do chamamos de Listeners.
Os Listeners ficam 'ouvindo' o programa por eventos que aconteçam. Quando um evento acontece, o Listener invoca o método associado ao tipo de evento.
O ActionListener é um Listener para os eventos mais comums, como um clique ou como quando o usuário pressiona Enter ou Space. O evento que é lido por um ActionListener é um ActionEvent. Quando você tem um item em seu menu e clica com o botão direito ou esquerdo nele você gerou um ActionEvent. Se voce selectionar o menu e apertar Enter, você gerou um ActionEvent nele.
Possuindo essa noção de Listener e Event, vamos reanalizar o código acima.

	arqSair.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
			System.exit(0);
		}
	});

O componente JMenuItem possui um método que nos permite associar a instancia de uma classe que trata eventos do tipo ActionEvent ao seu ActionListener.
Sempre que um ActionEvent acontecer o método 'actionPerformed' dessa instancia será chamado para tratar do evento.
No nosso caso, para não precisarmos escrever toda uma classe para isso, nós criamos uma instancia anonima de ActionListener explicitando o nosso método actionPerfomed. Quando a ação ocorrer, esse método será executado e em nosso caso irá fazer com que a aplicação encerre.
O parametro que o método recebe contém informações sobre o evento, tais como: De onde veio o evento, quando o evento aconteceu, quais teclas estavam pressionadas quando o evento occoreu e etc... Para maiores informações sobre o que se pode tirar do ActionEvent, acesse esse link
Com isso, encerramos a breve introdução a eventos. Se ficaram algumas dúvidas, não se preocupe pois iremos falar mais sobre eles no futuro.

Como criar um painel com abas

Nosso editor de texto já está ganhando forma e agora é hora de dar a ele a primeira funcionalidade prometida: Abas!
Vamos criar então um painel de abas onde cada aba será uma caixa de texto diferente para escrevermos.

import javax.swing.JFrame;
import javax.swing.JTextPane;
import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
 
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
 
// JTabbedPane para nosso painel de abas!
import javax.swing.JTabbedPane;
 
public class JanelaSimples extends JFrame {
 
	public JanelaSimples() {
		JMenuBar barra = new JMenuBar();
		JMenu menuArquivo = new JMenu("Arquivo");
		JMenuItem arqSair = new JMenuItem("Sair");
 
		arqSair.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		});
 
		menuArquivo.add(arqSair);
		barra.add(menuArquivo);
		this.setJMenuBar(barra);
 
		JTextPane texto = new JTextPane();
 
		this.setTitle("Minha primeira janela");
		this.setSize(640,480);
 
		/**
		 * A caixa de texto não é mais adicionada a janela
		 * e sim ao painel de abas
		 */
		//this.add(texto); &lt;--- comentado!
 
		this.setDefaultCloseOperation(EXIT_ON_CLOSE);
 
		/**
		 * Criar o painel e adiciona-lo a janela
		 */
		JTabbedPane painel = new JTabbedPane();
		painel.addTab("Aba 1", texto);
		this.add(painel);
 
		this.setVisible(true);
 
	}
 
	public static void main(String[] args) {
		new JanelaSimples();
	}
}

[JanelaSimples-5.java]

Coloquei o código completo para não nos perdermos. As partes novas estão comentadas para serem mais facilmente identificadas.

...
 
		//this.add(texto); &lt;--- comentado!
 
		...
 
		/**
		 * Criar o painel e adiciona-lo a janela
		 */
		JTabbedPane painel = new JTabbedPane();
		painel.addTab("Aba 1", texto);
		this.add(painel);
		...

Até aqui, bastante simples. Criamos um JTabbedPane, que é o nosso painel com abas e adicionamos uma aba com o título "Aba 1" e o componente que ela irá exibir é a nossa caixa de texto. Adicionamos então o painel a janela. Vejam que comentei a linha que adicionava a caixa de texto a janela já que agora a caixa de texto faz parte do painel de abas.

Próximo episódio

Bem pessoal, por hoje é só :)
Na próxima parte iremos abordar os seguintes temas:

  • Criação do menu 'Novo'
  • Criação do menu 'Salvar'
  • Criação do menu 'Salvar Como'
  • Criação do menu 'Sobre'

Talvez eu aborde mais coisas, mas não sei ainda.
Até a próxima :)

22Mar/080

Dica: Source Code Highlighter

Fala pessoal!

Quando estava escrevendo os posts aqui para o blog, precisava de uma ferramenta que me ajudasse a colorir o código e me fornecesse o html para ele colorido. Depois de experimentar várias ferramentas por aí, a mais bacana que encontrei e com suporte ao maior número de linguagens foi o Quick Highliter.

quickhighlighter.jpg

Você digita o código na linguagem desejada, seleciona no drop down a linguagem que está utilizando, define as opções que deseja e seu código sai colorido! Outra coisa bacana é que ele também faz indentação.
Para aqueles que gostam de um código fácil de ler e bem organizado, tá ai uma ótima dica.

Filed under: Outros No Comments
21Mar/089

Java: Conectando-se ao MySQL

Olá pessoas,

Aqui estou de novo tentando voltar a postar hehehe. Hoje vou mostrar um exemplo simples de classe em Java para se conectar ao MySQL, para que mais pra frente eu possa mostrar algumas coisas bacanas para se fazer com JSP + TagLibs + MySQL + Servlets :) (Não sou grande fã de usar frameworks quando ainda não conheço bem a linguagem).
Bom, chega de lenga lenga, e vamos ao que interessa :D

Para poder brincar você vai precisar do MyConnector/J instalado. Ele pode ser obtido em: http://dev.mysql.com/downloads/connector/j/5.1.html!
Ao baixar o Connector/J você encontrará dentro do .zip (ou do .tar.gz) um arquivo com o nome parecido com: mysql-connector-java-5.1.5-bin.jar. Certifique-se que você adicionará esse arquivo ao seu CLASSPATH pois ele será necessário para que os exemplos que usaremos funcionem.

Agora vamos criar um banco de dados simples para utilizarmos nos nossos exemplos:

CREATE DATABASE projeto;
USE projeto;
CREATE TABLE usuario (
   id_usuario INT NOT NULL PRIMARY KEY AUTO_INCREMENT
   ,nome VARCHAR(100)
   ,idade INT(3)
);

E aqui inserimos alguns dados, para podermos consulta-los.

INSERT INTO usuario (
   nome
   ,idade
) VALUES (
   "Henrique"
   ,22
);
 
INSERT INTO usuario (
   nome
   ,idade
) VALUES (
   "José"
   ,35
);
 
INSERT INTO usuario (
   nome
   ,idade
) VALUES (
   "Andreia"
   ,21
);

Vou começar com uma classe bem simples, que simplesmente se conecta ao MySQL, e ao longo do texto vamos incrementa-la para que faça mais coisas. Vamos então a primeira classe:

package com.heap.db;
 
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Connection;
 
public class MySQL {
 
    private String host;
    private String user;
    private String pass;
    private String database;
 
    public Connection c;
 
    /**
     * Construtor da classe
     *
     * @param host Host em que se deseja conectar
     * @param database Nome do database em que se deseja conectar
     * @param user Nome do usuário
     * @param pass Senha do usuário
     */
    public MySQL( String host, String database, String user, String pass ) {
        this.pass = pass;
        this.user = user;
        this.host = host;
        this.database = database;
    }
 
    /**
     * Método que estabelece a conexão com o banco de dados
     *
     * @return True se conseguir conectar, falso em caso contrário.
     */
    public boolean connect() {
        boolean isConnected = false;
        String url;
 
        url = "jdbc:mysql://"+this.host+"/"
              +this.database+"?"
              +"user="+this.user
              +"&password="+this.pass;
 
        try {
            Class.forName("com.mysql.jdbc.Driver").newInstance();
            System.out.println(url);
            this.c = DriverManager.getConnection(url);
            isConnected = true;
        } catch( SQLException e ) {
            e.printStackTrace();
            System.out.println(e.getMessage());
            isConnected = false;
        } catch ( ClassNotFoundException e ) {
            e.printStackTrace();
            System.out.println(e.getMessage());
            isConnected = false;
        } catch ( InstantiationException e ) {
            e.printStackTrace();
            System.out.println(e.getMessage());
            isConnected = false;
        } catch ( IllegalAccessException e ) {
            e.printStackTrace();
            System.out.println(e.getMessage());
            isConnected = false;
        }
 
        return isConnected;
    }
}

Código bastante simples como podemos perceber. A primeira linha indica que essa classe pertence ao pacote com.heap.db. É importante ressaltar isso, porque teremos de importar esse código para dentro da nossa classe que será o nosso programa de verdade, que chamará o método de conexão. Perceba também que no código eu não faço nenhum tratamento especial para as exceptions, apenos imprimo o erro. Essa não é uma técnica muito bacana, pois podemos perder informações. Eu gost de criar uma outra classe que faça o handle das exceptions e salve os erros em algum lugar, para que eu possa consulta-los se ncessário, mas em nosso caso aqui é o bastante apenas imprimi-las.

import com.heap.db.MySQL;
 
public class TesteDB {
 
    public static void main (String [] args) {
        MySQL db = new MySQL("localhost","projeto","root","12345");
        if ( db.connect() ) {
          System.out.println("Conectado!");
        };
      }
 
}

Logo na primeira linha desse código é feito o import da classe que se conecta ao MySQL. No método main() criamos uma instancia da classe MySQL com os parametros para especificar onde estamos conectando, e logo na sequência chamamos o método para conectar.
Agora é hora de testar isso tudo. Então apenas para motivos de esclarecimento, vamos dar uma olhada em como ficou a estrutura do nosso diretório.

C:\Projeto
¦   TesteDB.class
¦   TesteDB.java
¦
+---com
¦   +---heap
¦      +---db
¦               MySQL.class
¦               MySQL.java
¦
+---lib
         mysql-connector-java-5.1.5-bin.jar

No meu caso já se pode ver os arquivos .class pois já compilei o código, mas aqui vai a forma como iremos compilar:

À partir da raiz do projeto (No meu caso aqui C:\Projeto (Sim, estou usando windows porque estou escrevendo isso do computador do trabalho :|))
javac -cp . com\heap\db\MySQL.java
javac -cp . TesteDB.java

Agora que tudo está compilado, basta rodar!

java TesteDB
java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
         at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
         at java.security.AccessController.doPrivileged(Native Method)
         at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
         at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
         at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:276)
         at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
         at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
         at java.lang.Class.forName0(Native Method)
         at java.lang.Class.forName(Class.java:169)
         at com.heap.db.MySQL.connect(MySQL.java:32)
         at TesteDB.main(TesteDB.java:7)
com.mysql.jdbc.Driver

Oops! Algo saiu errado. Lembram-se no começo quando eu disse que precisavamos do Mysql Connector/J e que ele estivesse no CLASSPATH? Bem, como da pra notar na estrutura de diretórios, eu coloquei o Connector/J no diretório lib porém não fiz referência a essa lib quando fui rodar o programa, o que causou o erro. Então vamos rodar o comando correto e ver o que acontece:

java -cp .;lib\mysql-connector-java-5.1.5-bin.jar TesteDB
jdbc:mysql://localhost/projeto?user=root&password=12345
Conectado!

Como pode perceber, agora parece que tudo funcionou! A opção -cp do comando java diz ao interpretador qual o CLASSPATH a utilizar. Nesse caso mandei que utilizasse o diretório atual, e o arquivo .jar do Connector/J.

Muito legal isso tudo, porém uma classe que simplesmente conecta no banco é bastante inútil. Vamos então melhorar a nossa classe MySQL, adicionando algums métodos para que possamos consultar dados.

O primeiro método que irei adicionar é um que recebe uma query como parametro e retorna um ResultSet. O ResultSet nada mais é do que um resource que representa a tabela. Eis o código:

    /**
     * Esse método executa a query dada, e retorna um ResultSet
     * Talvez fosse melhor idéia fazer esse método lançar uma exception
     * a faze-lo retornar null como eu fiz, porém isso é apenas um exemplo
     * para demonstrar a funcionalidade do comando execute
     *
     * @param query String contendo a query que se deseja executar
     * @return ResultSet em caso de estar tudo Ok, null em caso de erro.
     */
    public ResultSet executar( String query ) {
        Statement st;
        ResultSet rs;
 
        try {
            st = this.c.createStatement();
            rs = st.executeQuery(query);
            return rs;
        } catch ( SQLException e ) {
            e.printStackTrace();
        }
 
        return null;
    }

Nesse trecho de código, criamos um Statement a partir da nossa conexão. O objeto Statement nos permite executar comando no nosso banco de dados.
E para testar esse código, nós iremos rodar uma query contra o nosso banco de dados que criamos no começo desse texto. O nosso código da classe TesteDB vai ficar dessa maneira:

import java.sql.ResultSet;
import java.sql.SQLException;
 
import com.heap.db.MySQL;
 
public class TesteDB {
 
    public static void main (String [] args) {
        MySQL db = new MySQL("localhost","projeto","root","12345");
        String query;
 
        if ( db.connect() ) {
            System.out.println("Conectado!");
            System.out.println("Rodando uma query contra o banco");
 
            query = "select * from usuario";
            ResultSet rs = db.executar(query);
            try {
                if ( rs != null ) { // Verifica se a query retornou algo
                    while ( rs.next() ) {
                        // Podemos referenciar a coluna pelo índice
                        System.out.println("Id: " + rs.getInt(1));
 
                        // Ou pelo seu nome
                        System.out.println("Nome: " + rs.getString("nome"));
                        System.out.println("Idade: " + rs.getInt("idade"));
                        System.out.println("----------------------------");
                    }
                }
          } catch ( SQLException e ) {
              e.printStackTrace();
          }
 
        };
      }
 
}

Recompile e rode esse código da mesma forma que fizemos lá em cima, você deve ter uma saída como essa:

jdbc:mysql://localhost/projeto?user=root&password=12345
Conectado!
Rodando uma query contra o banco
Id: 1
Nome: Henrique
Idade: 22
----------------------------
Id: 2
Nome: Jos‚
Idade: 35
----------------------------
Id: 3
Nome: Andreia
Idade: 21
----------------------------

No código você pode reparar que eu referenciei as colunas tanto utilizando um índice (cada coluna tem um índice numérico que vai de 1, representando a primeira coluna da tabela, até N onde N é o número de colunas na tabela). Repare também que para cada tipo de dados que a tabela possui utilizamos um método get distinto. Para a lista de métodos get que se pode utilizar, consulte a documentação do ResultSet .
Já melhoramso bastante! Agora nossa classe já tem uma função, ela consulta dados na nossa base! Vamos ver então como podemos ecrever um método para inserir dados em nossa tabela. De volta na classe MySQL adicione o seguinte método:

    /**
     * Executa uma query como update, delete ou insert.
     * Retorna o número de registros afetados quando falamos de um update ou delete
     * ou retorna 1 quando o insert é bem sucedido. Em outros casos retorna -1
     *
     * @param query A query que se deseja executar
     * @return 0 para um insert bem sucedido. -1 para erro
     */
    public int inserir( String query ) {
        Statement st;
        int result = -1;
 
        try {
            st = this.c.createStatement();
            result = st.executeUpdate(query);
        } catch ( SQLException e ) {
            e.printStackTrace();
        }
 
        return result;
    }

Esse método poderá ser utilizado tanto para inserir dados, como para apagar ou atualizar. Novamente criamos um Statement e chamamos um método para executar a nossa query. Para maiores informações sobre os métodos da classe Statement consulte a documentação.

Vejamos então como ficou a nossa classe MySQL completa, e um programa de exemplo que a utiliza.

MySQL.java

package com.heap.db;
 
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Statement;
 
public class MySQL {
 
    private String host;
    private String user;
    private String pass;
    private String database;
 
    public Connection c;
 
    /**
     * Construtor da classe
     *
     * @param host Host em que se deseja conectar
     * @param database Nome do database em que se deseja conectar
     * @param user Nome do usuário
     * @param pass Senha do usuário
     */
    public MySQL( String host, String database, String user, String pass ) {
        this.pass = pass;
        this.user = user;
        this.host = host;
        this.database = database;
    }
 
    /**
     * Método que estabelece a conexão com o banco de dados
     *
     * @return True se conseguir conectar, falso em caso contrário.
     */
    public boolean connect() {
        boolean isConnected = false;
        String url;
 
        url = "jdbc:mysql://"+this.host+"/"
              +this.database+"?"
              +"user="+this.user
              +"&password="+this.pass;
 
        try {
            Class.forName("com.mysql.jdbc.Driver").newInstance();
            System.out.println(url);
            this.c = DriverManager.getConnection(url);
            isConnected = true;
        } catch( SQLException e ) {
            e.printStackTrace();
            System.out.println(e.getMessage());
            isConnected = false;
        } catch ( ClassNotFoundException e ) {
            e.printStackTrace();
            System.out.println(e.getMessage());
            isConnected = false;
        } catch ( InstantiationException e ) {
            e.printStackTrace();
            System.out.println(e.getMessage());
            isConnected = false;
        } catch ( IllegalAccessException e ) {
            e.printStackTrace();
            System.out.println(e.getMessage());
            isConnected = false;
        }
 
        return isConnected;
    }
 
    /**
     * Esse método executa a query dada, e retorna um ResultSet
     * Talvez fosse melhor idéia fazer esse método lançar uma exception
     * a faze-lo retornar null como eu fiz, porém isso é apenas um exemplo
     * para demonstrar a funcionalidade do comando execute
     *
     * @param query String contendo a query que se deseja executar
     * @return ResultSet em caso de estar tudo Ok, null em caso de erro.
     */
    public ResultSet executar( String query ) {
        Statement st;
        ResultSet rs;
 
        try {
            st = this.c.createStatement();
            rs = st.executeQuery(query);
            return rs;
        } catch ( SQLException e ) {
            e.printStackTrace();
        }
 
        return null;
    }
 
    /**
     * Executa uma query como update, delete ou insert.
     * Retorna o número de registros afetados quando falamos de um update ou delete
     * ou retorna 1 quando o insert é bem sucedido. Em outros casos retorna -1
     *
     * @param query A query que se deseja executar
     * @return 0 para um insert bem sucedido. -1 para erro
     */
    public int inserir( String query ) {
        Statement st;
        int result = -1;
 
        try {
            st = this.c.createStatement();
            result = st.executeUpdate(query);
        } catch ( SQLException e ) {
            e.printStackTrace();
        }
 
        return result;
    }
}

TesteDB.java

import java.sql.ResultSet;
import java.sql.SQLException;
 
import com.heap.db.MySQL;
 
public class TesteDB {
 
    public static void main (String [] args) {
        MySQL db = new MySQL("localhost","projeto","root","12345");
        String query;
        int result;
 
        if ( db.connect() ) {
            System.out.println("Conectado!");
            System.out.println("Rodando uma query contra o banco");
 
            query = "select * from usuario";
            ResultSet rs = db.executar(query);
            try {
                if ( rs != null ) { // Verifica se a query retornou algo
                    while ( rs.next() ) {
                        // Podemos referenciar a coluna pelo índice
                        System.out.println("Id: " + rs.getInt(1));
 
                        // Ou pelo seu nome
                        System.out.println("Nome: " + rs.getString("nome"));
                        System.out.println("Idade: " + rs.getInt("idade"));
                        System.out.println("----------------------------");
                    }
                }
          } catch ( SQLException e ) {
              e.printStackTrace();
          }
 
          System.out.println("Inserindo dados na tabela");
          query = "insert into usuario (nome, idade) values ('Maria',23)";
          result = db.inserir(query);
          if (  result > -1 ) {
              System.out.println("Dado inserido com sucesso! Resutlt = " + result);
          } else {
              System.out.println("Erro inserindo dado.");
          }
 
          query = "update usuario set idade = 99";
          result = db.inserir(query);
          if (  result > -1 ) {
              System.out.println("Dado inserido com sucesso! Resutlt = " + result);
          } else {
              System.out.println("Erro inserindo dado.");
          }
 
        };
      }
 
}

O procedimento para compilar e rodar é o mesmo que seguimos lá no começo.
Bom, por hoje é só pessoas! Espero que esse pequeno tutorial possa ajudar aqueles que como eu estão começando! Continuem estudando e boa sorte!
No próximo vamos falar de coisas pouco mais avançadas e ainda mais divertidas.

Para maiores informações consulte os links:
MySQL AB: Basic JDBC Concepts
Java Documentation
Google

Filed under: Java, MySQL 9 Comments
29Jan/080

PL/SQL Oracle: Identificando o último dia da semana no mês

Fala pessoal!

Fiquei sumido desde o primeiro post. A vida está uma correria, e mal tenho tido tempo de fazer qualquer coisa :|
Bom, estava no trabalho dia desses e precisava de uma lógica para identificar qual é o último dia do mês que caia durante a semana. Depois de pesquisar um pouco como poderia fazer isso, e pensar por alguns instante, cheguei ao seguinte código.

  1. DECLARE
  2.    v_ultimo_dia VARCHAR2(10);
  3.  
  4. BEGIN
  5.  
  6.    v_ultimo_dia := TO_CHAR(LAST_DAY(SYSDATE),'D');
  7.    IF v_ultimo_dia = 7 THEN  -- Sabado
  8.       v_ultimo_dia := TO_CHAR((LAST_DAY(SYSDATE)-1),'DD/MM/YYYY');
  9.    ELSIF v_ultimo_dia = 1 THEN -- Domingo
  10.       v_ultimo_dia := TO_CHAR((LAST_DAY(SYSDATE)-2),'DD/MM/YYYY');
  11.    ELSE
  12.       v_ultimo_dia := TO_CHAR((LAST_DAY(SYSDATE)),'DD/MM/YYYY');
  13.    END IF;
  14. END;

A variável "v_ultimo_dia" agora contém a data no formato DD/MM/YYYY do último dia do mês atual.
E essa foi uma dica rápida para hoje.
De agora em diante, quando o tempo para postar for curto, pretendo tentar postar dicas rápidas, e eventualmente postar textos mais detalhados sobre coisas mais específicas.
Espero que todos aproveitem.

Até o próximo ;)

Filed under: Oracle, PL/SQL No Comments
2Nov/075

Bootstrap Simples

Introdução

Esse fim de semana andei estudando um pouco de assembly. Decidi fazer um bootstrap para ver se me animava um pouco, e acabou me voltando a vontade de realmente brincar de criar o meu sistema operacional. Como tudo precisa de um começo, decidi que o bootstrap seria um belo lugar para começar.
Vejamos então como é que fazemos para dar um boot na maquina e o que é que a(o) BIOS (Basic Input Output System) espera para iniciar o sistema operacional.
Quando o computador inicia e o POST termina, a BIOS faz uma chamada para ler o primeiro setor do disco que está definido como primário no setup. Funciona assim:

a) Olha o primeiro setor do disco definido no setup
b) Encontrou setor de boot válido?
c) Sim. Lê o setor e carrega o código para o segmento endereço 7C00h na memória. (Valeu pela correção Muzgo :)
d) Não. Parte para o próximo dispositivo da lista de boot e vai para a) até encontrar.
e) Caso não encontre em nenhum dos dispositivos, exibe mensagem de erro padrão da BIOS.

A Lista de software que usei:
1. NASM - Para compilar o código
2. QEMU - Maquina virtual que uso para testar o setor de boot
3. dd - Utilizado para escrever os dados no disco

O setor de boot

Quando definimos o disco no qual desejamos dar o boot, a BIOS chama uma interrupção (19h se nao me engano), para ler o primeiro setor desse disco. O primeiro setor é o que se encontra na posição CHS (Cylinder Head Sector) 0:0:1. Cada setor no disco tem 512 bytes, então como a BIOS lê o primeiro setor do disco, nosso 'programa' precisa ter 512 bytes ou menos. Meu objetivo aqui não é de falar muito sobre HCS, maiores informações você pode encontrar aqui.
O que identifica se temos um setor de boot válido ou não?
O setor de boot tem uma 'assinatura', os últimos dois bytes do setor devem ser '0xAA55'. Quando essa assinatura é encontrada, os 512 bytes são carregados para a memória na posição 7C00h e o programa é executado.

O código

Como já diria o conde Drácula em 'Castlevania: Symphony of the Night': "Enough talk!".
Vamos dar uma olhada então em um código bem simples, que imprime uma string na tela.

; Boot.asm
 
ORG 7C00h                   ; Posição onde estaremos quando o código for
 
                            ; carregado para a memória
 
mymsg: db 'Olá Setor de BOOT',10,0
 
xor ax,ax                   ; Limpando ax
 
mov si,mymsg                ; Nossa mensagem em SI
 
putstr:
        lodsb               ; Coloca o byte apontado em SI em AL, e incrementa
                            ; o contador
 
        or al,al            ; Verificamos se encontramos o byte '0' da string
        jz hang;            ; Se sim, paramos de imprimir
        mov ah,0x0E         ; Função para escrever
 
        mov bx,0x0007       ; Define a página e a cor onde escrevemos
        int 0x10            ; Interrupção de video
        jmp putstr          ; Imprimir proximo caracter
 
hang:
        jmp hang            ; Após impressão, entramos em loop infinito
 
times 512-($-$$)-2 DB 0     ; Preenchemos o resto da memória com '0's
 
                            ; até 510 bytes
DW 0xAA55                   ; Assinatura do setor de boot

Acredito que o código esteja auto explicativo. Você deve precisar manjar um pouco de assembly, então se não entendeu o código, procure estudar um pouquinho de assembly.
Vamos agora compilar o nosso código, criar um disco para o QEMU, e escrever o nosso setor de boot no disco.

$ nasm boot.asm -f bin -o boot.bin
$ qemu-img create /tmp/boot.img -f qcow 1M
$ dd if=boot.bin of=/tmp/boot.img

Com isso feito, basta agora executar o QEMU e dizer que desejamos usar o arquivo /tmp/boot.img como nosso disco.

$ qemu /tmp/boot.img -m 16

O resultado é a nossa frase impressa logo após a BIOS fazer o POST.

Colocando código no Disco

Bom, agora que já sabemos dar o boot, está na hora de colocarmos código no disco e executar esse código. Esse é um processo um pouco mais complicado, mas vamos que vamos!
A idéia agora é fazer com que o nosso programa de boot chame um binario que esteja gravado no disco e o execute. Então precisaremos escrever dois programas distintos: Um para ser o nosso setor de boot, e um que será chamado por ele. "Show me the code"

; Boot2.asm
 
ORG 7C00h
 
mov si,msg
 
prntMsg:                    ; Imprime a mensagem em SI
  lodsb
  mov ah,0x0E
 
  mov bx,0x0007
  int 10h
  or al,al
 
  jnz prntMsg
 
mov [drv],dl                ; DL contém o identificador da unidade em que 
                            ; o setor de boot foi encontrado
 
; Inicializar o disco. Aqui colocamos a cabeça do disco no inicio dele
 
.diskSetup
  mov ax,0        ; Função para resetar o disco rígido
  mov dl,[drv]    ; O drive que vamos resetar
 
  int 13h         ; Chama a interrupção de disco
  jc .diskSetup   ; Se der erro, tentamos de novo
 
; Após resetar o disco, colocaremos a cabeça no setor que o programa se 
 
; encontra e carregamos o programa na memória
 
.diskRead
  mov ah,02h    ; Função para ler o disco
  mov al,3      ; Ler 3 setores (512 * 3 bytes)
 
  mov ch,0      ; Apontar para o cilindro 0
  mov cl,0x02   ; Ler a partir do setor 2 (2, 3 e 4)
 
  mov dh,0      ; Cabeça 0
  mov dl,[drv]  ; Disco de onde queremos ler os dados
 
; Os dados que lemos com essa função são armazenados em ES:BX
; No nosso caso aqui entao teremos 1000h:0
 
  mov bx,0x1000
  mov es,bx
 
  mov bx,0
 
  int 13h       ; Interrupção do disco
  jc .diskRead  ; Em caso de erro, tenta de novo
 
jmp 1000h:0;    ; Aqui nós pulamos para o nosso código que acaba de ser
                ; carregado na memória
 
hang:
  jmp hang      ; Loop infinito
 
drv db 0
msg db 'Chamando programa do HD',13,10,0
 
times 512-($-$$)-2 db 0   ; Completa o espaço do setor que sobra com 0s
 
DW 0xAA55                 ; Assinatura do setor de boot nos ultimos dois bytes

Esse código então, como da para perceber, lê o código do disco, coloca o código na posição de memória 1000h:0 e pula para lá para começar a execução. Bom, precisamos agora então do programa que desejamos ler do disco e executar. Esse programa, assim como o primeiro exemplo vai simplesmente imprimir uma mensagem na tela.

;  Programa.asm
 
  mov ax, 1000h           ; Atualizar os registros de segmentos
 
  mov ds, ax
  mov es, ax
  mov si, msg             ; Mensagem em SI
 
putstr:
  lodsb
  or al,al
  jz hang
 
  mov ah,0x0E
  mov bx,0x0007
  int 0x10
 
  jmp putstr
 
hang:
  jmp hang
 
msg     db 'Bla bla bla!',13,10,0

Nesse programa nós atualizamos os registros de segmento para o mesmo endereço onde carregamos o programa do nosso HD, e executamos a rotina basica de imprimir a mensagem na tela, e entrar em loop infinito.
Para executar esse exemplo, os paso são parecidos. Vejamos:

$ nasm boot2.asm -f bin -o boot2.bin
$ nasm programa.asm -f bin -o programa.bin
$ qemu-img create ./boot2.img -f qcow 1M
$ dd if=boot2.bin of=./boot2.img
$ dd if=programa.bin of=./boot2.img bs=512 seek=1
$ qemu ./boot.img -m 16

O comando '$ dd if=programa.bin of=./boot2.img bs=512 seek=1' coloca o nosso código no segundo setor do disco, pois conforme definimos em nosso código, é la que nosso bootstrap está esperando encontra-lo.

Conclusão
Bom pessoal, é isso ai. Como o tpitulo disse, é um bootstrap simples, apenas para dar um gostinho de como funciona, e trazer mais animo para maiores pesquisas.
Espero que tenham gostado e que possa ser útil para alguém.

Até o próxmo :D

28Oct/070

Introdução

O objetivo aqui é bastante simples. Demonstrar códigos diversos, demonstrar seu funcionamento e procurar trazer de forma simples e prática o conhecimento. Não pretendo colocar apenas código em uma única linguagem específica, ou sobre um tópico específico; irei escrever sobre aquilo que estiver pesquisando no momento, ou que venha a ser de meu interesse.

Sinta-se a vontade para mandar sugestões ou comentários!

Regards,

Filed under: Outros No Comments
   

Pages

Categories

Blogroll

Archive

Meta