Mòdul 3

Pràctica 6: Caràcters, nombres, codis...
Tornar presentació tema
Pràctica 1 Pràctica 2 Pràctica 3 Pràctica 4 Pràctica 5 Pràctica 3  
     
  El codi ASCII  
     
 
 
     
  Allà pel període pleistocè, quan hom aconseguia per primera vegada introduir informació a un ordinador mitjançant un teclat de màquina d'escriure (què, què és això? busca-ho en una enciclopèdia, si et plau!, o bé demana-li a Google informació quant a "Remington", "Underwood" o "Olivetti") , és perquè hom havia codificat els caràcters d'aquest teclat, és a dir, perquè havia atribuït a cadascun dels caràcters un codi, això és, un nombre enter, que l'ordinador podia desar i manipular amb facilitat.  
     
 

Ben aviat, amb el propòsit que la informació fós intercanviable entre sistemes informàtics diversos, s'arribà a un estàndar per a codificacions, mitjançant l'establiment del codi ASCII ("American Standard Code for Information Interchange"). És aquest:

 
     
 
 
     
  En realitat, el codi ASCII comença a 0 i acaba a 127. Els caràcters de la taula anterior, del 32 (espai) al 126 són els caràcters imprimibles, mentre els que van del 0 al 31 i el 127 són caràcters no imprimibles i descriuen certes accions o ordres per a dispositius d'impressió: pots imaginar-los com codificacions de les tecles que, directament, no imprimeixen res. Així, per exemple,  
     
 
El codi  9  correspon al caràcter d'escapament  \t  (tabulador)
El codi  10  correspon al caràcter d'escapament  \n  (canvi de línia)
 
     
  Així, doncs, la paraula "Dilluns", un cop codificada, es converteix en la llista de nombres 68,105,108,108,117,110,115.  
     
  No tots eren americans, però...  
     
  Pero, però... no hi ha accents! ni tampoc hi és la "ç"! I si volem que les màquines ens entenguin i ens escriguin en grec, o rus, o japonès? Ja es veu que els creadors de la codificació ASCII es basaren en el teclat d'una màquina d'escriure en anglès, i només en aquest...  
     
  Encara hi havia, però, cert marge: l'espai de memòria recessari per enmagatzemar un caràcter és d'un byte, o sigui, vuit bits. Això dóna 28 = 256 possibilitats, de les quals només se n'havien gastat 128, del 0 al 127. Aparegueren llavors les taules ASCII exteses, en les quals, l'atribució de caràcters (caràcters especials, en deien) des del 128 al 175 era variable, en funció de les necessitats (à, ü, ç, ñ, etc.) i la resta, fins el 255, consistia en símbols diversos i trossos de línies i cantonades per enmarcar taules.  
     
  La situació actual: la codificació Unicode  
     
  Ja es veu que les taules ASCII exteses no poden ser capaces de respondre al munt enorme d'alfabets i sistemes d'escriptura que els humans solem fer servir. Avui s'ha imposat l'estàndar Unicode, que "proporciona un nombre únic per a cada caràcter, sense que importi la plataforma, sense que importi el programa, sense que importi el idioma." segons s'estableix a
 
     
 
http://www.unicode.org/standard/translations/spanish.html
 
     
  El mètode és simple: tantes taules de codificació com alfabets hi hagi, tot mantenint la codificació per a caràcters compartits per diverses taules. Pots veure aquestes taules a  
     
 
http://www.unicode.org/charts/
 
     
  Quina taula s'ha de fer servir a cada cas és una informació que resideix a la màquina de l'usuari, i tot el programari que hi hagi (i, per tant, Java) escull automàticament la codificació adequada a partir d'aquesta informació.  
     
  En el nostre cas, de llengües que usen l'alfabet llatí, la codificació Unicode dels caràcters sense accentuar i d'alguns símbols usuals és la mateixa que al codi ASCII entre el 32 i el 126. Aquesta n'és la taula  
     
 
 
     
  Java i Unicode: bytes i chars, codis, caràcters i cadenes  
     
  Mentre el codi ASCII era plenament vigent, les coses eren senzilles: una cadena (string) no era més que una matriu amb els codis dels caràcters de la cadena:  
     
 
"Dilluns"={68,105,108,108,117,110,115}; // No vàlid a Java!
 
     
  Alguns llenguatges, C per exemple, afegeixen un 0 al final com a marcador de final de cadena:  
     
 
"Dilluns"={68,105,108,108,117,110,115,0}; // No vàlid a Java!
 
     
  Però ara, amb Unicode, quan 68 no és sempre una 'D', Java no en pot fer un tractament tan simple.  
 
  • D'una banda hi ha els codis, és a dir, els nombres. Corresponen al tipus primitiu byte. Un valor de tipus byte ocupa això, un byte, és a dir, vuit bits. Per tant, hi ha 256 valors possibles (mira més amunt). Java els disposa des del -128 al 127.
 
 
