//public static void Write(Stream stream, SpecialPostingBlockEntry entry) //{ // TermLink.Write(stream, entry.FirstPosting ?? new TermLink()); // stream.WriteInt64Network(entry.Offset); //} public static int Write(byte[] buffer, SpecialPostingBlockEntry entry, int offset) { var size = TermLink.Write(buffer, entry.FirstPosting, offset); offset += size; buffer.WriteInt64Network(entry.Offset, offset); size += 8; return(size); }
/// <summary> /// ibatrak очистка полей с количеством ссылок в заголовке записи /// </summary> public static void ClearItems(Stream stream, long offset) { //в заголовке проставляем 0 записей, сами ссылки не трогаем //ирбис так делает, проверено var ifpRecord = ReadLeader(stream, offset); //в записи со спец блоком зануляется только TotalLinkCount и оба поля во вложенных записях var special = ifpRecord.Special; if (!special) { ifpRecord.BlockLinkCount = 0; } ifpRecord.TotalLinkCount = 0; WriteLeader(stream, ifpRecord, offset); if (special) { //начитаем первую запись из спец блока, в ней ссылка на первую запись обыкновенного формата //остальные записи спец блока здесь не нужны var specBlock = new SpecialPostingBlockEntry[ifpRecord.Capacity]; var specBlockBuffer = new byte[ifpRecord.Capacity * SpecialPostingBlockEntry.RecordSize]; var fileOffset = stream.Position; if (stream.Read(specBlockBuffer, 0, specBlockBuffer.Length) != specBlockBuffer.Length) { throw new IOException(); } for (int i = 0; i < ifpRecord.Capacity; i++) { var entry = SpecialPostingBlockEntry.Read(specBlockBuffer, i * SpecialPostingBlockEntry.RecordSize); entry.FileOffset = fileOffset + i + SpecialPostingBlockEntry.RecordSize; specBlock[i] = entry; } var nonEmptyEntries = specBlock.Where(e => e.Offset > 0).Count(); if (nonEmptyEntries != ifpRecord.BlockLinkCount) { throw new InvalidOperationException("Ошибка чтения ifp offset=" + offset); } var nestedRecords = new IfpRecord[ifpRecord.BlockLinkCount]; for (int i = 0; i < ifpRecord.BlockLinkCount; i++) { if (specBlock[i].Offset <= 0 || (i > 0 && nestedRecords[i - 1].FullOffset != specBlock[i].Offset)) { throw new InvalidOperationException("Ошибка чтения ifp offset=" + offset); } var nestedRecord = ReadLeader(stream, specBlock[i].Offset); nestedRecord.BlockLinkCount = 0; nestedRecord.TotalLinkCount = 0; nestedRecords[i] = nestedRecord; WriteLeader(stream, ifpRecord, specBlock[i].Offset); } } }
//public static SpecialPostingBlockEntry Read(Stream stream) //{ // var result = new SpecialPostingBlockEntry // { // FileOffset = stream.Position, // FirstPosting = TermLink.Read(stream), // Offset = stream.ReadInt64Network() // }; // return result; //} public static SpecialPostingBlockEntry Read(byte[] buffer, int offset) { //ibatrak чтение из буфера var result = new SpecialPostingBlockEntry { FirstPosting = TermLink.Read(buffer, offset), Offset = buffer.ReadInt64Network(offset + TermLink.RecordSize) }; return(result); }
/// <summary> /// Read the record. /// </summary> public static IfpRecord Read ( [NotNull] Stream stream, long offset ) { stream.Position = offset; IfpRecord result = new IfpRecord { LowOffset = stream.ReadInt32Network(), HighOffset = stream.ReadInt32Network(), TotalLinkCount = stream.ReadInt32Network(), BlockLinkCount = stream.ReadInt32Network(), Capacity = stream.ReadInt32Network() }; // ibatrak чтение вложенных записей в спец блоке // Специальный формат записи .ifp // В этом случае первой записью является специальный блок, // который представляет собой заголовок (обыкновенного формата), // в котором смещения имеют специальные значения = -1001 if (result.LowOffset == -1001 && result.HighOffset == -1001) { // irbis64.dll делает так // читает первые 24 байта блока спец ссылок // (это 2 записи) // записи с реальными адресами идут через одну // берет вторую запись, адрес из нее в файле // и читает записеи IFP // по количеству result.BlockLinkCount // первую запись спец блока просто пропускаем, // читаем сразу вторую stream.Position += 12; SpecialPostingBlockEntry entry = new SpecialPostingBlockEntry { Posting = stream.ReadInt32Network(), Low = stream.ReadInt32Network(), High = stream.ReadInt32Network() }; stream.Position = entry.Offset; IfpRecord[] nestedRecords = new IfpRecord[result.BlockLinkCount]; for (int i = 0; i < result.BlockLinkCount; i++) { var nestedRecord = Read(stream, stream.Position); nestedRecords[i] = nestedRecord; // Last record in the list must have // negative offset values if (nestedRecord.LowOffset == -1 && nestedRecord.HighOffset == -1) { if (i != result.BlockLinkCount - 1) { throw new InvalidOperationException ( "IFP reading error" ); } break; } if (nestedRecord.FullOffset < 0) { throw new InvalidOperationException ( "IFP reading error" ); } stream.Position = nestedRecord.FullOffset; } TermLink[] links = nestedRecords .SelectMany(r => r.Links) .ToArray(); if (links.Length != result.TotalLinkCount) { throw new InvalidOperationException ( "IFP reading error" ); } result.Links.AddRange(links); return(result); } //ibatrak до сюда for (int i = 0; i < result.BlockLinkCount; i++) { TermLink link = TermLink.Read(stream); result.Links.Add(link); } return(result); }
/// <summary> /// ibatrak добавление новой или обновление существующей записи /// </summary> public static int Write(Stream stream, NodeItem item, TermLink[] links, long offset, bool padding) { int size = 0; IfpRecord ifpRecord = null; //повторное использование существующих записей long oldOffset = item.FullOffset; //если элемент новый - отметить в нем ссылку на запись ifp if (item.FullOffset == 0) { item.FullOffset = offset; } int capacity = 0; //требуется писать записи обыкновенного формата, в случае расширения старой записи bool expanding = false; if (oldOffset > 0) { bool topRecord = true; int linksCount = links.Length; //перезаписываем в цикле все записи по цепочке while (true) { //начитаем старую версию записи с нужным объемом или последнюю в цепочке ifpRecord = Read(stream, oldOffset); //в записи со спец блоком реальная вместимость записи в ссылках считается по вложенным записям capacity = ifpRecord.Special ? ifpRecord._NestedRecordLeaders.Sum(r => r.Capacity) : ifpRecord.Capacity; bool last = ifpRecord.Last; //если в старой записи хватает места, перезапишем ее if (links.Length <= capacity) { ifpRecord._changed = false; ReplaceLinks(ifpRecord, links, links.Length); //счетчик ссылок обновим ifpRecord.TotalLinkCount = links.Length; if (!ifpRecord.Special) { ifpRecord.BlockLinkCount = links.Length; } //запись в файл только при наличии изменений if (ifpRecord._changed) { Write(stream, ifpRecord, oldOffset, false /*при обновлении записей всегда padding = false*/); } links = null; } else //перезапись старой записи при расширении { expanding = true; //ирбис до упора не заполняет, при расширении делит пополам //уменьшение размеров последнего блока возможно, если //количество новых элементов плюс половина последнего блока не превышают размер блока int countToAdd = 0; //запись со спец блоком в цепочке быть не может //деление записей происходит только в последней по цепочке записи int newLinksCount = links.Length - capacity; if (!ifpRecord.Special) { //половина последнего блока + количество новых ссылок вмещается в блок, тогда делим if (last && capacity > 1 && ((int)Math.Ceiling((double)capacity / 2d) + newLinksCount) <= PostingsInBlock) { countToAdd = (int)Math.Ceiling((double)capacity / 2d); } else { countToAdd = capacity; } } else { //запись со спец блоком //половина последнего блока + количество новых ссылок вмещается в блок, тогда делим if (((int)Math.Ceiling((double)ifpRecord._NestedRecordLeaders[ifpRecord._NestedRecordLeaders.Length - 1].Capacity / 2d) + newLinksCount) <= PostingsInBlock) { //в записи со спец блоком последняя делится пополам countToAdd = capacity - (int)Math.Ceiling((double)ifpRecord._NestedRecordLeaders[ifpRecord._NestedRecordLeaders.Length - 1].Capacity / 2d); } else { countToAdd = capacity; } } //if (!last) // countToAdd = capacity; //else //{ // if (!ifpRecord.Special) // countToAdd = capacity > 1 ? (int)Math.Ceiling((double)capacity / 2d) : capacity; // else //в записи со спец блоком прследняя делится пополам // countToAdd = capacity - (int)Math.Ceiling((double)ifpRecord._NestedRecordLeaders[ifpRecord._NestedRecordLeaders.Length - 1].Capacity / 2d); //} //если в старой записи не хватает места, перезапишем в нее сколько влезет //остальное запишем в новую запись ifpRecord._changed = false; ReplaceLinks(ifpRecord, links, countToAdd); //ifpRecord.Links.Clear(); //ifpRecord.Links.AddRange(links.Take(countToAdd).ToArray()); //счетчик ссылок обновим //верхняя в цепочке запись получает в заголовке полное количество ссылок if (topRecord) { ifpRecord.TotalLinkCount = linksCount; } else { ifpRecord.TotalLinkCount = countToAdd; } //в обычной записи здесь ставится количество записей в блоке if (!ifpRecord.Special) { ifpRecord.BlockLinkCount = countToAdd; } //так быстрее, чем ToArray Array.Copy(links, countToAdd, links, 0, links.Length - countToAdd); Array.Resize(ref links, links.Length - countToAdd); //links = links.Skip(countToAdd).ToArray(); //установка ссылки на следующую запись если мы достигли конца цепочки для обычных записей //для записи со спец блоком будет расширение спец блока и там установка ссылки на следующую запись int newBlocks = (int)Math.Ceiling((double)links.Length / (double)PostingsInBlock); //расширение записи со спец блоком идет не на одну запись, а по количеству блоков //if (last || (ifpRecord.Special && ifpRecord._NestedRecordLeaders.Length < ifpRecord.Capacity)) if (last || (ifpRecord.Special && ifpRecord._NestedRecordLeaders.Length + newBlocks <= ifpRecord.Capacity)) { SetNext(ifpRecord, offset, links, newBlocks); //SetNext(stream, ifpRecord, offset, links[0], newBlocks); } //если расширяется запись со спец блоком, то лидер записи и новый спец блок помещаются в другое место //в новом спец блоке добавляется 4 блока //else if (ifpRecord.Special && ifpRecord._NestedRecordLeaders.Length >= ifpRecord.Capacity) else if (ifpRecord.Special && ifpRecord._NestedRecordLeaders.Length + newBlocks > ifpRecord.Capacity) { var newLeader = new IfpRecord(); newLeader.Special = true; var specialBlock = ifpRecord._SpecialBlock; int count = specialBlock.Length; int newCapacity = 4 * (int)Math.Ceiling(((double)ifpRecord._NestedRecordLeaders.Length + (double)newBlocks) / 4d); Array.Resize(ref specialBlock, newCapacity); for (int i = count; i < specialBlock.Length; i++) { specialBlock[i] = new SpecialPostingBlockEntry(); } newLeader.Capacity = newCapacity; newLeader.TotalLinkCount = linksCount; //count - это количество элементов в спец блоке всего, надо количество непустых элементов //newLeader.BlockLinkCount = count /*при вызове SetNext будет инкремент*/; newLeader.BlockLinkCount = ifpRecord.BlockLinkCount /*при вызове SetNext будет инкремент*/; int newLeaderSize = LeaderSize + newLeader.Capacity * SpecialPostingBlockEntry.RecordSize; newLeader._SpecialBlock = specialBlock; newLeader._NestedRecordLeaders = ifpRecord._NestedRecordLeaders; //newLeader._NestedRecordLeaders используется для SetNext SetNext(newLeader, offset + newLeaderSize, links, newBlocks); //SetNext(stream, newLeader, offset + newLeaderSize, links[0], newBlocks); newLeader._NestedRecordLeaders = null; //в элементе надо заменить адрес старой записи item.FullOffset = offset; Write(stream, newLeader, offset, true); offset += newLeaderSize; size += newLeaderSize; } if (ifpRecord._changed) { Write(stream, ifpRecord, oldOffset, false /*при обновлении записей всегда padding = false*/); } } if (last || ifpRecord.FullOffset <= 0 || links == null || links.Length == 0) { //если будем расширять запись со спец блоком, то максимальный размер у нее не более PostingsInBlock if (ifpRecord.Special) { capacity = PostingsInBlock; } break; } oldOffset = ifpRecord.FullOffset; topRecord = false; } } //добавление новой записи if (links != null && links.Length > 0) { //при расширении записи всегда пишем записи обычного формата //здесь назначаем емкость записи //if (ifpRecord.Links.Count < MinPostingsInBlock) if (expanding || links.Length < MinPostingsInBlock) { //делаем несколько блоков только если идет расширение старой записи int blockCount = expanding ? (int)Math.Ceiling((double)links.Length / (double)PostingsInBlock) : 1; for (int i = 0; i < blockCount; i++) { var lastBlock = i == blockCount - 1; ifpRecord = new IfpRecord(); ifpRecord.Links.AddRange(blockCount == 1 ? links : links.Skip(i * PostingsInBlock).Take(PostingsInBlock)); //при расширении записи следующая запись получает столько ссылок, сколько емкость предыдущей записи + 1 int count = ifpRecord.Links.Count; int recCapacity = 0; if (lastBlock && capacity > 0) { recCapacity = Math.Max(capacity, ifpRecord.Links.Count) + 1; for (int j = count; j < recCapacity; j++) { ifpRecord.Links.Add(new TermLink()); } } else { recCapacity = count; } ifpRecord.TotalLinkCount = count; ifpRecord.BlockLinkCount = count; ifpRecord.Capacity = recCapacity; ifpRecord.Last = lastBlock; //у непоследней записи нужно установить ссылку на следующую if (!lastBlock) { ifpRecord.FullOffset = offset + /*размер ifp записи обычного формата*/ LeaderSize + count * TermLink.RecordSize; } int written = Write(stream, ifpRecord, offset, padding); offset += written; size += written; } } else { //новая запись со спецблоками только в случае создания новой, //при расширении всегда идут записи обыкновенного формата ifpRecord = new IfpRecord(); ifpRecord.Links.AddRange(links); //для новой записи здесь нужно обозначить спец блок //при создании новой записи со спец блоком ирбис не выравнивает количество записей под размер блока //если в последствии такую запись расширяют, //то добавляется еще одна запись обычного формата и дальше по обычной схеме ifpRecord.Special = true; ifpRecord.TotalLinkCount = links.Length; //в записи со спец блоком здесь количество вложенных записей ifpRecord.BlockLinkCount = (int)Math.Ceiling((double)links.Length / (double)PostingsInBlock); //в записи со спецблоками в поле емкость ставится количество вложенных записей кратное 4 //емкость выставляется кратно 4, реальное количество блоков в BlockLinkCount ifpRecord.Capacity = 4 * (int)Math.Ceiling((double)links.Length / (double)PostingsInBlock / 4d); size += Write(stream, ifpRecord, offset, padding); } } ////если элемент новый - отметить в нем ссылку на запись ifp //if (item.FullOffset == 0) // item.FullOffset = offset; return(size); }
///// <summary> ///// ibatrak установка ссылки на следующую запись ///// </summary> //private static void SetNext(Stream stream, IfpRecord ifpRecord, long next, TermLink link, int newBlocks) //{ // if (ifpRecord.FullOffset != next && next > 0) // { // var r = ifpRecord; // //поиск последней записи по цепочке // //метод вызывается только для последней записи, потребности в этом нет // //оставлено для совместимости // while (r.FullOffset > 0) // { // r = ReadLeader(stream, r.FullOffset); // } // if (r.Special) // { // //дальше работаем со вложенной записью // var nestedRecord = r._NestedRecordLeaders[r._NestedRecordLeaders.Length - 1]; // //установка ссылки на новую запись в спец блок, если есть место // if (r._NestedRecordLeaders.Length < r._SpecialBlock.Length) // { // var specialBlockEntry = r._SpecialBlock[r._NestedRecordLeaders.Length]; // if (specialBlockEntry.Offset == 0) // { // specialBlockEntry.Offset = next; // specialBlockEntry.FirstPosting = link; // //обновим счетчик блоков // r.BlockLinkCount += newBlocks; // if (ifpRecord._NestedRecordLeaders == null || !ifpRecord._NestedRecordLeaders.Contains(nestedRecord)) // { // //если запись не является вложенной, то записать спец блок // stream.Position = specialBlockEntry.FileOffset; // SpecialPostingBlockEntry.Write(stream, specialBlockEntry); // } // } // } // r = nestedRecord; // } // r.FullOffset = next; // //обновить ссылку на следующую запись в последней по цепочке записи, если она отличается от текущей // //и не содержится во вложенных // if (r != ifpRecord && (ifpRecord._NestedRecordLeaders == null || !ifpRecord._NestedRecordLeaders.Contains(r))) // WriteLeader(stream, r, r._FileOffset); // } //} /// <summary> /// Запись данных в простом формате или со спец блоками /// </summary> private static int Write(Stream stream, IfpRecord ifpRecord, long offset, bool padding) { int size = 0; byte[] buffer = null; //обработка обычной записи if (!ifpRecord.Special) { //в записи обыкновенного формата указываем либо указатель на следующую из параметров //либо обозначаем последнюю запись size = LeaderSize + ifpRecord.Links.Count * TermLink.RecordSize; buffer = new byte[size]; Write(buffer, ifpRecord, 0); } else { //обработка записи со спец блоком var links = ifpRecord.Links.ToArray(); //если нет - делаем запись со спец-блоком //здесь записи делятся по 254, то есть будет минимум 2 блока var blockCount = ifpRecord._NestedRecordLeaders != null ? ifpRecord._NestedRecordLeaders.Length : (int)Math.Ceiling((double)links.Length / (double)PostingsInBlock); int specialBlockSize = ifpRecord.Capacity * SpecialPostingBlockEntry.RecordSize; size = /*размер первой записи*/ LeaderSize /*в первой записи ссылок нет*/ + /*размер спец блока*/ specialBlockSize + (ifpRecord._NestedRecordLeaders == null ? /*размер вложенных записей только если они будут созданы*/ /*размер записей по числу блоков*/ LeaderSize * blockCount + links.Length * TermLink.RecordSize : 0); buffer = new byte[size]; ifpRecord.Links.Clear(); //устанавливается через Special //ifpRecord.LowOffset = -1001; //ifpRecord.HighOffset = -1001; //уже сделали ifpRecord.Links.Clear(); будет запись только заголовка //int bufferOffset = Write(buffer, ifpRecord, 0); int bufferOffset = WriteLeader(buffer, ifpRecord, 0); //запись спец блока //пишем записи спец блока по количеству Capacity int linksWritten = 0; for (int i = 0; i < ifpRecord.Capacity; i++) { int count = 0; var entry = ifpRecord._SpecialBlock != null ? ifpRecord._SpecialBlock[i] : new SpecialPostingBlockEntry(); //генерация адресов для новых записей спец блока if (ifpRecord._NestedRecordLeaders == null && i < blockCount) { //в ирбисе здесь номер слова из первой ссылки, зачем непонятно entry.FirstPosting = links[linksWritten]; //ссылка на место за спец блоком + размер записей обыкновенного формата столько, //сколько указывают предыдущие записи спец блока entry.Offset = offset + /*размер первой записи*/ LeaderSize + /*рамер спец блока*/ specialBlockSize + /*размер записей обыкновенного формата*/ LeaderSize * i + linksWritten * TermLink.RecordSize; //ifpRecord._NestedRecordLeaders уже null //if (ifpRecord._NestedRecordLeaders != null) // count = Math.Min(links.Length - linksWritten, ifpRecord._NestedRecordLeaders[i].TotalLinkCount); //else // count = Math.Min(links.Length - linksWritten, PostingsInBlock); count = Math.Min(links.Length - linksWritten, PostingsInBlock); linksWritten += count; } bufferOffset += SpecialPostingBlockEntry.Write(buffer, entry, bufferOffset); } linksWritten = 0; for (int i = 0; i < blockCount; i++) { //если обновляется существующая запись, количество ссылок брать из существующих блоков int count = 0; IfpRecord nestedRecord = null; //здесь пишем записи обычного формата if (ifpRecord._NestedRecordLeaders != null) { //для старой вложенной записи перезаписываем блок по количеству записей в заголовке //вместимость в ссылках и ссылки на следующие блоки не переписываем count = Math.Min(links.Length - linksWritten, ifpRecord._NestedRecordLeaders[i].Capacity); nestedRecord = ifpRecord._NestedRecordLeaders[i]; nestedRecord.TotalLinkCount = count; nestedRecord.BlockLinkCount = count; } else { //для новой записи записываем PostingsInBlock count = Math.Min(links.Length - linksWritten, PostingsInBlock); nestedRecord = new IfpRecord { TotalLinkCount = count, BlockLinkCount = count, Capacity = count }; if (i < blockCount - 1) { //ссылка на место сразу за этой записью nestedRecord.FullOffset = offset + /*размер первой записи*/ LeaderSize + /*рамер спец блока*/ specialBlockSize + /*размер предыдущих записей + размер этой записи*/ LeaderSize * (i + 1) + (linksWritten + count) * TermLink.RecordSize; } else { //в последней записи обыкновенного формата обозначаем последнюю запись nestedRecord.Last = true; } } var linksBlock = new TermLink[count]; Array.Copy(links, linksWritten, linksBlock, 0, linksBlock.Length); nestedRecord.Links.AddRange(linksBlock); linksWritten += count; //старые вложенные записи пишутся на старое место if (ifpRecord._NestedRecordLeaders != null) { Write(stream, nestedRecord, nestedRecord._FileOffset, padding); } else { bufferOffset += Write(buffer, nestedRecord, bufferOffset); } } } if (buffer.Length != size) { throw new InvalidOperationException("Ошибка записи ifp"); } //выравнивание размера файла if (padding && (offset + buffer.Length) > stream.Length) { long newSize = LeaderSize + (long)Math.Ceiling((double)(offset + buffer.Length - LeaderSize) / (double)BlockSize) * BlockSize; stream.SetLength(newSize); } if (stream.Seek(offset, SeekOrigin.Begin) != offset) { throw new IOException(); } stream.Write(buffer, 0, buffer.Length); stream.Flush(); return(size); }
public static IfpRecord Read ( Stream stream, long offset ) { //new ObjectDumper() // .DumpStream(stream, offset, 100); //ibatrak чтение лидера вынесено отдельно //stream.Position = offset; //IfpRecord result = new IfpRecord // { // LowOffset = stream.ReadInt32Network(), // HighOffset = stream.ReadInt32Network(), // TotalLinkCount = stream.ReadInt32Network(), // BlockLinkCount = stream.ReadInt32Network(), // Capacity = stream.ReadInt32Network(), // _FileOffset = offset // }; IfpRecord result = ReadLeader(stream, offset); //ibatrak чтение вложенных записей в спец блоке //Специальный формат записи .ifp //В этом случае первой записью является специальный блок, //который представляет собой заголовок (обыкновенного формата), в котором смещения имеют специальные значения = -1001, и набор вхождений следующего формата: //Число бит Параметр Описание //32 POSTING 1-я ссылка из записи обыкновенного формата //32 LOW младшее слово смещения на следующую запись (если нет 0) //32 HIGH старшее слово смещения на следующую запись (если нет 0) //Число вхождений кратно 4. Записи, на которые ссылается специальный блок связаны между собой как описано выше. //Общее количество ссылок для данного термина сохраняется только в специальном блоке. if (result.Special) { //irbis64.dll делает так //читает первые 24 байта блока спец ссылок //берет первую запись, адрес из нее в файле и читает записей IFP //по количеству result.BlockLinkCount //записи спец блока идут по количеству Capacity //каждая указывает на запись обычного формата //также в каждой записи обычного формата FullOffset указывает на следующую запись (кроме последней) //это должно соответствовать var specBlock = new SpecialPostingBlockEntry[result.Capacity]; var specBlockBuffer = new byte[result.Capacity * SpecialPostingBlockEntry.RecordSize]; var fileOffset = stream.Position; if (stream.Read(specBlockBuffer, 0, specBlockBuffer.Length) != specBlockBuffer.Length) { throw new IOException(); } for (int i = 0; i < result.Capacity; i++) { var entry = SpecialPostingBlockEntry.Read(specBlockBuffer, i * SpecialPostingBlockEntry.RecordSize); entry.FileOffset = fileOffset + i + SpecialPostingBlockEntry.RecordSize; specBlock[i] = entry; } //запомнить для редактирования result._SpecialBlock = specBlock; var nonEmptyEntries = specBlock.Where(e => e.Offset > 0).Count(); if (nonEmptyEntries != result.BlockLinkCount) { throw new InvalidOperationException("Ошибка чтения ifp offset=" + offset); } var nestedRecords = new IfpRecord[result.BlockLinkCount]; for (int i = 0; i < result.BlockLinkCount; i++) { if (specBlock[i].Offset <= 0 || (i > 0 && nestedRecords[i - 1].FullOffset != specBlock[i].Offset)) { throw new InvalidOperationException("Ошибка чтения ifp offset=" + offset); } var nestedRecord = Read(stream, specBlock[i].Offset); nestedRecords[i] = nestedRecord; } var links = nestedRecords.SelectMany(r => r.Links).ToArray(); if (links.Length != result.TotalLinkCount) { throw new InvalidOperationException("Ошибка чтения ifp offset=" + offset); } result.Links.AddRange(links); result._NestedRecordLeaders = nestedRecords.ToArray(); foreach (var r in nestedRecords) { r._links.Clear(); } return(result); } //ibatrak до сюда //ibatrak читаем ссылки за раз, так быстрее var linkBuffer = new byte[result.BlockLinkCount * TermLink.RecordSize]; if (stream.Read(linkBuffer, 0, linkBuffer.Length) != linkBuffer.Length) { throw new IOException(); } for (int i = 0; i < result.BlockLinkCount; i++) { TermLink link = TermLink.Read(linkBuffer, i * TermLink.RecordSize); result.Links.Add(link); } //for (int i = 0; i < result.BlockLinkCount; i++) //{ // TermLink link = TermLink.Read(stream); // result.Links.Add(link); //} return(result); }