Как прочитать NSInputStream с UTF-8?

Я пытаюсь прочитать большой файл в iOS, используя NSInputStream, чтобы разделить строку файлов на новые строки (я не хочу использовать componentsSeparatedByCharactersInSet, так как он использует слишком много памяти).

Но поскольку не все строки кажутся кодированными UTF-8 (поскольку они могут отображаться как ASCII, те же байты), я часто получаю предупреждение Incorrect NSStringEncoding value 0x0000 detected. Assuming NSASCIIStringEncoding. Will stop this compatiblity mapping behavior in the near future..

Мой вопрос: есть ли способ подавить это предупреждение, например. установка флага компилятора?

Кроме того: можно ли добавить/объединить два чтения буфера, поскольку чтение из потока байтов, затем преобразование буфера в строку, а затем добавление строки может привести к повреждению строки?

Ниже приведен пример метода, который демонстрирует, что преобразование байта в строку отбрасывает первую и вторую половину символа UTF-8 как недопустимые.

- (void)NSInputStreamTest {
  uint8_t testString[] = {0xd0, 0x91}; // @"Б"

  // Test 1: Read max 1 byte at a time of UTF-8 string
  uint8_t buf1[1], buf2[1];
  NSString *s1, *s2, *s3;
  NSInteger c1, c2;
  NSInputStream *inStream = [[NSInputStream alloc] initWithData:[[NSData alloc] initWithBytes:testString length:2]];

  [inStream open];
  c1 = [inStream read:buf1 maxLength:1];
  s1 = [[NSString alloc] initWithBytes:buf1 length:1 encoding:NSUTF8StringEncoding];
  NSLog(@"Test 1: Read %d byte(s): %@", c1, s1);
  c2 = [inStream read:buf2 maxLength:1];
  s2 = [[NSString alloc] initWithBytes:buf2 length:1 encoding:NSUTF8StringEncoding];
  NSLog(@"Test 1: Read %d byte(s): %@", c2, s2);
  s3 = [s1 stringByAppendingString:s2];
  NSLog(@"Test 1: Concatenated: %@", s3);
  [inStream close];

  // Test 2: Read max 2 bytes at a time of UTF-8 string
  uint8_t buf4[2];
  NSString *s4;
  NSInteger c4;
  NSInputStream *inStream2 = [[NSInputStream alloc] initWithData:[[NSData alloc] initWithBytes:testString length:2]];

  [inStream2 open];
  c4 = [inStream2 read:buf4 maxLength:2];
  s4 = [[NSString alloc] initWithBytes:buf4 length:2 encoding:NSUTF8StringEncoding];
  NSLog(@"Test 2: Read %d byte(s): %@", c4, s4);
  [inStream2 close];
}

Выход:

2013-02-10 21:16:23.412 Test[11144:c07] Test 1: Read 1 byte(s): (null)
2013-02-10 21:16:23.413 Test[11144:c07] Test 1: Read 1 byte(s): (null)
2013-02-10 21:16:23.413 Test[11144:c07] Test 1: Concatenated: (null)
2013-02-10 21:16:23.413 Test[11144:c07] Test 2: Read 2 byte(s): Б

person Kreisquadratur    schedule 10.02.2013    source источник
comment
Итак, какой у вас вопрос?   -  person Mick MacCallum    schedule 10.02.2013
comment
@ 0x7fffffff Я добавил вопрос, извините. Может быть и двукратным.   -  person Kreisquadratur    schedule 10.02.2013


Ответы (2)


Прежде всего, в строке: s3 = [s1 stringByAppendingString:s2]; вы пытаетесь объединить значения «nil». Результатом также будет «ноль». Итак, вы можете захотеть объединить байты вместо строк:

uint8_t buf3[2];
buf3[0] = buf1[0];
buf3[1] = buf2[0];
s3 = [[NSString alloc] initWithBytes:buf3 length:2 encoding:NSUTF8StringEncoding];

Выход:

2015-11-06 12:57:40.304 Test[10803:883182] Test 1: Read 1 byte(s): (null)
2015-11-06 12:57:40.305 Test[10803:883182] Test 1: Read 1 byte(s): (null)
2015-11-06 12:57:40.305 Test[10803:883182] Test 1: Concatenated: Б

Во-вторых, длина символа UTF-8 может составлять [1..6] байт.

(1 byte)   0aaa aaaa         //if symbol lays in 0x00 .. 0x7F (ASCII)
(2 bytes)  110x xxxx 10xx xxxx
(3 bytes)  1110 xxxx 10xx xxxx 10xx xxxx
(4 bytes)  1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
(5 bytes)  1111 10xx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx
(6 bytes)  1111 110x 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx

Итак, если вы собираетесь читать необработанные байты NSInputStream, а затем переводить их в UTF-8 NSString, вы, вероятно, захотите читать байт за байтом из NSInputStream, пока не получите действительную строку:

#define MAX_UTF8_BYTES 6
NSString *utf8String;
NSMutableData *_data = [[NSMutableData alloc] init]; //for easy 'appending' bytes

int bytes_read = 0;
while (!utf8String) {
    if (bytes_read > MAX_UTF8_BYTES) {
        NSLog(@"Can't decode input byte array into UTF8.");
        return;
    }
    else {
        uint8_t byte[1];
        [_inputStream read:byte maxLength:1];
        [_data appendBytes:byte length:1];
        utf8String = [NSString stringWithUTF8String:[_data bytes]];
        bytes_read++;
    }
}
person Artem Zaytsev    schedule 06.11.2015

ASCII (и, следовательно, символ новой строки) является подмножеством UTF-8, поэтому конфликтов быть не должно.

Должна быть возможность разделить поток по символам новой строки, как в простом потоке ASCII. Затем вы можете преобразовать каждый фрагмент («строку») в NSString, используя UTF-8.

Вы уверены, что ошибки кодирования не настоящие, т. е. что ваш поток действительно может содержать ошибочные символы по отношению к кодировке UTF-8?

Отредактировано для добавления из комментариев:

Это предполагает, что строки состоят из достаточно небольшого количества символов, чтобы сохранить всю строку в памяти до преобразования из UTF-8.

person Monolo    schedule 10.02.2013
comment
Вы были правы насчет второй части: строка действительно содержала ошибочные символы/байты. Однако первая часть не тривиальна, так как вы можете разделить символ UTF-8 и потерять его половинки при преобразовании в NSString. - person Kreisquadratur; 11.02.2013
comment
На самом деле символ новой строки нельзя использовать для составления многобайтового символа. UTF-8 гарантирует, что ни один символ ASCII не используется в мультибайте. Но разделение может произойти, потому что размер буфера может быть меньше, чем строка для чтения. - person Kreisquadratur; 11.02.2013
comment
@Kreisquadratur А, я предполагал, что вы можете прочитать целую строку, прежде чем ее расшифровывать. - person Monolo; 11.02.2013