public void read(FileReader dbfFile) throws IOException, InvalidDbfFileException {
        // Type of reader
        int fileType = dbfFile.readByte();
        if (fileType != 0x03) {
            throw new InvalidDbfFileException("Unsupported DBF reader Type " + fileType);
        }

        // Update date
        int year = (int) dbfFile.readByte();
        int month = (int) dbfFile.readByte();
        int day = (int) dbfFile.readByte();
        Calendar cal = Calendar.getInstance();
        cal.set(year + 1900, month, day);
        updateDate = cal.getTime();

        // Number of records
        int numRecordsInt = dbfFile.readLittleEndianInt(); // signed integer
        numberOfRecords = ByteUtils.getUnsigned(numRecordsInt); // unsigned long

        // Length of the header structure
        headerLength = dbfFile.readLittleEndianUnsignedShort();

        // Length of a record
        recordLength = dbfFile.readLittleEndianShort();

        // Skip everything till the language driver ID.
        dbfFile.skipBytes(17);

        // Language driver ID.
        languageDriverId = dbfFile.readByte();
        tryToSetEncoding();

        // Skip two reserved bytes (30. and 31. byte).
        dbfFile.skipBytes(2);

        // Calculate the number of fields in the header
        int nNumFields = (headerLength - fileDescriptorSize) / columnDescriptorSize;

        // Offset from start of record, start at 1 because that's the delete flag
        int dataOffset = 1;

        // Read all of the header records
        fields = new ArrayList<DbfColumn>(nNumFields);
        for (int i = 0; i < nNumFields; i++) {
            /**
             * read the field name.
             * field name: 10 8-bit characters, ASCII (terminated by 00h)
             */
            String sFieldName = dbfFile.readChars(11, configuration.getEncodingName());

            // read the field type
            byte fieldType = dbfFile.readByte();
            char cDbaseType = (char) fieldType;

            // read the field data address, offset from the start of the record.
            int nFieldDataAddress = dbfFile.readLittleEndianInt();

            // read the field length in bytes
            // if field type is char, then read FieldLength and Decimal count as one number to allow char fields to be
            // longer than 256 bytes (ASCII char). This is the way Clipper and FoxPro do it, and there is really no downside
            // since for char fields decimal count should be zero for other versions that do not support this extended functionality.\
            int fieldLength = 0;
            int nDecimals = 0;
            if (cDbaseType == 'C' || cDbaseType == 'c') {
                //treat decimal count as high byte
                fieldLength = dbfFile.readLittleEndianShort();
            } else {
                //read field length as an unsigned byte.
                fieldLength = (int) dbfFile.readByte();

                //read decimal count as one byte
                nDecimals = (int) dbfFile.readByte();
            }

            // read the reserved bytes
            dbfFile.skipBytes(14);

            // create and add field to collection
            fields.add(new DbfColumn(sFieldName, DbfColumn.getDbaseType(cDbaseType), fieldLength, nDecimals, dataOffset));

            // add up address information, you can not trust the address recorded in the DBF file...
            dataOffset += fieldLength;
        }

        // Last byte is a marker for the end of the field definitions.
        dbfFile.skipBytes(1);

        // read any extra header bytes... move to first record
        // equivalent to reader.BaseStream.Seek(headerLength, SeekOrigin.Begin) except that we are not using the seek function since
        // we need to support streams that can not seek like web connections.
        int extraReadBytes = headerLength - (fileDescriptorSize + (columnDescriptorSize * fields.size()));
        if (extraReadBytes > 0) {
            dbfFile.skipBytes(extraReadBytes);
        }

        //if the stream is not forward-only, calculate number of records using file size,
        //sometimes the header does not contain the correct record count
        //if we are reading the file from the web, we have to use readNext() functions anyway so
        //Number of records is not so important and we can trust the DBF to have it stored correctly.
        if (numberOfRecords == 0) {
                //notice here that we subtract file end byte which is supposed to be 0x1A,
                //but some DBF files are incorrectly written without this byte, so we round off to nearest integer.
                //that gives a correct result with or without ending byte.
            if (recordLength > 0) {
                numberOfRecords = Math.round(((double) (dbfFile.length() - headerLength - 1) / recordLength));
            }
        }

        // Lock header since it was read from a file. we don't want it modified because that would corrupt the file.
        // User can override this lock if really necessary by calling #unlock() method.
        locked = true;

        // Clear dirty bit
        isDirty = false;
    }