byte unByte=12; // Atenció! entre -128 i 127
 
 
  • Després hi ha els caràcters, és a dir, les lletres i símbols. Corresponen al tipus primitiu char i un valor de tipus char és ja una lletra, que correspon a un valor del tipus byte segons la codificació activa:

 
 
char unChar='C';    // Atenció! són apòstrofs (')
                      //no cometes (")
char unAltreChar=67 // El valor és el caràcter 'C'
                      // Atenció! entre -128 i 127
byte unByte='C';    // El valor és 67
 
 
  • Finalment, una cadena (string) és un objecte de la classe java.lang.String que es construeix a partir d'una matriu de chars o de bytes:
 
 
char[] matDeChars={'D','i','l','l','u','n','s'};
char[] matDeBytes={68,105,108,108,117,110,115};
String cadena_1=new String(matDeChars);
            //El valor és "Dilluns"
String cadena_2=new String(matDeBytes);
             // El valor és "Dilluns"
 
 

a part d'allò que ja saps:

 
 

String cadena_3="Dilluns";

 
     
  Tampoc és tan complicat, però, eh? bytes i chars són perfectament intercanviables: els bytes són nombres i els chars són el mateix, però tenint en compte la codificació disponible.  
     
  La classe java.lang.String conté un munt de mètodes per manipular els chars i bytes d'una cadena:  
     
 
  • public String (byte[] bytes). Mètode constructor per a objectes de la classe java.lang.String. Construeix la cadena a partir de la descodificació dels bytes de la matriu bytes.

  • public String (char[] chars). Mètode constructor per a objectes de la classe java.lang.String. Construeix la cadena a partir dels chars de la matriu chars.

  • public char charAt(int index). Retorna el char que és a la posició índex (el primer caràcter té index 0).

  • public byte[] getBytes(). Retorna la matriu de bytes corresponents als caràcters que formen la cadena, descodificats.

  • public int indexOf(int ch). Retorna la posició de la primera vegada que apareix el caràcter de descodificació el nombre ch.

  • public int indexOf(int ch,int index). El mateix que l'anterior, però la cerca no comença al principi de la cadena sinó a partir de la posició index.

  • public int lastIndexOf(int ch). Retorna la posició de l'última vegada que apareix el caràcter de descodificació el nombre ch.

  • public int lastIndexOf(int ch,int index). El mateix que l'anterior, però la cerca no comença al final de la cadena sinó a partir de la posició index.

  • public String replace(char vellChar,char nouChar). Canvia tots els caràcters vellChar que contingui la cadena pel caràcter nouChar.

  • public char[] toCharArray(). Retorna la matriu de chars que formen la cadena.
 
     
  Un programa d'encriptació:  
     
Ara podem posar en funcionament tot això i, de passada, respondre a la pregunta que, segur, t'estàs fent: de què serveix tota aquesta complicació? quina gràcia té que els caràcters, les lletres, siguin, en realitat, nombres?  
     
  L'art de transmetre missatges que siguin completament inintel·ligibles per a altres persones que no siguin l'emissor i el receptor és molt antic i de gran utilitat, per exemple, en la guerra. Juli Cèsar feia servir aquest mètode: després d'escriure el missatge:  
     
 
GALLI IRRVMATORES SVNT
 
     
  (que no hauria agradat gens a Astèrix) substituia cadascuna de les lletres per la que estava dos llocs més endavant a l'alfabet. El resultat és:  
     
 
ICNNL LTTYOCXQTGV VYPX
 
     
  (A l'alfabet llatí clàssic no hi ha ni "J" ni "U").  
     
  Podem fer una classe que faci precisament aixó. Obre un nou projecte que es digui Encriptacio i afegeix-li aquesta classe:  
     

/**
 * Encriptació i desencriptació pel mètode del Cèsar restringit
 * als caràcters ASCII imprimibles (32 a 126).
 *
 * @author Carles Romero
 * @version 2004/01/10
 */

public class CesarASCII {

    /**
     * La translació que sofriran els caràcters en encriptar (+)
     * i/o desencriptar (-).
     */

    int translacio=0;

    /**
     * Mètode constructor per a aquesta encriptació del Cèsar.
     * @param laTranslacio la translació que sofriran els
     * caràcters en encriptar (+) i/o desencriptar (-)
     */

    public CesarASCII (int laTranslacio) { // constructor
        translacio=laTranslacio;
    }

 
     
  De moment, només hi ha la variable d'instància translacio (inicialitzada a 0) i un mètode constructor en el qual es demana, com a paràmetre, el valor d'aquesta variable. Ja pots compilar-la, construir-ne un objecte i inspeccionar-la...  
     
  Ara hi posaràs el mètode que fa la feina: com que l'encriptació consisteix en sumar al byte que codifica una certa lletra el valor de translació i desencriptar consisteix en restar-li-lo, podem fer que int +1 sigui l'indicador d'encriptació i int -1 el de desencriptació. El mètode es pot dir:  
     
 
    private byte trasllada (byte bIn,int mesOmenys) {
 
     
  byte bIn és el byte a traslladar i int mesOmenys és +1 o -1 segons convingui.  
     
  El primer pas és traslladar l'origen de bytes a 0 (ara és a 32). És fàcil:  
     
 
        int iBIn=(int)bIn-32; // Fer començar la codificació a 0
 
     
  (Observa que, per tal que les coses funcionin, cal fer càsting del tipus byte al tipus int)  
     
  Després, com que del caràcter 32 al caràcter 126 (que són els caràcters imprimibles ASCII) n'hi ha 95, ens hem d'assegurar que la variable int mou, que és la que se sumarà al byte,  
 
  • no superi 95,
 
 
        int mou=(mesOmenys*translacio)%95;
 
 
  • i que sigui positiva,
 
 
            if (mou<0) { // Assegurar que mou >= 0
                mou=mou+95;
            }
 
  Ara ja pots fer la translació, tot vigilant que el resultat no passi de 95:  
     
 
        iBIn=(iBIn+mou)%95;
 
     
  Finalment, ja pots retornar el valor, després de tornar a posar l'origen a 32 i fer el corresponent càsting:  
     
 
        return (byte)iBIn;
 
     
  La classe quedarà ara així:  
     

/**
 * Encriptació i desencriptació pel mètode del Cèsar restringit
 * als caràcters ASCII imprimibles (32 a 126).
 *
 * @author Carles Romero
 * @version 2004/01/10
 */
public class CesarASCII {

    /**
     * La translació que sofriran els caràcters en encriptar (+)
     * i/o desencriptar (-).
     */
    int translacio=0;

    /**
     * Mètode constructor per a aquesta encriptació del Cèsar.
     * @param laTranslacio la translació que sofriran els
     * caràcters en encriptar (+) i/o desencriptar (-)
     */
    public CesarASCII (int laTranslacio) { // constructor
        translacio=laTranslacio;
    }

    private byte trasllada (byte bIn,int mesOmenys) {
        int iBIn=(int)bIn-32; // Fer començar la codificació a 0
        int mou=(mesOmenys*translacio)%95; // del 32 al 126 hi
                                           //  ha 95 caràcters

            if (mou<0) { // Assegurar que mou >= 0
                mou=mou+95;
            }
        iBIn=(iBIn+mou)%95;
        iBIn=iBIn+32; // Tornar a la codificació ASCII standard
        return (byte)iBIn;
    }


}
 
     
  El pas següent és fer aquesta feina sobre tots els caràcters d'una cadena. Necessites un altre mètode que ho faci:  
     
 
    private String encriptaOdesencripta
        (String stringEntrada, int encriptar) {
 
     
  El paràmetre encriptar serà, com abans, +1 o -1 segons calgui encriptar o desencriptar. Caldrà obtenir la matriu de bytes que codifiquen la cadena d'entrada stringEntrada,  
     
 
                byte[] bytes=stringEntrada.getBytes();
 
     
  i fer l'operació de trasllació, un per un:  
     
 
                int quants=bytes.length;
                    for (int i=0;i<quants;i++) {
                        bytes[i]=trasllada(bytes[i],encriptar);
                    }
 
     
  tot demanant el mètode private byte trasllada (byte bIn,int mesOmenys) que has escrit abans. Ara ja només cal construir la cadena ja encriptada:  
     
 
                stringSortida=new String(bytes);
 
     
  i retornar-la. La classe, amb el nou mètode incorporat, és així:  
     

/**
 * Encriptació i desencriptació pel mètode del Cèsar restringit
 * als caràcters ASCII imprimibles (32 a 126).
 *
 * @author Carles Romero
 * @version 2004/01/10
 */
public class CesarASCII {

    /**
     * La translació que sofriran els caràcters en encriptar (+)
     * i/o desencriptar (-).
     */
    int translacio=0;

    /**
     * Mètode constructor per a aquesta encriptació del Cèsar.
     * @param laTranslacio la translació que sofriran els
     * caràcters en encriptar (+) i/o desencriptar (-)
     */
    public CesarASCII (int laTranslacio) { // constructor
        translacio=laTranslacio;
    }

    private byte trasllada (byte bIn,int mesOmenys) {
        int iBIn=(int)bIn-32; // Fer començar la codificació a 0
        int mou=(mesOmenys*translacio)%95; // del 32 al 126 hi
                                           // ha 95 caràcters
            if (mou<0) { // Assegurar que mou >= 0
                mou=mou+95;
            }
        iBIn=(iBIn+mou)%95;
        iBIn=iBIn+32; // Tornar a la codificació ASCII standard
        return (byte)iBIn;
    }

    private String encriptaOdesencripta
            (String stringEntrada,int encriptar) {
        String stringSortida=null;
            if (stringEntrada!=null) {
                byte[] bytes=stringEntrada.getBytes();
                int quants=bytes.length;
                    for (int i=0;i<quants;i++) {
                        bytes[i]=trasllada(bytes[i],encriptar);
                    }
                stringSortida=new String(bytes);
            }
        return stringSortida;
    }


}
 
     
  Observa la precacució de comprovar si stringEntrada és efectivament una cadena ja construïda!  
     
  Ara ja només queda escriure els dos mètodes public per encriptar i desencriptar:  
     

/**
 * Encriptació i desencriptació pel mètode del Cèsar restringit
 * als caràcters ASCII imprimibles (32 a 126).
 *
 * @author Carles Romero
 * @version 2004/01/10
 */
public class CesarASCII {

    /**
     * La translació que sofriran els caràcters en encriptar (+)
     * i/o desencriptar (-).
     */
    int translacio=0;

    /**
     * Mètode constructor per a aquesta encriptació del Cèsar.
     * @param laTranslacio la translació que sofriran els
     * caràcters en encriptar (+) i/o desencriptar (-)
     */
    public CesarASCII (int laTranslacio) { // constructor
        translacio=laTranslacio;
    }

    /**
     * Encriptació segons aquesta encriptació del Cèsar.
     * @param string la cadena a encriptar
     * @return la cadena encriptada
     */

    public String encripta (String string) {
        return encriptaOdesencripta(string,1);
    }

    /**
     * Desencriptació segons aquesta encriptació del Cèsar.
     * @param string la cadena a desencriptar
     * @return la cadena desencriptada
     */

    public String desEncripta (String string) {
        return encriptaOdesencripta(string,-1);
    }


    private byte trasllada (byte bIn,int mesOmenys) {
        int iBIn=(int)bIn-32; // Fer començar la codificació a 0
        int mou=(mesOmenys*translacio)%95; // del 32 al 126 hi
                                           //  ha 95 caràcters

            if (mou<0) { // Assegurar que mou >= 0
                mou=mou+95;
            }
        iBIn=(iBIn+mou)%95;
        iBIn=iBIn+32; // Tornar a la codificació ASCII standard
        return (byte)iBIn;
    }

    
private String encriptaOdesencripta
            (String stringEntrada,int encriptar) {
        String stringSortida=null;
            if (stringEntrada!=null) {
                byte[] bytes=stringEntrada.getBytes();
                int quants=bytes.length;
                    for (int i=0;i<quants;i++) {
                        bytes[i]=trasllada(bytes[i],encriptar);
                    }
                stringSortida=new String(bytes);
            }
        return stringSortida;
    }

}
 
     
Apa, a compilar i provar-la!  
     
  Un exercici...  
     
Ara hauries de ser capaç d'escriure una classe que posés en relació caràcters i codis i que et fes les taules del principi d'aquesta pràctica. L'esquelet de la classe pot ser aquest:  
     
 
/**
* Codificació de caràcters.
*
* @author (el teu nom)
* @version (un número de versió o la data)
*/

public final class CodisCaracters {

    /**
     * Caràcter corresponent a un cert codi.
     * @return el caràcter corresponent al codi que es dóna
     */

    public static char Codificacio (int codi) {
        // posa el teu codi aquí...
    }

    /**
     * Codi corresponent a un cert caràcter.
     * @return el codi corresponent al caràcter que es dóna
     */

    public static int Codificacio (char caracter) {
        // posa el teu codi aquí...
    }

    /**
     * Codi corresponent al primer caràcter d'una cadena.
     * @return el codi corresponent al primer caràcter de la
     * cadena que es dóna
     */

    public static int Codificacio (String lletra) {
        // posa el teu codi aquí...
    }

    /**
     * Impressió d'una llista de parelles codi/caràcter a partir
     * del caràcter 32 (espai).
     * @param finsElCaracter el límit superior de la llista (ha de
     * ser un nombre enter múltiple de 4
     */

    public static void llistaCodificacio (int finsElCaracter) {
        // posa el teu codi aquí...
    }

}

 
     
  Els tres primers mètodes no són gens difícils i, amb una sola línia de codi a cadascún d'ells n'hi hauria d'haver prou: tot consisteix en un bon ús del càsting...  
     
  En canvi, el mètode public static void llistaCodificacio (int finsElCaracter) é