001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.io; 020 021import java.io.*; 022import java.util.*; 023import java.rmi.server.UID; 024import java.security.MessageDigest; 025import org.apache.commons.logging.*; 026import org.apache.hadoop.util.Options; 027import org.apache.hadoop.fs.*; 028import org.apache.hadoop.fs.Options.CreateOpts; 029import org.apache.hadoop.io.compress.CodecPool; 030import org.apache.hadoop.io.compress.CompressionCodec; 031import org.apache.hadoop.io.compress.CompressionInputStream; 032import org.apache.hadoop.io.compress.CompressionOutputStream; 033import org.apache.hadoop.io.compress.Compressor; 034import org.apache.hadoop.io.compress.Decompressor; 035import org.apache.hadoop.io.compress.DefaultCodec; 036import org.apache.hadoop.io.compress.GzipCodec; 037import org.apache.hadoop.io.compress.zlib.ZlibFactory; 038import org.apache.hadoop.io.serializer.Deserializer; 039import org.apache.hadoop.io.serializer.Serializer; 040import org.apache.hadoop.io.serializer.SerializationFactory; 041import org.apache.hadoop.classification.InterfaceAudience; 042import org.apache.hadoop.classification.InterfaceStability; 043import org.apache.hadoop.conf.*; 044import org.apache.hadoop.util.Progressable; 045import org.apache.hadoop.util.Progress; 046import org.apache.hadoop.util.ReflectionUtils; 047import org.apache.hadoop.util.NativeCodeLoader; 048import org.apache.hadoop.util.MergeSort; 049import org.apache.hadoop.util.PriorityQueue; 050import org.apache.hadoop.util.Time; 051 052/** 053 * <code>SequenceFile</code>s are flat files consisting of binary key/value 054 * pairs. 055 * 056 * <p><code>SequenceFile</code> provides {@link SequenceFile.Writer}, 057 * {@link SequenceFile.Reader} and {@link Sorter} classes for writing, 058 * reading and sorting respectively.</p> 059 * 060 * There are three <code>SequenceFile</code> <code>Writer</code>s based on the 061 * {@link CompressionType} used to compress key/value pairs: 062 * <ol> 063 * <li> 064 * <code>Writer</code> : Uncompressed records. 065 * </li> 066 * <li> 067 * <code>RecordCompressWriter</code> : Record-compressed files, only compress 068 * values. 069 * </li> 070 * <li> 071 * <code>BlockCompressWriter</code> : Block-compressed files, both keys & 072 * values are collected in 'blocks' 073 * separately and compressed. The size of 074 * the 'block' is configurable. 075 * </ol> 076 * 077 * <p>The actual compression algorithm used to compress key and/or values can be 078 * specified by using the appropriate {@link CompressionCodec}.</p> 079 * 080 * <p>The recommended way is to use the static <tt>createWriter</tt> methods 081 * provided by the <code>SequenceFile</code> to chose the preferred format.</p> 082 * 083 * <p>The {@link SequenceFile.Reader} acts as the bridge and can read any of the 084 * above <code>SequenceFile</code> formats.</p> 085 * 086 * <h4 id="Formats">SequenceFile Formats</h4> 087 * 088 * <p>Essentially there are 3 different formats for <code>SequenceFile</code>s 089 * depending on the <code>CompressionType</code> specified. All of them share a 090 * <a href="#Header">common header</a> described below. 091 * 092 * <h5 id="Header">SequenceFile Header</h5> 093 * <ul> 094 * <li> 095 * version - 3 bytes of magic header <b>SEQ</b>, followed by 1 byte of actual 096 * version number (e.g. SEQ4 or SEQ6) 097 * </li> 098 * <li> 099 * keyClassName -key class 100 * </li> 101 * <li> 102 * valueClassName - value class 103 * </li> 104 * <li> 105 * compression - A boolean which specifies if compression is turned on for 106 * keys/values in this file. 107 * </li> 108 * <li> 109 * blockCompression - A boolean which specifies if block-compression is 110 * turned on for keys/values in this file. 111 * </li> 112 * <li> 113 * compression codec - <code>CompressionCodec</code> class which is used for 114 * compression of keys and/or values (if compression is 115 * enabled). 116 * </li> 117 * <li> 118 * metadata - {@link Metadata} for this file. 119 * </li> 120 * <li> 121 * sync - A sync marker to denote end of the header. 122 * </li> 123 * </ul> 124 * 125 * <h5 id="#UncompressedFormat">Uncompressed SequenceFile Format</h5> 126 * <ul> 127 * <li> 128 * <a href="#Header">Header</a> 129 * </li> 130 * <li> 131 * Record 132 * <ul> 133 * <li>Record length</li> 134 * <li>Key length</li> 135 * <li>Key</li> 136 * <li>Value</li> 137 * </ul> 138 * </li> 139 * <li> 140 * A sync-marker every few <code>100</code> bytes or so. 141 * </li> 142 * </ul> 143 * 144 * <h5 id="#RecordCompressedFormat">Record-Compressed SequenceFile Format</h5> 145 * <ul> 146 * <li> 147 * <a href="#Header">Header</a> 148 * </li> 149 * <li> 150 * Record 151 * <ul> 152 * <li>Record length</li> 153 * <li>Key length</li> 154 * <li>Key</li> 155 * <li><i>Compressed</i> Value</li> 156 * </ul> 157 * </li> 158 * <li> 159 * A sync-marker every few <code>100</code> bytes or so. 160 * </li> 161 * </ul> 162 * 163 * <h5 id="#BlockCompressedFormat">Block-Compressed SequenceFile Format</h5> 164 * <ul> 165 * <li> 166 * <a href="#Header">Header</a> 167 * </li> 168 * <li> 169 * Record <i>Block</i> 170 * <ul> 171 * <li>Uncompressed number of records in the block</li> 172 * <li>Compressed key-lengths block-size</li> 173 * <li>Compressed key-lengths block</li> 174 * <li>Compressed keys block-size</li> 175 * <li>Compressed keys block</li> 176 * <li>Compressed value-lengths block-size</li> 177 * <li>Compressed value-lengths block</li> 178 * <li>Compressed values block-size</li> 179 * <li>Compressed values block</li> 180 * </ul> 181 * </li> 182 * <li> 183 * A sync-marker every block. 184 * </li> 185 * </ul> 186 * 187 * <p>The compressed blocks of key lengths and value lengths consist of the 188 * actual lengths of individual keys/values encoded in ZeroCompressedInteger 189 * format.</p> 190 * 191 * @see CompressionCodec 192 */ 193@InterfaceAudience.Public 194@InterfaceStability.Stable 195public class SequenceFile { 196 private static final Log LOG = LogFactory.getLog(SequenceFile.class); 197 198 private SequenceFile() {} // no public ctor 199 200 private static final byte BLOCK_COMPRESS_VERSION = (byte)4; 201 private static final byte CUSTOM_COMPRESS_VERSION = (byte)5; 202 private static final byte VERSION_WITH_METADATA = (byte)6; 203 private static byte[] VERSION = new byte[] { 204 (byte)'S', (byte)'E', (byte)'Q', VERSION_WITH_METADATA 205 }; 206 207 private static final int SYNC_ESCAPE = -1; // "length" of sync entries 208 private static final int SYNC_HASH_SIZE = 16; // number of bytes in hash 209 private static final int SYNC_SIZE = 4+SYNC_HASH_SIZE; // escape + hash 210 211 /** The number of bytes between sync points.*/ 212 public static final int SYNC_INTERVAL = 100*SYNC_SIZE; 213 214 /** 215 * The compression type used to compress key/value pairs in the 216 * {@link SequenceFile}. 217 * 218 * @see SequenceFile.Writer 219 */ 220 public static enum CompressionType { 221 /** Do not compress records. */ 222 NONE, 223 /** Compress values only, each separately. */ 224 RECORD, 225 /** Compress sequences of records together in blocks. */ 226 BLOCK 227 } 228 229 /** 230 * Get the compression type for the reduce outputs 231 * @param job the job config to look in 232 * @return the kind of compression to use 233 */ 234 static public CompressionType getDefaultCompressionType(Configuration job) { 235 String name = job.get("io.seqfile.compression.type"); 236 return name == null ? CompressionType.RECORD : 237 CompressionType.valueOf(name); 238 } 239 240 /** 241 * Set the default compression type for sequence files. 242 * @param job the configuration to modify 243 * @param val the new compression type (none, block, record) 244 */ 245 static public void setDefaultCompressionType(Configuration job, 246 CompressionType val) { 247 job.set("io.seqfile.compression.type", val.toString()); 248 } 249 250 /** 251 * Create a new Writer with the given options. 252 * @param conf the configuration to use 253 * @param opts the options to create the file with 254 * @return a new Writer 255 * @throws IOException 256 */ 257 public static Writer createWriter(Configuration conf, Writer.Option... opts 258 ) throws IOException { 259 Writer.CompressionOption compressionOption = 260 Options.getOption(Writer.CompressionOption.class, opts); 261 CompressionType kind; 262 if (compressionOption != null) { 263 kind = compressionOption.getValue(); 264 } else { 265 kind = getDefaultCompressionType(conf); 266 opts = Options.prependOptions(opts, Writer.compression(kind)); 267 } 268 switch (kind) { 269 default: 270 case NONE: 271 return new Writer(conf, opts); 272 case RECORD: 273 return new RecordCompressWriter(conf, opts); 274 case BLOCK: 275 return new BlockCompressWriter(conf, opts); 276 } 277 } 278 279 /** 280 * Construct the preferred type of SequenceFile Writer. 281 * @param fs The configured filesystem. 282 * @param conf The configuration. 283 * @param name The name of the file. 284 * @param keyClass The 'key' type. 285 * @param valClass The 'value' type. 286 * @return Returns the handle to the constructed SequenceFile Writer. 287 * @throws IOException 288 * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)} 289 * instead. 290 */ 291 @Deprecated 292 public static Writer 293 createWriter(FileSystem fs, Configuration conf, Path name, 294 Class keyClass, Class valClass) throws IOException { 295 return createWriter(conf, Writer.filesystem(fs), 296 Writer.file(name), Writer.keyClass(keyClass), 297 Writer.valueClass(valClass)); 298 } 299 300 /** 301 * Construct the preferred type of SequenceFile Writer. 302 * @param fs The configured filesystem. 303 * @param conf The configuration. 304 * @param name The name of the file. 305 * @param keyClass The 'key' type. 306 * @param valClass The 'value' type. 307 * @param compressionType The compression type. 308 * @return Returns the handle to the constructed SequenceFile Writer. 309 * @throws IOException 310 * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)} 311 * instead. 312 */ 313 @Deprecated 314 public static Writer 315 createWriter(FileSystem fs, Configuration conf, Path name, 316 Class keyClass, Class valClass, 317 CompressionType compressionType) throws IOException { 318 return createWriter(conf, Writer.filesystem(fs), 319 Writer.file(name), Writer.keyClass(keyClass), 320 Writer.valueClass(valClass), 321 Writer.compression(compressionType)); 322 } 323 324 /** 325 * Construct the preferred type of SequenceFile Writer. 326 * @param fs The configured filesystem. 327 * @param conf The configuration. 328 * @param name The name of the file. 329 * @param keyClass The 'key' type. 330 * @param valClass The 'value' type. 331 * @param compressionType The compression type. 332 * @param progress The Progressable object to track progress. 333 * @return Returns the handle to the constructed SequenceFile Writer. 334 * @throws IOException 335 * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)} 336 * instead. 337 */ 338 @Deprecated 339 public static Writer 340 createWriter(FileSystem fs, Configuration conf, Path name, 341 Class keyClass, Class valClass, CompressionType compressionType, 342 Progressable progress) throws IOException { 343 return createWriter(conf, Writer.file(name), 344 Writer.filesystem(fs), 345 Writer.keyClass(keyClass), 346 Writer.valueClass(valClass), 347 Writer.compression(compressionType), 348 Writer.progressable(progress)); 349 } 350 351 /** 352 * Construct the preferred type of SequenceFile Writer. 353 * @param fs The configured filesystem. 354 * @param conf The configuration. 355 * @param name The name of the file. 356 * @param keyClass The 'key' type. 357 * @param valClass The 'value' type. 358 * @param compressionType The compression type. 359 * @param codec The compression codec. 360 * @return Returns the handle to the constructed SequenceFile Writer. 361 * @throws IOException 362 * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)} 363 * instead. 364 */ 365 @Deprecated 366 public static Writer 367 createWriter(FileSystem fs, Configuration conf, Path name, 368 Class keyClass, Class valClass, CompressionType compressionType, 369 CompressionCodec codec) throws IOException { 370 return createWriter(conf, Writer.file(name), 371 Writer.filesystem(fs), 372 Writer.keyClass(keyClass), 373 Writer.valueClass(valClass), 374 Writer.compression(compressionType, codec)); 375 } 376 377 /** 378 * Construct the preferred type of SequenceFile Writer. 379 * @param fs The configured filesystem. 380 * @param conf The configuration. 381 * @param name The name of the file. 382 * @param keyClass The 'key' type. 383 * @param valClass The 'value' type. 384 * @param compressionType The compression type. 385 * @param codec The compression codec. 386 * @param progress The Progressable object to track progress. 387 * @param metadata The metadata of the file. 388 * @return Returns the handle to the constructed SequenceFile Writer. 389 * @throws IOException 390 * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)} 391 * instead. 392 */ 393 @Deprecated 394 public static Writer 395 createWriter(FileSystem fs, Configuration conf, Path name, 396 Class keyClass, Class valClass, 397 CompressionType compressionType, CompressionCodec codec, 398 Progressable progress, Metadata metadata) throws IOException { 399 return createWriter(conf, Writer.file(name), 400 Writer.filesystem(fs), 401 Writer.keyClass(keyClass), 402 Writer.valueClass(valClass), 403 Writer.compression(compressionType, codec), 404 Writer.progressable(progress), 405 Writer.metadata(metadata)); 406 } 407 408 /** 409 * Construct the preferred type of SequenceFile Writer. 410 * @param fs The configured filesystem. 411 * @param conf The configuration. 412 * @param name The name of the file. 413 * @param keyClass The 'key' type. 414 * @param valClass The 'value' type. 415 * @param bufferSize buffer size for the underlaying outputstream. 416 * @param replication replication factor for the file. 417 * @param blockSize block size for the file. 418 * @param compressionType The compression type. 419 * @param codec The compression codec. 420 * @param progress The Progressable object to track progress. 421 * @param metadata The metadata of the file. 422 * @return Returns the handle to the constructed SequenceFile Writer. 423 * @throws IOException 424 * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)} 425 * instead. 426 */ 427 @Deprecated 428 public static Writer 429 createWriter(FileSystem fs, Configuration conf, Path name, 430 Class keyClass, Class valClass, int bufferSize, 431 short replication, long blockSize, 432 CompressionType compressionType, CompressionCodec codec, 433 Progressable progress, Metadata metadata) throws IOException { 434 return createWriter(conf, Writer.file(name), 435 Writer.filesystem(fs), 436 Writer.keyClass(keyClass), 437 Writer.valueClass(valClass), 438 Writer.bufferSize(bufferSize), 439 Writer.replication(replication), 440 Writer.blockSize(blockSize), 441 Writer.compression(compressionType, codec), 442 Writer.progressable(progress), 443 Writer.metadata(metadata)); 444 } 445 446 /** 447 * Construct the preferred type of SequenceFile Writer. 448 * @param fs The configured filesystem. 449 * @param conf The configuration. 450 * @param name The name of the file. 451 * @param keyClass The 'key' type. 452 * @param valClass The 'value' type. 453 * @param bufferSize buffer size for the underlaying outputstream. 454 * @param replication replication factor for the file. 455 * @param blockSize block size for the file. 456 * @param createParent create parent directory if non-existent 457 * @param compressionType The compression type. 458 * @param codec The compression codec. 459 * @param metadata The metadata of the file. 460 * @return Returns the handle to the constructed SequenceFile Writer. 461 * @throws IOException 462 */ 463 @Deprecated 464 public static Writer 465 createWriter(FileSystem fs, Configuration conf, Path name, 466 Class keyClass, Class valClass, int bufferSize, 467 short replication, long blockSize, boolean createParent, 468 CompressionType compressionType, CompressionCodec codec, 469 Metadata metadata) throws IOException { 470 return createWriter(FileContext.getFileContext(fs.getUri(), conf), 471 conf, name, keyClass, valClass, compressionType, codec, 472 metadata, EnumSet.of(CreateFlag.CREATE,CreateFlag.OVERWRITE), 473 CreateOpts.bufferSize(bufferSize), 474 createParent ? CreateOpts.createParent() 475 : CreateOpts.donotCreateParent(), 476 CreateOpts.repFac(replication), 477 CreateOpts.blockSize(blockSize) 478 ); 479 } 480 481 /** 482 * Construct the preferred type of SequenceFile Writer. 483 * @param fc The context for the specified file. 484 * @param conf The configuration. 485 * @param name The name of the file. 486 * @param keyClass The 'key' type. 487 * @param valClass The 'value' type. 488 * @param compressionType The compression type. 489 * @param codec The compression codec. 490 * @param metadata The metadata of the file. 491 * @param createFlag gives the semantics of create: overwrite, append etc. 492 * @param opts file creation options; see {@link CreateOpts}. 493 * @return Returns the handle to the constructed SequenceFile Writer. 494 * @throws IOException 495 */ 496 public static Writer 497 createWriter(FileContext fc, Configuration conf, Path name, 498 Class keyClass, Class valClass, 499 CompressionType compressionType, CompressionCodec codec, 500 Metadata metadata, 501 final EnumSet<CreateFlag> createFlag, CreateOpts... opts) 502 throws IOException { 503 return createWriter(conf, fc.create(name, createFlag, opts), 504 keyClass, valClass, compressionType, codec, metadata).ownStream(); 505 } 506 507 /** 508 * Construct the preferred type of SequenceFile Writer. 509 * @param fs The configured filesystem. 510 * @param conf The configuration. 511 * @param name The name of the file. 512 * @param keyClass The 'key' type. 513 * @param valClass The 'value' type. 514 * @param compressionType The compression type. 515 * @param codec The compression codec. 516 * @param progress The Progressable object to track progress. 517 * @return Returns the handle to the constructed SequenceFile Writer. 518 * @throws IOException 519 * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)} 520 * instead. 521 */ 522 @Deprecated 523 public static Writer 524 createWriter(FileSystem fs, Configuration conf, Path name, 525 Class keyClass, Class valClass, 526 CompressionType compressionType, CompressionCodec codec, 527 Progressable progress) throws IOException { 528 return createWriter(conf, Writer.file(name), 529 Writer.filesystem(fs), 530 Writer.keyClass(keyClass), 531 Writer.valueClass(valClass), 532 Writer.compression(compressionType, codec), 533 Writer.progressable(progress)); 534 } 535 536 /** 537 * Construct the preferred type of 'raw' SequenceFile Writer. 538 * @param conf The configuration. 539 * @param out The stream on top which the writer is to be constructed. 540 * @param keyClass The 'key' type. 541 * @param valClass The 'value' type. 542 * @param compressionType The compression type. 543 * @param codec The compression codec. 544 * @param metadata The metadata of the file. 545 * @return Returns the handle to the constructed SequenceFile Writer. 546 * @throws IOException 547 * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)} 548 * instead. 549 */ 550 @Deprecated 551 public static Writer 552 createWriter(Configuration conf, FSDataOutputStream out, 553 Class keyClass, Class valClass, 554 CompressionType compressionType, 555 CompressionCodec codec, Metadata metadata) throws IOException { 556 return createWriter(conf, Writer.stream(out), Writer.keyClass(keyClass), 557 Writer.valueClass(valClass), 558 Writer.compression(compressionType, codec), 559 Writer.metadata(metadata)); 560 } 561 562 /** 563 * Construct the preferred type of 'raw' SequenceFile Writer. 564 * @param conf The configuration. 565 * @param out The stream on top which the writer is to be constructed. 566 * @param keyClass The 'key' type. 567 * @param valClass The 'value' type. 568 * @param compressionType The compression type. 569 * @param codec The compression codec. 570 * @return Returns the handle to the constructed SequenceFile Writer. 571 * @throws IOException 572 * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)} 573 * instead. 574 */ 575 @Deprecated 576 public static Writer 577 createWriter(Configuration conf, FSDataOutputStream out, 578 Class keyClass, Class valClass, CompressionType compressionType, 579 CompressionCodec codec) throws IOException { 580 return createWriter(conf, Writer.stream(out), Writer.keyClass(keyClass), 581 Writer.valueClass(valClass), 582 Writer.compression(compressionType, codec)); 583 } 584 585 586 /** The interface to 'raw' values of SequenceFiles. */ 587 public static interface ValueBytes { 588 589 /** Writes the uncompressed bytes to the outStream. 590 * @param outStream : Stream to write uncompressed bytes into. 591 * @throws IOException 592 */ 593 public void writeUncompressedBytes(DataOutputStream outStream) 594 throws IOException; 595 596 /** Write compressed bytes to outStream. 597 * Note: that it will NOT compress the bytes if they are not compressed. 598 * @param outStream : Stream to write compressed bytes into. 599 */ 600 public void writeCompressedBytes(DataOutputStream outStream) 601 throws IllegalArgumentException, IOException; 602 603 /** 604 * Size of stored data. 605 */ 606 public int getSize(); 607 } 608 609 private static class UncompressedBytes implements ValueBytes { 610 private int dataSize; 611 private byte[] data; 612 613 private UncompressedBytes() { 614 data = null; 615 dataSize = 0; 616 } 617 618 private void reset(DataInputStream in, int length) throws IOException { 619 if (data == null) { 620 data = new byte[length]; 621 } else if (length > data.length) { 622 data = new byte[Math.max(length, data.length * 2)]; 623 } 624 dataSize = -1; 625 in.readFully(data, 0, length); 626 dataSize = length; 627 } 628 629 @Override 630 public int getSize() { 631 return dataSize; 632 } 633 634 @Override 635 public void writeUncompressedBytes(DataOutputStream outStream) 636 throws IOException { 637 outStream.write(data, 0, dataSize); 638 } 639 640 @Override 641 public void writeCompressedBytes(DataOutputStream outStream) 642 throws IllegalArgumentException, IOException { 643 throw 644 new IllegalArgumentException("UncompressedBytes cannot be compressed!"); 645 } 646 647 } // UncompressedBytes 648 649 private static class CompressedBytes implements ValueBytes { 650 private int dataSize; 651 private byte[] data; 652 DataInputBuffer rawData = null; 653 CompressionCodec codec = null; 654 CompressionInputStream decompressedStream = null; 655 656 private CompressedBytes(CompressionCodec codec) { 657 data = null; 658 dataSize = 0; 659 this.codec = codec; 660 } 661 662 private void reset(DataInputStream in, int length) throws IOException { 663 if (data == null) { 664 data = new byte[length]; 665 } else if (length > data.length) { 666 data = new byte[Math.max(length, data.length * 2)]; 667 } 668 dataSize = -1; 669 in.readFully(data, 0, length); 670 dataSize = length; 671 } 672 673 @Override 674 public int getSize() { 675 return dataSize; 676 } 677 678 @Override 679 public void writeUncompressedBytes(DataOutputStream outStream) 680 throws IOException { 681 if (decompressedStream == null) { 682 rawData = new DataInputBuffer(); 683 decompressedStream = codec.createInputStream(rawData); 684 } else { 685 decompressedStream.resetState(); 686 } 687 rawData.reset(data, 0, dataSize); 688 689 byte[] buffer = new byte[8192]; 690 int bytesRead = 0; 691 while ((bytesRead = decompressedStream.read(buffer, 0, 8192)) != -1) { 692 outStream.write(buffer, 0, bytesRead); 693 } 694 } 695 696 @Override 697 public void writeCompressedBytes(DataOutputStream outStream) 698 throws IllegalArgumentException, IOException { 699 outStream.write(data, 0, dataSize); 700 } 701 702 } // CompressedBytes 703 704 /** 705 * The class encapsulating with the metadata of a file. 706 * The metadata of a file is a list of attribute name/value 707 * pairs of Text type. 708 * 709 */ 710 public static class Metadata implements Writable { 711 712 private TreeMap<Text, Text> theMetadata; 713 714 public Metadata() { 715 this(new TreeMap<Text, Text>()); 716 } 717 718 public Metadata(TreeMap<Text, Text> arg) { 719 if (arg == null) { 720 this.theMetadata = new TreeMap<Text, Text>(); 721 } else { 722 this.theMetadata = arg; 723 } 724 } 725 726 public Text get(Text name) { 727 return this.theMetadata.get(name); 728 } 729 730 public void set(Text name, Text value) { 731 this.theMetadata.put(name, value); 732 } 733 734 public TreeMap<Text, Text> getMetadata() { 735 return new TreeMap<Text, Text>(this.theMetadata); 736 } 737 738 @Override 739 public void write(DataOutput out) throws IOException { 740 out.writeInt(this.theMetadata.size()); 741 Iterator<Map.Entry<Text, Text>> iter = 742 this.theMetadata.entrySet().iterator(); 743 while (iter.hasNext()) { 744 Map.Entry<Text, Text> en = iter.next(); 745 en.getKey().write(out); 746 en.getValue().write(out); 747 } 748 } 749 750 @Override 751 public void readFields(DataInput in) throws IOException { 752 int sz = in.readInt(); 753 if (sz < 0) throw new IOException("Invalid size: " + sz + " for file metadata object"); 754 this.theMetadata = new TreeMap<Text, Text>(); 755 for (int i = 0; i < sz; i++) { 756 Text key = new Text(); 757 Text val = new Text(); 758 key.readFields(in); 759 val.readFields(in); 760 this.theMetadata.put(key, val); 761 } 762 } 763 764 @Override 765 public boolean equals(Object other) { 766 if (other == null) { 767 return false; 768 } 769 if (other.getClass() != this.getClass()) { 770 return false; 771 } else { 772 return equals((Metadata)other); 773 } 774 } 775 776 public boolean equals(Metadata other) { 777 if (other == null) return false; 778 if (this.theMetadata.size() != other.theMetadata.size()) { 779 return false; 780 } 781 Iterator<Map.Entry<Text, Text>> iter1 = 782 this.theMetadata.entrySet().iterator(); 783 Iterator<Map.Entry<Text, Text>> iter2 = 784 other.theMetadata.entrySet().iterator(); 785 while (iter1.hasNext() && iter2.hasNext()) { 786 Map.Entry<Text, Text> en1 = iter1.next(); 787 Map.Entry<Text, Text> en2 = iter2.next(); 788 if (!en1.getKey().equals(en2.getKey())) { 789 return false; 790 } 791 if (!en1.getValue().equals(en2.getValue())) { 792 return false; 793 } 794 } 795 if (iter1.hasNext() || iter2.hasNext()) { 796 return false; 797 } 798 return true; 799 } 800 801 @Override 802 public int hashCode() { 803 assert false : "hashCode not designed"; 804 return 42; // any arbitrary constant will do 805 } 806 807 @Override 808 public String toString() { 809 StringBuilder sb = new StringBuilder(); 810 sb.append("size: ").append(this.theMetadata.size()).append("\n"); 811 Iterator<Map.Entry<Text, Text>> iter = 812 this.theMetadata.entrySet().iterator(); 813 while (iter.hasNext()) { 814 Map.Entry<Text, Text> en = iter.next(); 815 sb.append("\t").append(en.getKey().toString()).append("\t").append(en.getValue().toString()); 816 sb.append("\n"); 817 } 818 return sb.toString(); 819 } 820 } 821 822 /** Write key/value pairs to a sequence-format file. */ 823 public static class Writer implements java.io.Closeable, Syncable { 824 private Configuration conf; 825 FSDataOutputStream out; 826 boolean ownOutputStream = true; 827 DataOutputBuffer buffer = new DataOutputBuffer(); 828 829 Class keyClass; 830 Class valClass; 831 832 private final CompressionType compress; 833 CompressionCodec codec = null; 834 CompressionOutputStream deflateFilter = null; 835 DataOutputStream deflateOut = null; 836 Metadata metadata = null; 837 Compressor compressor = null; 838 839 private boolean appendMode = false; 840 841 protected Serializer keySerializer; 842 protected Serializer uncompressedValSerializer; 843 protected Serializer compressedValSerializer; 844 845 // Insert a globally unique 16-byte value every few entries, so that one 846 // can seek into the middle of a file and then synchronize with record 847 // starts and ends by scanning for this value. 848 long lastSyncPos; // position of last sync 849 byte[] sync; // 16 random bytes 850 { 851 try { 852 MessageDigest digester = MessageDigest.getInstance("MD5"); 853 long time = Time.now(); 854 digester.update((new UID()+"@"+time).getBytes()); 855 sync = digester.digest(); 856 } catch (Exception e) { 857 throw new RuntimeException(e); 858 } 859 } 860 861 public static interface Option {} 862 863 static class FileOption extends Options.PathOption 864 implements Option { 865 FileOption(Path path) { 866 super(path); 867 } 868 } 869 870 /** 871 * @deprecated only used for backwards-compatibility in the createWriter methods 872 * that take FileSystem. 873 */ 874 @Deprecated 875 private static class FileSystemOption implements Option { 876 private final FileSystem value; 877 protected FileSystemOption(FileSystem value) { 878 this.value = value; 879 } 880 public FileSystem getValue() { 881 return value; 882 } 883 } 884 885 static class StreamOption extends Options.FSDataOutputStreamOption 886 implements Option { 887 StreamOption(FSDataOutputStream stream) { 888 super(stream); 889 } 890 } 891 892 static class BufferSizeOption extends Options.IntegerOption 893 implements Option { 894 BufferSizeOption(int value) { 895 super(value); 896 } 897 } 898 899 static class BlockSizeOption extends Options.LongOption implements Option { 900 BlockSizeOption(long value) { 901 super(value); 902 } 903 } 904 905 static class ReplicationOption extends Options.IntegerOption 906 implements Option { 907 ReplicationOption(int value) { 908 super(value); 909 } 910 } 911 912 static class AppendIfExistsOption extends Options.BooleanOption implements 913 Option { 914 AppendIfExistsOption(boolean value) { 915 super(value); 916 } 917 } 918 919 static class KeyClassOption extends Options.ClassOption implements Option { 920 KeyClassOption(Class<?> value) { 921 super(value); 922 } 923 } 924 925 static class ValueClassOption extends Options.ClassOption 926 implements Option { 927 ValueClassOption(Class<?> value) { 928 super(value); 929 } 930 } 931 932 static class MetadataOption implements Option { 933 private final Metadata value; 934 MetadataOption(Metadata value) { 935 this.value = value; 936 } 937 Metadata getValue() { 938 return value; 939 } 940 } 941 942 static class ProgressableOption extends Options.ProgressableOption 943 implements Option { 944 ProgressableOption(Progressable value) { 945 super(value); 946 } 947 } 948 949 private static class CompressionOption implements Option { 950 private final CompressionType value; 951 private final CompressionCodec codec; 952 CompressionOption(CompressionType value) { 953 this(value, null); 954 } 955 CompressionOption(CompressionType value, CompressionCodec codec) { 956 this.value = value; 957 this.codec = (CompressionType.NONE != value && null == codec) 958 ? new DefaultCodec() 959 : codec; 960 } 961 CompressionType getValue() { 962 return value; 963 } 964 CompressionCodec getCodec() { 965 return codec; 966 } 967 } 968 969 public static Option file(Path value) { 970 return new FileOption(value); 971 } 972 973 /** 974 * @deprecated only used for backwards-compatibility in the createWriter methods 975 * that take FileSystem. 976 */ 977 @Deprecated 978 private static Option filesystem(FileSystem fs) { 979 return new SequenceFile.Writer.FileSystemOption(fs); 980 } 981 982 public static Option bufferSize(int value) { 983 return new BufferSizeOption(value); 984 } 985 986 public static Option stream(FSDataOutputStream value) { 987 return new StreamOption(value); 988 } 989 990 public static Option replication(short value) { 991 return new ReplicationOption(value); 992 } 993 994 public static Option appendIfExists(boolean value) { 995 return new AppendIfExistsOption(value); 996 } 997 998 public static Option blockSize(long value) { 999 return new BlockSizeOption(value); 1000 } 1001 1002 public static Option progressable(Progressable value) { 1003 return new ProgressableOption(value); 1004 } 1005 1006 public static Option keyClass(Class<?> value) { 1007 return new KeyClassOption(value); 1008 } 1009 1010 public static Option valueClass(Class<?> value) { 1011 return new ValueClassOption(value); 1012 } 1013 1014 public static Option metadata(Metadata value) { 1015 return new MetadataOption(value); 1016 } 1017 1018 public static Option compression(CompressionType value) { 1019 return new CompressionOption(value); 1020 } 1021 1022 public static Option compression(CompressionType value, 1023 CompressionCodec codec) { 1024 return new CompressionOption(value, codec); 1025 } 1026 1027 /** 1028 * Construct a uncompressed writer from a set of options. 1029 * @param conf the configuration to use 1030 * @param options the options used when creating the writer 1031 * @throws IOException if it fails 1032 */ 1033 Writer(Configuration conf, 1034 Option... opts) throws IOException { 1035 BlockSizeOption blockSizeOption = 1036 Options.getOption(BlockSizeOption.class, opts); 1037 BufferSizeOption bufferSizeOption = 1038 Options.getOption(BufferSizeOption.class, opts); 1039 ReplicationOption replicationOption = 1040 Options.getOption(ReplicationOption.class, opts); 1041 ProgressableOption progressOption = 1042 Options.getOption(ProgressableOption.class, opts); 1043 FileOption fileOption = Options.getOption(FileOption.class, opts); 1044 AppendIfExistsOption appendIfExistsOption = Options.getOption( 1045 AppendIfExistsOption.class, opts); 1046 FileSystemOption fsOption = Options.getOption(FileSystemOption.class, opts); 1047 StreamOption streamOption = Options.getOption(StreamOption.class, opts); 1048 KeyClassOption keyClassOption = 1049 Options.getOption(KeyClassOption.class, opts); 1050 ValueClassOption valueClassOption = 1051 Options.getOption(ValueClassOption.class, opts); 1052 MetadataOption metadataOption = 1053 Options.getOption(MetadataOption.class, opts); 1054 CompressionOption compressionTypeOption = 1055 Options.getOption(CompressionOption.class, opts); 1056 // check consistency of options 1057 if ((fileOption == null) == (streamOption == null)) { 1058 throw new IllegalArgumentException("file or stream must be specified"); 1059 } 1060 if (fileOption == null && (blockSizeOption != null || 1061 bufferSizeOption != null || 1062 replicationOption != null || 1063 progressOption != null)) { 1064 throw new IllegalArgumentException("file modifier options not " + 1065 "compatible with stream"); 1066 } 1067 1068 FSDataOutputStream out; 1069 boolean ownStream = fileOption != null; 1070 if (ownStream) { 1071 Path p = fileOption.getValue(); 1072 FileSystem fs; 1073 if (fsOption != null) { 1074 fs = fsOption.getValue(); 1075 } else { 1076 fs = p.getFileSystem(conf); 1077 } 1078 int bufferSize = bufferSizeOption == null ? getBufferSize(conf) : 1079 bufferSizeOption.getValue(); 1080 short replication = replicationOption == null ? 1081 fs.getDefaultReplication(p) : 1082 (short) replicationOption.getValue(); 1083 long blockSize = blockSizeOption == null ? fs.getDefaultBlockSize(p) : 1084 blockSizeOption.getValue(); 1085 Progressable progress = progressOption == null ? null : 1086 progressOption.getValue(); 1087 1088 if (appendIfExistsOption != null && appendIfExistsOption.getValue() 1089 && fs.exists(p)) { 1090 1091 // Read the file and verify header details 1092 SequenceFile.Reader reader = new SequenceFile.Reader(conf, 1093 SequenceFile.Reader.file(p), new Reader.OnlyHeaderOption()); 1094 try { 1095 1096 if (keyClassOption.getValue() != reader.getKeyClass() 1097 || valueClassOption.getValue() != reader.getValueClass()) { 1098 throw new IllegalArgumentException( 1099 "Key/value class provided does not match the file"); 1100 } 1101 1102 if (reader.getVersion() != VERSION[3]) { 1103 throw new VersionMismatchException(VERSION[3], 1104 reader.getVersion()); 1105 } 1106 1107 if (metadataOption != null) { 1108 LOG.info("MetaData Option is ignored during append"); 1109 } 1110 metadataOption = (MetadataOption) SequenceFile.Writer 1111 .metadata(reader.getMetadata()); 1112 1113 CompressionOption readerCompressionOption = new CompressionOption( 1114 reader.getCompressionType(), reader.getCompressionCodec()); 1115 1116 if (readerCompressionOption.value != compressionTypeOption.value 1117 || !readerCompressionOption.codec.getClass().getName() 1118 .equals(compressionTypeOption.codec.getClass().getName())) { 1119 throw new IllegalArgumentException( 1120 "Compression option provided does not match the file"); 1121 } 1122 1123 sync = reader.getSync(); 1124 1125 } finally { 1126 reader.close(); 1127 } 1128 1129 out = fs.append(p, bufferSize, progress); 1130 this.appendMode = true; 1131 } else { 1132 out = fs 1133 .create(p, true, bufferSize, replication, blockSize, progress); 1134 } 1135 } else { 1136 out = streamOption.getValue(); 1137 } 1138 Class<?> keyClass = keyClassOption == null ? 1139 Object.class : keyClassOption.getValue(); 1140 Class<?> valueClass = valueClassOption == null ? 1141 Object.class : valueClassOption.getValue(); 1142 Metadata metadata = metadataOption == null ? 1143 new Metadata() : metadataOption.getValue(); 1144 this.compress = compressionTypeOption.getValue(); 1145 final CompressionCodec codec = compressionTypeOption.getCodec(); 1146 if (codec != null && 1147 (codec instanceof GzipCodec) && 1148 !NativeCodeLoader.isNativeCodeLoaded() && 1149 !ZlibFactory.isNativeZlibLoaded(conf)) { 1150 throw new IllegalArgumentException("SequenceFile doesn't work with " + 1151 "GzipCodec without native-hadoop " + 1152 "code!"); 1153 } 1154 init(conf, out, ownStream, keyClass, valueClass, codec, metadata); 1155 } 1156 1157 /** Create the named file. 1158 * @deprecated Use 1159 * {@link SequenceFile#createWriter(Configuration, Writer.Option...)} 1160 * instead. 1161 */ 1162 @Deprecated 1163 public Writer(FileSystem fs, Configuration conf, Path name, 1164 Class keyClass, Class valClass) throws IOException { 1165 this.compress = CompressionType.NONE; 1166 init(conf, fs.create(name), true, keyClass, valClass, null, 1167 new Metadata()); 1168 } 1169 1170 /** Create the named file with write-progress reporter. 1171 * @deprecated Use 1172 * {@link SequenceFile#createWriter(Configuration, Writer.Option...)} 1173 * instead. 1174 */ 1175 @Deprecated 1176 public Writer(FileSystem fs, Configuration conf, Path name, 1177 Class keyClass, Class valClass, 1178 Progressable progress, Metadata metadata) throws IOException { 1179 this.compress = CompressionType.NONE; 1180 init(conf, fs.create(name, progress), true, keyClass, valClass, 1181 null, metadata); 1182 } 1183 1184 /** Create the named file with write-progress reporter. 1185 * @deprecated Use 1186 * {@link SequenceFile#createWriter(Configuration, Writer.Option...)} 1187 * instead. 1188 */ 1189 @Deprecated 1190 public Writer(FileSystem fs, Configuration conf, Path name, 1191 Class keyClass, Class valClass, 1192 int bufferSize, short replication, long blockSize, 1193 Progressable progress, Metadata metadata) throws IOException { 1194 this.compress = CompressionType.NONE; 1195 init(conf, 1196 fs.create(name, true, bufferSize, replication, blockSize, progress), 1197 true, keyClass, valClass, null, metadata); 1198 } 1199 1200 boolean isCompressed() { return compress != CompressionType.NONE; } 1201 boolean isBlockCompressed() { return compress == CompressionType.BLOCK; } 1202 1203 Writer ownStream() { this.ownOutputStream = true; return this; } 1204 1205 /** Write and flush the file header. */ 1206 private void writeFileHeader() 1207 throws IOException { 1208 out.write(VERSION); 1209 Text.writeString(out, keyClass.getName()); 1210 Text.writeString(out, valClass.getName()); 1211 1212 out.writeBoolean(this.isCompressed()); 1213 out.writeBoolean(this.isBlockCompressed()); 1214 1215 if (this.isCompressed()) { 1216 Text.writeString(out, (codec.getClass()).getName()); 1217 } 1218 this.metadata.write(out); 1219 out.write(sync); // write the sync bytes 1220 out.flush(); // flush header 1221 } 1222 1223 /** Initialize. */ 1224 @SuppressWarnings("unchecked") 1225 void init(Configuration conf, FSDataOutputStream out, boolean ownStream, 1226 Class keyClass, Class valClass, 1227 CompressionCodec codec, Metadata metadata) 1228 throws IOException { 1229 this.conf = conf; 1230 this.out = out; 1231 this.ownOutputStream = ownStream; 1232 this.keyClass = keyClass; 1233 this.valClass = valClass; 1234 this.codec = codec; 1235 this.metadata = metadata; 1236 SerializationFactory serializationFactory = new SerializationFactory(conf); 1237 this.keySerializer = serializationFactory.getSerializer(keyClass); 1238 if (this.keySerializer == null) { 1239 throw new IOException( 1240 "Could not find a serializer for the Key class: '" 1241 + keyClass.getCanonicalName() + "'. " 1242 + "Please ensure that the configuration '" + 1243 CommonConfigurationKeys.IO_SERIALIZATIONS_KEY + "' is " 1244 + "properly configured, if you're using" 1245 + "custom serialization."); 1246 } 1247 this.keySerializer.open(buffer); 1248 this.uncompressedValSerializer = serializationFactory.getSerializer(valClass); 1249 if (this.uncompressedValSerializer == null) { 1250 throw new IOException( 1251 "Could not find a serializer for the Value class: '" 1252 + valClass.getCanonicalName() + "'. " 1253 + "Please ensure that the configuration '" + 1254 CommonConfigurationKeys.IO_SERIALIZATIONS_KEY + "' is " 1255 + "properly configured, if you're using" 1256 + "custom serialization."); 1257 } 1258 this.uncompressedValSerializer.open(buffer); 1259 if (this.codec != null) { 1260 ReflectionUtils.setConf(this.codec, this.conf); 1261 this.compressor = CodecPool.getCompressor(this.codec); 1262 this.deflateFilter = this.codec.createOutputStream(buffer, compressor); 1263 this.deflateOut = 1264 new DataOutputStream(new BufferedOutputStream(deflateFilter)); 1265 this.compressedValSerializer = serializationFactory.getSerializer(valClass); 1266 if (this.compressedValSerializer == null) { 1267 throw new IOException( 1268 "Could not find a serializer for the Value class: '" 1269 + valClass.getCanonicalName() + "'. " 1270 + "Please ensure that the configuration '" + 1271 CommonConfigurationKeys.IO_SERIALIZATIONS_KEY + "' is " 1272 + "properly configured, if you're using" 1273 + "custom serialization."); 1274 } 1275 this.compressedValSerializer.open(deflateOut); 1276 } 1277 1278 if (appendMode) { 1279 sync(); 1280 } else { 1281 writeFileHeader(); 1282 } 1283 } 1284 1285 /** Returns the class of keys in this file. */ 1286 public Class getKeyClass() { return keyClass; } 1287 1288 /** Returns the class of values in this file. */ 1289 public Class getValueClass() { return valClass; } 1290 1291 /** Returns the compression codec of data in this file. */ 1292 public CompressionCodec getCompressionCodec() { return codec; } 1293 1294 /** create a sync point */ 1295 public void sync() throws IOException { 1296 if (sync != null && lastSyncPos != out.getPos()) { 1297 out.writeInt(SYNC_ESCAPE); // mark the start of the sync 1298 out.write(sync); // write sync 1299 lastSyncPos = out.getPos(); // update lastSyncPos 1300 } 1301 } 1302 1303 /** 1304 * flush all currently written data to the file system 1305 * @deprecated Use {@link #hsync()} or {@link #hflush()} instead 1306 */ 1307 @Deprecated 1308 public void syncFs() throws IOException { 1309 if (out != null) { 1310 out.sync(); // flush contents to file system 1311 } 1312 } 1313 1314 @Override 1315 public void hsync() throws IOException { 1316 if (out != null) { 1317 out.hsync(); 1318 } 1319 } 1320 1321 @Override 1322 public void hflush() throws IOException { 1323 if (out != null) { 1324 out.hflush(); 1325 } 1326 } 1327 1328 /** Returns the configuration of this file. */ 1329 Configuration getConf() { return conf; } 1330 1331 /** Close the file. */ 1332 @Override 1333 public synchronized void close() throws IOException { 1334 keySerializer.close(); 1335 uncompressedValSerializer.close(); 1336 if (compressedValSerializer != null) { 1337 compressedValSerializer.close(); 1338 } 1339 1340 CodecPool.returnCompressor(compressor); 1341 compressor = null; 1342 1343 if (out != null) { 1344 1345 // Close the underlying stream iff we own it... 1346 if (ownOutputStream) { 1347 out.close(); 1348 } else { 1349 out.flush(); 1350 } 1351 out = null; 1352 } 1353 } 1354 1355 synchronized void checkAndWriteSync() throws IOException { 1356 if (sync != null && 1357 out.getPos() >= lastSyncPos+SYNC_INTERVAL) { // time to emit sync 1358 sync(); 1359 } 1360 } 1361 1362 /** Append a key/value pair. */ 1363 public void append(Writable key, Writable val) 1364 throws IOException { 1365 append((Object) key, (Object) val); 1366 } 1367 1368 /** Append a key/value pair. */ 1369 @SuppressWarnings("unchecked") 1370 public synchronized void append(Object key, Object val) 1371 throws IOException { 1372 if (key.getClass() != keyClass) 1373 throw new IOException("wrong key class: "+key.getClass().getName() 1374 +" is not "+keyClass); 1375 if (val.getClass() != valClass) 1376 throw new IOException("wrong value class: "+val.getClass().getName() 1377 +" is not "+valClass); 1378 1379 buffer.reset(); 1380 1381 // Append the 'key' 1382 keySerializer.serialize(key); 1383 int keyLength = buffer.getLength(); 1384 if (keyLength < 0) 1385 throw new IOException("negative length keys not allowed: " + key); 1386 1387 // Append the 'value' 1388 if (compress == CompressionType.RECORD) { 1389 deflateFilter.resetState(); 1390 compressedValSerializer.serialize(val); 1391 deflateOut.flush(); 1392 deflateFilter.finish(); 1393 } else { 1394 uncompressedValSerializer.serialize(val); 1395 } 1396 1397 // Write the record out 1398 checkAndWriteSync(); // sync 1399 out.writeInt(buffer.getLength()); // total record length 1400 out.writeInt(keyLength); // key portion length 1401 out.write(buffer.getData(), 0, buffer.getLength()); // data 1402 } 1403 1404 public synchronized void appendRaw(byte[] keyData, int keyOffset, 1405 int keyLength, ValueBytes val) throws IOException { 1406 if (keyLength < 0) 1407 throw new IOException("negative length keys not allowed: " + keyLength); 1408 1409 int valLength = val.getSize(); 1410 1411 checkAndWriteSync(); 1412 1413 out.writeInt(keyLength+valLength); // total record length 1414 out.writeInt(keyLength); // key portion length 1415 out.write(keyData, keyOffset, keyLength); // key 1416 val.writeUncompressedBytes(out); // value 1417 } 1418 1419 /** Returns the current length of the output file. 1420 * 1421 * <p>This always returns a synchronized position. In other words, 1422 * immediately after calling {@link SequenceFile.Reader#seek(long)} with a position 1423 * returned by this method, {@link SequenceFile.Reader#next(Writable)} may be called. However 1424 * the key may be earlier in the file than key last written when this 1425 * method was called (e.g., with block-compression, it may be the first key 1426 * in the block that was being written when this method was called). 1427 */ 1428 public synchronized long getLength() throws IOException { 1429 return out.getPos(); 1430 } 1431 1432 } // class Writer 1433 1434 /** Write key/compressed-value pairs to a sequence-format file. */ 1435 static class RecordCompressWriter extends Writer { 1436 1437 RecordCompressWriter(Configuration conf, 1438 Option... options) throws IOException { 1439 super(conf, options); 1440 } 1441 1442 /** Append a key/value pair. */ 1443 @Override 1444 @SuppressWarnings("unchecked") 1445 public synchronized void append(Object key, Object val) 1446 throws IOException { 1447 if (key.getClass() != keyClass) 1448 throw new IOException("wrong key class: "+key.getClass().getName() 1449 +" is not "+keyClass); 1450 if (val.getClass() != valClass) 1451 throw new IOException("wrong value class: "+val.getClass().getName() 1452 +" is not "+valClass); 1453 1454 buffer.reset(); 1455 1456 // Append the 'key' 1457 keySerializer.serialize(key); 1458 int keyLength = buffer.getLength(); 1459 if (keyLength < 0) 1460 throw new IOException("negative length keys not allowed: " + key); 1461 1462 // Compress 'value' and append it 1463 deflateFilter.resetState(); 1464 compressedValSerializer.serialize(val); 1465 deflateOut.flush(); 1466 deflateFilter.finish(); 1467 1468 // Write the record out 1469 checkAndWriteSync(); // sync 1470 out.writeInt(buffer.getLength()); // total record length 1471 out.writeInt(keyLength); // key portion length 1472 out.write(buffer.getData(), 0, buffer.getLength()); // data 1473 } 1474 1475 /** Append a key/value pair. */ 1476 @Override 1477 public synchronized void appendRaw(byte[] keyData, int keyOffset, 1478 int keyLength, ValueBytes val) throws IOException { 1479 1480 if (keyLength < 0) 1481 throw new IOException("negative length keys not allowed: " + keyLength); 1482 1483 int valLength = val.getSize(); 1484 1485 checkAndWriteSync(); // sync 1486 out.writeInt(keyLength+valLength); // total record length 1487 out.writeInt(keyLength); // key portion length 1488 out.write(keyData, keyOffset, keyLength); // 'key' data 1489 val.writeCompressedBytes(out); // 'value' data 1490 } 1491 1492 } // RecordCompressionWriter 1493 1494 /** Write compressed key/value blocks to a sequence-format file. */ 1495 static class BlockCompressWriter extends Writer { 1496 1497 private int noBufferedRecords = 0; 1498 1499 private DataOutputBuffer keyLenBuffer = new DataOutputBuffer(); 1500 private DataOutputBuffer keyBuffer = new DataOutputBuffer(); 1501 1502 private DataOutputBuffer valLenBuffer = new DataOutputBuffer(); 1503 private DataOutputBuffer valBuffer = new DataOutputBuffer(); 1504 1505 private final int compressionBlockSize; 1506 1507 BlockCompressWriter(Configuration conf, 1508 Option... options) throws IOException { 1509 super(conf, options); 1510 compressionBlockSize = 1511 conf.getInt("io.seqfile.compress.blocksize", 1000000); 1512 keySerializer.close(); 1513 keySerializer.open(keyBuffer); 1514 uncompressedValSerializer.close(); 1515 uncompressedValSerializer.open(valBuffer); 1516 } 1517 1518 /** Workhorse to check and write out compressed data/lengths */ 1519 private synchronized 1520 void writeBuffer(DataOutputBuffer uncompressedDataBuffer) 1521 throws IOException { 1522 deflateFilter.resetState(); 1523 buffer.reset(); 1524 deflateOut.write(uncompressedDataBuffer.getData(), 0, 1525 uncompressedDataBuffer.getLength()); 1526 deflateOut.flush(); 1527 deflateFilter.finish(); 1528 1529 WritableUtils.writeVInt(out, buffer.getLength()); 1530 out.write(buffer.getData(), 0, buffer.getLength()); 1531 } 1532 1533 /** Compress and flush contents to dfs */ 1534 @Override 1535 public synchronized void sync() throws IOException { 1536 if (noBufferedRecords > 0) { 1537 super.sync(); 1538 1539 // No. of records 1540 WritableUtils.writeVInt(out, noBufferedRecords); 1541 1542 // Write 'keys' and lengths 1543 writeBuffer(keyLenBuffer); 1544 writeBuffer(keyBuffer); 1545 1546 // Write 'values' and lengths 1547 writeBuffer(valLenBuffer); 1548 writeBuffer(valBuffer); 1549 1550 // Flush the file-stream 1551 out.flush(); 1552 1553 // Reset internal states 1554 keyLenBuffer.reset(); 1555 keyBuffer.reset(); 1556 valLenBuffer.reset(); 1557 valBuffer.reset(); 1558 noBufferedRecords = 0; 1559 } 1560 1561 } 1562 1563 /** Close the file. */ 1564 @Override 1565 public synchronized void close() throws IOException { 1566 if (out != null) { 1567 sync(); 1568 } 1569 super.close(); 1570 } 1571 1572 /** Append a key/value pair. */ 1573 @Override 1574 @SuppressWarnings("unchecked") 1575 public synchronized void append(Object key, Object val) 1576 throws IOException { 1577 if (key.getClass() != keyClass) 1578 throw new IOException("wrong key class: "+key+" is not "+keyClass); 1579 if (val.getClass() != valClass) 1580 throw new IOException("wrong value class: "+val+" is not "+valClass); 1581 1582 // Save key/value into respective buffers 1583 int oldKeyLength = keyBuffer.getLength(); 1584 keySerializer.serialize(key); 1585 int keyLength = keyBuffer.getLength() - oldKeyLength; 1586 if (keyLength < 0) 1587 throw new IOException("negative length keys not allowed: " + key); 1588 WritableUtils.writeVInt(keyLenBuffer, keyLength); 1589 1590 int oldValLength = valBuffer.getLength(); 1591 uncompressedValSerializer.serialize(val); 1592 int valLength = valBuffer.getLength() - oldValLength; 1593 WritableUtils.writeVInt(valLenBuffer, valLength); 1594 1595 // Added another key/value pair 1596 ++noBufferedRecords; 1597 1598 // Compress and flush? 1599 int currentBlockSize = keyBuffer.getLength() + valBuffer.getLength(); 1600 if (currentBlockSize >= compressionBlockSize) { 1601 sync(); 1602 } 1603 } 1604 1605 /** Append a key/value pair. */ 1606 @Override 1607 public synchronized void appendRaw(byte[] keyData, int keyOffset, 1608 int keyLength, ValueBytes val) throws IOException { 1609 1610 if (keyLength < 0) 1611 throw new IOException("negative length keys not allowed"); 1612 1613 int valLength = val.getSize(); 1614 1615 // Save key/value data in relevant buffers 1616 WritableUtils.writeVInt(keyLenBuffer, keyLength); 1617 keyBuffer.write(keyData, keyOffset, keyLength); 1618 WritableUtils.writeVInt(valLenBuffer, valLength); 1619 val.writeUncompressedBytes(valBuffer); 1620 1621 // Added another key/value pair 1622 ++noBufferedRecords; 1623 1624 // Compress and flush? 1625 int currentBlockSize = keyBuffer.getLength() + valBuffer.getLength(); 1626 if (currentBlockSize >= compressionBlockSize) { 1627 sync(); 1628 } 1629 } 1630 1631 } // BlockCompressionWriter 1632 1633 /** Get the configured buffer size */ 1634 private static int getBufferSize(Configuration conf) { 1635 return conf.getInt("io.file.buffer.size", 4096); 1636 } 1637 1638 /** Reads key/value pairs from a sequence-format file. */ 1639 public static class Reader implements java.io.Closeable { 1640 private String filename; 1641 private FSDataInputStream in; 1642 private DataOutputBuffer outBuf = new DataOutputBuffer(); 1643 1644 private byte version; 1645 1646 private String keyClassName; 1647 private String valClassName; 1648 private Class keyClass; 1649 private Class valClass; 1650 1651 private CompressionCodec codec = null; 1652 private Metadata metadata = null; 1653 1654 private byte[] sync = new byte[SYNC_HASH_SIZE]; 1655 private byte[] syncCheck = new byte[SYNC_HASH_SIZE]; 1656 private boolean syncSeen; 1657 1658 private long headerEnd; 1659 private long end; 1660 private int keyLength; 1661 private int recordLength; 1662 1663 private boolean decompress; 1664 private boolean blockCompressed; 1665 1666 private Configuration conf; 1667 1668 private int noBufferedRecords = 0; 1669 private boolean lazyDecompress = true; 1670 private boolean valuesDecompressed = true; 1671 1672 private int noBufferedKeys = 0; 1673 private int noBufferedValues = 0; 1674 1675 private DataInputBuffer keyLenBuffer = null; 1676 private CompressionInputStream keyLenInFilter = null; 1677 private DataInputStream keyLenIn = null; 1678 private Decompressor keyLenDecompressor = null; 1679 private DataInputBuffer keyBuffer = null; 1680 private CompressionInputStream keyInFilter = null; 1681 private DataInputStream keyIn = null; 1682 private Decompressor keyDecompressor = null; 1683 1684 private DataInputBuffer valLenBuffer = null; 1685 private CompressionInputStream valLenInFilter = null; 1686 private DataInputStream valLenIn = null; 1687 private Decompressor valLenDecompressor = null; 1688 private DataInputBuffer valBuffer = null; 1689 private CompressionInputStream valInFilter = null; 1690 private DataInputStream valIn = null; 1691 private Decompressor valDecompressor = null; 1692 1693 private Deserializer keyDeserializer; 1694 private Deserializer valDeserializer; 1695 1696 /** 1697 * A tag interface for all of the Reader options 1698 */ 1699 public static interface Option {} 1700 1701 /** 1702 * Create an option to specify the path name of the sequence file. 1703 * @param value the path to read 1704 * @return a new option 1705 */ 1706 public static Option file(Path value) { 1707 return new FileOption(value); 1708 } 1709 1710 /** 1711 * Create an option to specify the stream with the sequence file. 1712 * @param value the stream to read. 1713 * @return a new option 1714 */ 1715 public static Option stream(FSDataInputStream value) { 1716 return new InputStreamOption(value); 1717 } 1718 1719 /** 1720 * Create an option to specify the starting byte to read. 1721 * @param value the number of bytes to skip over 1722 * @return a new option 1723 */ 1724 public static Option start(long value) { 1725 return new StartOption(value); 1726 } 1727 1728 /** 1729 * Create an option to specify the number of bytes to read. 1730 * @param value the number of bytes to read 1731 * @return a new option 1732 */ 1733 public static Option length(long value) { 1734 return new LengthOption(value); 1735 } 1736 1737 /** 1738 * Create an option with the buffer size for reading the given pathname. 1739 * @param value the number of bytes to buffer 1740 * @return a new option 1741 */ 1742 public static Option bufferSize(int value) { 1743 return new BufferSizeOption(value); 1744 } 1745 1746 private static class FileOption extends Options.PathOption 1747 implements Option { 1748 private FileOption(Path value) { 1749 super(value); 1750 } 1751 } 1752 1753 private static class InputStreamOption 1754 extends Options.FSDataInputStreamOption 1755 implements Option { 1756 private InputStreamOption(FSDataInputStream value) { 1757 super(value); 1758 } 1759 } 1760 1761 private static class StartOption extends Options.LongOption 1762 implements Option { 1763 private StartOption(long value) { 1764 super(value); 1765 } 1766 } 1767 1768 private static class LengthOption extends Options.LongOption 1769 implements Option { 1770 private LengthOption(long value) { 1771 super(value); 1772 } 1773 } 1774 1775 private static class BufferSizeOption extends Options.IntegerOption 1776 implements Option { 1777 private BufferSizeOption(int value) { 1778 super(value); 1779 } 1780 } 1781 1782 // only used directly 1783 private static class OnlyHeaderOption extends Options.BooleanOption 1784 implements Option { 1785 private OnlyHeaderOption() { 1786 super(true); 1787 } 1788 } 1789 1790 public Reader(Configuration conf, Option... opts) throws IOException { 1791 // Look up the options, these are null if not set 1792 FileOption fileOpt = Options.getOption(FileOption.class, opts); 1793 InputStreamOption streamOpt = 1794 Options.getOption(InputStreamOption.class, opts); 1795 StartOption startOpt = Options.getOption(StartOption.class, opts); 1796 LengthOption lenOpt = Options.getOption(LengthOption.class, opts); 1797 BufferSizeOption bufOpt = Options.getOption(BufferSizeOption.class,opts); 1798 OnlyHeaderOption headerOnly = 1799 Options.getOption(OnlyHeaderOption.class, opts); 1800 // check for consistency 1801 if ((fileOpt == null) == (streamOpt == null)) { 1802 throw new 1803 IllegalArgumentException("File or stream option must be specified"); 1804 } 1805 if (fileOpt == null && bufOpt != null) { 1806 throw new IllegalArgumentException("buffer size can only be set when" + 1807 " a file is specified."); 1808 } 1809 // figure out the real values 1810 Path filename = null; 1811 FSDataInputStream file; 1812 final long len; 1813 if (fileOpt != null) { 1814 filename = fileOpt.getValue(); 1815 FileSystem fs = filename.getFileSystem(conf); 1816 int bufSize = bufOpt == null ? getBufferSize(conf): bufOpt.getValue(); 1817 len = null == lenOpt 1818 ? fs.getFileStatus(filename).getLen() 1819 : lenOpt.getValue(); 1820 file = openFile(fs, filename, bufSize, len); 1821 } else { 1822 len = null == lenOpt ? Long.MAX_VALUE : lenOpt.getValue(); 1823 file = streamOpt.getValue(); 1824 } 1825 long start = startOpt == null ? 0 : startOpt.getValue(); 1826 // really set up 1827 initialize(filename, file, start, len, conf, headerOnly != null); 1828 } 1829 1830 /** 1831 * Construct a reader by opening a file from the given file system. 1832 * @param fs The file system used to open the file. 1833 * @param file The file being read. 1834 * @param conf Configuration 1835 * @throws IOException 1836 * @deprecated Use Reader(Configuration, Option...) instead. 1837 */ 1838 @Deprecated 1839 public Reader(FileSystem fs, Path file, 1840 Configuration conf) throws IOException { 1841 this(conf, file(file.makeQualified(fs))); 1842 } 1843 1844 /** 1845 * Construct a reader by the given input stream. 1846 * @param in An input stream. 1847 * @param buffersize unused 1848 * @param start The starting position. 1849 * @param length The length being read. 1850 * @param conf Configuration 1851 * @throws IOException 1852 * @deprecated Use Reader(Configuration, Reader.Option...) instead. 1853 */ 1854 @Deprecated 1855 public Reader(FSDataInputStream in, int buffersize, 1856 long start, long length, Configuration conf) throws IOException { 1857 this(conf, stream(in), start(start), length(length)); 1858 } 1859 1860 /** Common work of the constructors. */ 1861 private void initialize(Path filename, FSDataInputStream in, 1862 long start, long length, Configuration conf, 1863 boolean tempReader) throws IOException { 1864 if (in == null) { 1865 throw new IllegalArgumentException("in == null"); 1866 } 1867 this.filename = filename == null ? "<unknown>" : filename.toString(); 1868 this.in = in; 1869 this.conf = conf; 1870 boolean succeeded = false; 1871 try { 1872 seek(start); 1873 this.end = this.in.getPos() + length; 1874 // if it wrapped around, use the max 1875 if (end < length) { 1876 end = Long.MAX_VALUE; 1877 } 1878 init(tempReader); 1879 succeeded = true; 1880 } finally { 1881 if (!succeeded) { 1882 IOUtils.cleanup(LOG, this.in); 1883 } 1884 } 1885 } 1886 1887 /** 1888 * Override this method to specialize the type of 1889 * {@link FSDataInputStream} returned. 1890 * @param fs The file system used to open the file. 1891 * @param file The file being read. 1892 * @param bufferSize The buffer size used to read the file. 1893 * @param length The length being read if it is >= 0. Otherwise, 1894 * the length is not available. 1895 * @return The opened stream. 1896 * @throws IOException 1897 */ 1898 protected FSDataInputStream openFile(FileSystem fs, Path file, 1899 int bufferSize, long length) throws IOException { 1900 return fs.open(file, bufferSize); 1901 } 1902 1903 /** 1904 * Initialize the {@link Reader} 1905 * @param tmpReader <code>true</code> if we are constructing a temporary 1906 * reader {@link SequenceFile.Sorter.cloneFileAttributes}, 1907 * and hence do not initialize every component; 1908 * <code>false</code> otherwise. 1909 * @throws IOException 1910 */ 1911 private void init(boolean tempReader) throws IOException { 1912 byte[] versionBlock = new byte[VERSION.length]; 1913 String exceptionMsg = this + " not a SequenceFile"; 1914 1915 // Try to read sequence file header. 1916 try { 1917 in.readFully(versionBlock); 1918 } catch (EOFException e) { 1919 throw new EOFException(exceptionMsg); 1920 } 1921 1922 if ((versionBlock[0] != VERSION[0]) || 1923 (versionBlock[1] != VERSION[1]) || 1924 (versionBlock[2] != VERSION[2])) { 1925 throw new IOException(this + " not a SequenceFile"); 1926 } 1927 1928 // Set 'version' 1929 version = versionBlock[3]; 1930 if (version > VERSION[3]) { 1931 throw new VersionMismatchException(VERSION[3], version); 1932 } 1933 1934 if (version < BLOCK_COMPRESS_VERSION) { 1935 UTF8 className = new UTF8(); 1936 1937 className.readFields(in); 1938 keyClassName = className.toStringChecked(); // key class name 1939 1940 className.readFields(in); 1941 valClassName = className.toStringChecked(); // val class name 1942 } else { 1943 keyClassName = Text.readString(in); 1944 valClassName = Text.readString(in); 1945 } 1946 1947 if (version > 2) { // if version > 2 1948 this.decompress = in.readBoolean(); // is compressed? 1949 } else { 1950 decompress = false; 1951 } 1952 1953 if (version >= BLOCK_COMPRESS_VERSION) { // if version >= 4 1954 this.blockCompressed = in.readBoolean(); // is block-compressed? 1955 } else { 1956 blockCompressed = false; 1957 } 1958 1959 // if version >= 5 1960 // setup the compression codec 1961 if (decompress) { 1962 if (version >= CUSTOM_COMPRESS_VERSION) { 1963 String codecClassname = Text.readString(in); 1964 try { 1965 Class<? extends CompressionCodec> codecClass 1966 = conf.getClassByName(codecClassname).asSubclass(CompressionCodec.class); 1967 this.codec = ReflectionUtils.newInstance(codecClass, conf); 1968 } catch (ClassNotFoundException cnfe) { 1969 throw new IllegalArgumentException("Unknown codec: " + 1970 codecClassname, cnfe); 1971 } 1972 } else { 1973 codec = new DefaultCodec(); 1974 ((Configurable)codec).setConf(conf); 1975 } 1976 } 1977 1978 this.metadata = new Metadata(); 1979 if (version >= VERSION_WITH_METADATA) { // if version >= 6 1980 this.metadata.readFields(in); 1981 } 1982 1983 if (version > 1) { // if version > 1 1984 in.readFully(sync); // read sync bytes 1985 headerEnd = in.getPos(); // record end of header 1986 } 1987 1988 // Initialize... *not* if this we are constructing a temporary Reader 1989 if (!tempReader) { 1990 valBuffer = new DataInputBuffer(); 1991 if (decompress) { 1992 valDecompressor = CodecPool.getDecompressor(codec); 1993 valInFilter = codec.createInputStream(valBuffer, valDecompressor); 1994 valIn = new DataInputStream(valInFilter); 1995 } else { 1996 valIn = valBuffer; 1997 } 1998 1999 if (blockCompressed) { 2000 keyLenBuffer = new DataInputBuffer(); 2001 keyBuffer = new DataInputBuffer(); 2002 valLenBuffer = new DataInputBuffer(); 2003 2004 keyLenDecompressor = CodecPool.getDecompressor(codec); 2005 keyLenInFilter = codec.createInputStream(keyLenBuffer, 2006 keyLenDecompressor); 2007 keyLenIn = new DataInputStream(keyLenInFilter); 2008 2009 keyDecompressor = CodecPool.getDecompressor(codec); 2010 keyInFilter = codec.createInputStream(keyBuffer, keyDecompressor); 2011 keyIn = new DataInputStream(keyInFilter); 2012 2013 valLenDecompressor = CodecPool.getDecompressor(codec); 2014 valLenInFilter = codec.createInputStream(valLenBuffer, 2015 valLenDecompressor); 2016 valLenIn = new DataInputStream(valLenInFilter); 2017 } 2018 2019 SerializationFactory serializationFactory = 2020 new SerializationFactory(conf); 2021 this.keyDeserializer = 2022 getDeserializer(serializationFactory, getKeyClass()); 2023 if (this.keyDeserializer == null) { 2024 throw new IOException( 2025 "Could not find a deserializer for the Key class: '" 2026 + getKeyClass().getCanonicalName() + "'. " 2027 + "Please ensure that the configuration '" + 2028 CommonConfigurationKeys.IO_SERIALIZATIONS_KEY + "' is " 2029 + "properly configured, if you're using " 2030 + "custom serialization."); 2031 } 2032 if (!blockCompressed) { 2033 this.keyDeserializer.open(valBuffer); 2034 } else { 2035 this.keyDeserializer.open(keyIn); 2036 } 2037 this.valDeserializer = 2038 getDeserializer(serializationFactory, getValueClass()); 2039 if (this.valDeserializer == null) { 2040 throw new IOException( 2041 "Could not find a deserializer for the Value class: '" 2042 + getValueClass().getCanonicalName() + "'. " 2043 + "Please ensure that the configuration '" + 2044 CommonConfigurationKeys.IO_SERIALIZATIONS_KEY + "' is " 2045 + "properly configured, if you're using " 2046 + "custom serialization."); 2047 } 2048 this.valDeserializer.open(valIn); 2049 } 2050 } 2051 2052 @SuppressWarnings("unchecked") 2053 private Deserializer getDeserializer(SerializationFactory sf, Class c) { 2054 return sf.getDeserializer(c); 2055 } 2056 2057 /** Close the file. */ 2058 @Override 2059 public synchronized void close() throws IOException { 2060 // Return the decompressors to the pool 2061 CodecPool.returnDecompressor(keyLenDecompressor); 2062 CodecPool.returnDecompressor(keyDecompressor); 2063 CodecPool.returnDecompressor(valLenDecompressor); 2064 CodecPool.returnDecompressor(valDecompressor); 2065 keyLenDecompressor = keyDecompressor = null; 2066 valLenDecompressor = valDecompressor = null; 2067 2068 if (keyDeserializer != null) { 2069 keyDeserializer.close(); 2070 } 2071 if (valDeserializer != null) { 2072 valDeserializer.close(); 2073 } 2074 2075 // Close the input-stream 2076 in.close(); 2077 } 2078 2079 /** Returns the name of the key class. */ 2080 public String getKeyClassName() { 2081 return keyClassName; 2082 } 2083 2084 /** Returns the class of keys in this file. */ 2085 public synchronized Class<?> getKeyClass() { 2086 if (null == keyClass) { 2087 try { 2088 keyClass = WritableName.getClass(getKeyClassName(), conf); 2089 } catch (IOException e) { 2090 throw new RuntimeException(e); 2091 } 2092 } 2093 return keyClass; 2094 } 2095 2096 /** Returns the name of the value class. */ 2097 public String getValueClassName() { 2098 return valClassName; 2099 } 2100 2101 /** Returns the class of values in this file. */ 2102 public synchronized Class<?> getValueClass() { 2103 if (null == valClass) { 2104 try { 2105 valClass = WritableName.getClass(getValueClassName(), conf); 2106 } catch (IOException e) { 2107 throw new RuntimeException(e); 2108 } 2109 } 2110 return valClass; 2111 } 2112 2113 /** Returns true if values are compressed. */ 2114 public boolean isCompressed() { return decompress; } 2115 2116 /** Returns true if records are block-compressed. */ 2117 public boolean isBlockCompressed() { return blockCompressed; } 2118 2119 /** Returns the compression codec of data in this file. */ 2120 public CompressionCodec getCompressionCodec() { return codec; } 2121 2122 private byte[] getSync() { 2123 return sync; 2124 } 2125 2126 private byte getVersion() { 2127 return version; 2128 } 2129 2130 /** 2131 * Get the compression type for this file. 2132 * @return the compression type 2133 */ 2134 public CompressionType getCompressionType() { 2135 if (decompress) { 2136 return blockCompressed ? CompressionType.BLOCK : CompressionType.RECORD; 2137 } else { 2138 return CompressionType.NONE; 2139 } 2140 } 2141 2142 /** Returns the metadata object of the file */ 2143 public Metadata getMetadata() { 2144 return this.metadata; 2145 } 2146 2147 /** Returns the configuration used for this file. */ 2148 Configuration getConf() { return conf; } 2149 2150 /** Read a compressed buffer */ 2151 private synchronized void readBuffer(DataInputBuffer buffer, 2152 CompressionInputStream filter) throws IOException { 2153 // Read data into a temporary buffer 2154 DataOutputBuffer dataBuffer = new DataOutputBuffer(); 2155 2156 try { 2157 int dataBufferLength = WritableUtils.readVInt(in); 2158 dataBuffer.write(in, dataBufferLength); 2159 2160 // Set up 'buffer' connected to the input-stream 2161 buffer.reset(dataBuffer.getData(), 0, dataBuffer.getLength()); 2162 } finally { 2163 dataBuffer.close(); 2164 } 2165 2166 // Reset the codec 2167 filter.resetState(); 2168 } 2169 2170 /** Read the next 'compressed' block */ 2171 private synchronized void readBlock() throws IOException { 2172 // Check if we need to throw away a whole block of 2173 // 'values' due to 'lazy decompression' 2174 if (lazyDecompress && !valuesDecompressed) { 2175 in.seek(WritableUtils.readVInt(in)+in.getPos()); 2176 in.seek(WritableUtils.readVInt(in)+in.getPos()); 2177 } 2178 2179 // Reset internal states 2180 noBufferedKeys = 0; noBufferedValues = 0; noBufferedRecords = 0; 2181 valuesDecompressed = false; 2182 2183 //Process sync 2184 if (sync != null) { 2185 in.readInt(); 2186 in.readFully(syncCheck); // read syncCheck 2187 if (!Arrays.equals(sync, syncCheck)) // check it 2188 throw new IOException("File is corrupt!"); 2189 } 2190 syncSeen = true; 2191 2192 // Read number of records in this block 2193 noBufferedRecords = WritableUtils.readVInt(in); 2194 2195 // Read key lengths and keys 2196 readBuffer(keyLenBuffer, keyLenInFilter); 2197 readBuffer(keyBuffer, keyInFilter); 2198 noBufferedKeys = noBufferedRecords; 2199 2200 // Read value lengths and values 2201 if (!lazyDecompress) { 2202 readBuffer(valLenBuffer, valLenInFilter); 2203 readBuffer(valBuffer, valInFilter); 2204 noBufferedValues = noBufferedRecords; 2205 valuesDecompressed = true; 2206 } 2207 } 2208 2209 /** 2210 * Position valLenIn/valIn to the 'value' 2211 * corresponding to the 'current' key 2212 */ 2213 private synchronized void seekToCurrentValue() throws IOException { 2214 if (!blockCompressed) { 2215 if (decompress) { 2216 valInFilter.resetState(); 2217 } 2218 valBuffer.reset(); 2219 } else { 2220 // Check if this is the first value in the 'block' to be read 2221 if (lazyDecompress && !valuesDecompressed) { 2222 // Read the value lengths and values 2223 readBuffer(valLenBuffer, valLenInFilter); 2224 readBuffer(valBuffer, valInFilter); 2225 noBufferedValues = noBufferedRecords; 2226 valuesDecompressed = true; 2227 } 2228 2229 // Calculate the no. of bytes to skip 2230 // Note: 'current' key has already been read! 2231 int skipValBytes = 0; 2232 int currentKey = noBufferedKeys + 1; 2233 for (int i=noBufferedValues; i > currentKey; --i) { 2234 skipValBytes += WritableUtils.readVInt(valLenIn); 2235 --noBufferedValues; 2236 } 2237 2238 // Skip to the 'val' corresponding to 'current' key 2239 if (skipValBytes > 0) { 2240 if (valIn.skipBytes(skipValBytes) != skipValBytes) { 2241 throw new IOException("Failed to seek to " + currentKey + 2242 "(th) value!"); 2243 } 2244 } 2245 } 2246 } 2247 2248 /** 2249 * Get the 'value' corresponding to the last read 'key'. 2250 * @param val : The 'value' to be read. 2251 * @throws IOException 2252 */ 2253 public synchronized void getCurrentValue(Writable val) 2254 throws IOException { 2255 if (val instanceof Configurable) { 2256 ((Configurable) val).setConf(this.conf); 2257 } 2258 2259 // Position stream to 'current' value 2260 seekToCurrentValue(); 2261 2262 if (!blockCompressed) { 2263 val.readFields(valIn); 2264 2265 if (valIn.read() > 0) { 2266 LOG.info("available bytes: " + valIn.available()); 2267 throw new IOException(val+" read "+(valBuffer.getPosition()-keyLength) 2268 + " bytes, should read " + 2269 (valBuffer.getLength()-keyLength)); 2270 } 2271 } else { 2272 // Get the value 2273 int valLength = WritableUtils.readVInt(valLenIn); 2274 val.readFields(valIn); 2275 2276 // Read another compressed 'value' 2277 --noBufferedValues; 2278 2279 // Sanity check 2280 if ((valLength < 0) && LOG.isDebugEnabled()) { 2281 LOG.debug(val + " is a zero-length value"); 2282 } 2283 } 2284 2285 } 2286 2287 /** 2288 * Get the 'value' corresponding to the last read 'key'. 2289 * @param val : The 'value' to be read. 2290 * @throws IOException 2291 */ 2292 public synchronized Object getCurrentValue(Object val) 2293 throws IOException { 2294 if (val instanceof Configurable) { 2295 ((Configurable) val).setConf(this.conf); 2296 } 2297 2298 // Position stream to 'current' value 2299 seekToCurrentValue(); 2300 2301 if (!blockCompressed) { 2302 val = deserializeValue(val); 2303 2304 if (valIn.read() > 0) { 2305 LOG.info("available bytes: " + valIn.available()); 2306 throw new IOException(val+" read "+(valBuffer.getPosition()-keyLength) 2307 + " bytes, should read " + 2308 (valBuffer.getLength()-keyLength)); 2309 } 2310 } else { 2311 // Get the value 2312 int valLength = WritableUtils.readVInt(valLenIn); 2313 val = deserializeValue(val); 2314 2315 // Read another compressed 'value' 2316 --noBufferedValues; 2317 2318 // Sanity check 2319 if ((valLength < 0) && LOG.isDebugEnabled()) { 2320 LOG.debug(val + " is a zero-length value"); 2321 } 2322 } 2323 return val; 2324 2325 } 2326 2327 @SuppressWarnings("unchecked") 2328 private Object deserializeValue(Object val) throws IOException { 2329 return valDeserializer.deserialize(val); 2330 } 2331 2332 /** Read the next key in the file into <code>key</code>, skipping its 2333 * value. True if another entry exists, and false at end of file. */ 2334 public synchronized boolean next(Writable key) throws IOException { 2335 if (key.getClass() != getKeyClass()) 2336 throw new IOException("wrong key class: "+key.getClass().getName() 2337 +" is not "+keyClass); 2338 2339 if (!blockCompressed) { 2340 outBuf.reset(); 2341 2342 keyLength = next(outBuf); 2343 if (keyLength < 0) 2344 return false; 2345 2346 valBuffer.reset(outBuf.getData(), outBuf.getLength()); 2347 2348 key.readFields(valBuffer); 2349 valBuffer.mark(0); 2350 if (valBuffer.getPosition() != keyLength) 2351 throw new IOException(key + " read " + valBuffer.getPosition() 2352 + " bytes, should read " + keyLength); 2353 } else { 2354 //Reset syncSeen 2355 syncSeen = false; 2356 2357 if (noBufferedKeys == 0) { 2358 try { 2359 readBlock(); 2360 } catch (EOFException eof) { 2361 return false; 2362 } 2363 } 2364 2365 int keyLength = WritableUtils.readVInt(keyLenIn); 2366 2367 // Sanity check 2368 if (keyLength < 0) { 2369 return false; 2370 } 2371 2372 //Read another compressed 'key' 2373 key.readFields(keyIn); 2374 --noBufferedKeys; 2375 } 2376 2377 return true; 2378 } 2379 2380 /** Read the next key/value pair in the file into <code>key</code> and 2381 * <code>val</code>. Returns true if such a pair exists and false when at 2382 * end of file */ 2383 public synchronized boolean next(Writable key, Writable val) 2384 throws IOException { 2385 if (val.getClass() != getValueClass()) 2386 throw new IOException("wrong value class: "+val+" is not "+valClass); 2387 2388 boolean more = next(key); 2389 2390 if (more) { 2391 getCurrentValue(val); 2392 } 2393 2394 return more; 2395 } 2396 2397 /** 2398 * Read and return the next record length, potentially skipping over 2399 * a sync block. 2400 * @return the length of the next record or -1 if there is no next record 2401 * @throws IOException 2402 */ 2403 private synchronized int readRecordLength() throws IOException { 2404 if (in.getPos() >= end) { 2405 return -1; 2406 } 2407 int length = in.readInt(); 2408 if (version > 1 && sync != null && 2409 length == SYNC_ESCAPE) { // process a sync entry 2410 in.readFully(syncCheck); // read syncCheck 2411 if (!Arrays.equals(sync, syncCheck)) // check it 2412 throw new IOException("File is corrupt!"); 2413 syncSeen = true; 2414 if (in.getPos() >= end) { 2415 return -1; 2416 } 2417 length = in.readInt(); // re-read length 2418 } else { 2419 syncSeen = false; 2420 } 2421 2422 return length; 2423 } 2424 2425 /** Read the next key/value pair in the file into <code>buffer</code>. 2426 * Returns the length of the key read, or -1 if at end of file. The length 2427 * of the value may be computed by calling buffer.getLength() before and 2428 * after calls to this method. */ 2429 /** @deprecated Call {@link #nextRaw(DataOutputBuffer,SequenceFile.ValueBytes)}. */ 2430 @Deprecated 2431 synchronized int next(DataOutputBuffer buffer) throws IOException { 2432 // Unsupported for block-compressed sequence files 2433 if (blockCompressed) { 2434 throw new IOException("Unsupported call for block-compressed" + 2435 " SequenceFiles - use SequenceFile.Reader.next(DataOutputStream, ValueBytes)"); 2436 } 2437 try { 2438 int length = readRecordLength(); 2439 if (length == -1) { 2440 return -1; 2441 } 2442 int keyLength = in.readInt(); 2443 buffer.write(in, length); 2444 return keyLength; 2445 } catch (ChecksumException e) { // checksum failure 2446 handleChecksumException(e); 2447 return next(buffer); 2448 } 2449 } 2450 2451 public ValueBytes createValueBytes() { 2452 ValueBytes val = null; 2453 if (!decompress || blockCompressed) { 2454 val = new UncompressedBytes(); 2455 } else { 2456 val = new CompressedBytes(codec); 2457 } 2458 return val; 2459 } 2460 2461 /** 2462 * Read 'raw' records. 2463 * @param key - The buffer into which the key is read 2464 * @param val - The 'raw' value 2465 * @return Returns the total record length or -1 for end of file 2466 * @throws IOException 2467 */ 2468 public synchronized int nextRaw(DataOutputBuffer key, ValueBytes val) 2469 throws IOException { 2470 if (!blockCompressed) { 2471 int length = readRecordLength(); 2472 if (length == -1) { 2473 return -1; 2474 } 2475 int keyLength = in.readInt(); 2476 int valLength = length - keyLength; 2477 key.write(in, keyLength); 2478 if (decompress) { 2479 CompressedBytes value = (CompressedBytes)val; 2480 value.reset(in, valLength); 2481 } else { 2482 UncompressedBytes value = (UncompressedBytes)val; 2483 value.reset(in, valLength); 2484 } 2485 2486 return length; 2487 } else { 2488 //Reset syncSeen 2489 syncSeen = false; 2490 2491 // Read 'key' 2492 if (noBufferedKeys == 0) { 2493 if (in.getPos() >= end) 2494 return -1; 2495 2496 try { 2497 readBlock(); 2498 } catch (EOFException eof) { 2499 return -1; 2500 } 2501 } 2502 int keyLength = WritableUtils.readVInt(keyLenIn); 2503 if (keyLength < 0) { 2504 throw new IOException("zero length key found!"); 2505 } 2506 key.write(keyIn, keyLength); 2507 --noBufferedKeys; 2508 2509 // Read raw 'value' 2510 seekToCurrentValue(); 2511 int valLength = WritableUtils.readVInt(valLenIn); 2512 UncompressedBytes rawValue = (UncompressedBytes)val; 2513 rawValue.reset(valIn, valLength); 2514 --noBufferedValues; 2515 2516 return (keyLength+valLength); 2517 } 2518 2519 } 2520 2521 /** 2522 * Read 'raw' keys. 2523 * @param key - The buffer into which the key is read 2524 * @return Returns the key length or -1 for end of file 2525 * @throws IOException 2526 */ 2527 public synchronized int nextRawKey(DataOutputBuffer key) 2528 throws IOException { 2529 if (!blockCompressed) { 2530 recordLength = readRecordLength(); 2531 if (recordLength == -1) { 2532 return -1; 2533 } 2534 keyLength = in.readInt(); 2535 key.write(in, keyLength); 2536 return keyLength; 2537 } else { 2538 //Reset syncSeen 2539 syncSeen = false; 2540 2541 // Read 'key' 2542 if (noBufferedKeys == 0) { 2543 if (in.getPos() >= end) 2544 return -1; 2545 2546 try { 2547 readBlock(); 2548 } catch (EOFException eof) { 2549 return -1; 2550 } 2551 } 2552 int keyLength = WritableUtils.readVInt(keyLenIn); 2553 if (keyLength < 0) { 2554 throw new IOException("zero length key found!"); 2555 } 2556 key.write(keyIn, keyLength); 2557 --noBufferedKeys; 2558 2559 return keyLength; 2560 } 2561 2562 } 2563 2564 /** Read the next key in the file, skipping its 2565 * value. Return null at end of file. */ 2566 public synchronized Object next(Object key) throws IOException { 2567 if (key != null && key.getClass() != getKeyClass()) { 2568 throw new IOException("wrong key class: "+key.getClass().getName() 2569 +" is not "+keyClass); 2570 } 2571 2572 if (!blockCompressed) { 2573 outBuf.reset(); 2574 2575 keyLength = next(outBuf); 2576 if (keyLength < 0) 2577 return null; 2578 2579 valBuffer.reset(outBuf.getData(), outBuf.getLength()); 2580 2581 key = deserializeKey(key); 2582 valBuffer.mark(0); 2583 if (valBuffer.getPosition() != keyLength) 2584 throw new IOException(key + " read " + valBuffer.getPosition() 2585 + " bytes, should read " + keyLength); 2586 } else { 2587 //Reset syncSeen 2588 syncSeen = false; 2589 2590 if (noBufferedKeys == 0) { 2591 try { 2592 readBlock(); 2593 } catch (EOFException eof) { 2594 return null; 2595 } 2596 } 2597 2598 int keyLength = WritableUtils.readVInt(keyLenIn); 2599 2600 // Sanity check 2601 if (keyLength < 0) { 2602 return null; 2603 } 2604 2605 //Read another compressed 'key' 2606 key = deserializeKey(key); 2607 --noBufferedKeys; 2608 } 2609 2610 return key; 2611 } 2612 2613 @SuppressWarnings("unchecked") 2614 private Object deserializeKey(Object key) throws IOException { 2615 return keyDeserializer.deserialize(key); 2616 } 2617 2618 /** 2619 * Read 'raw' values. 2620 * @param val - The 'raw' value 2621 * @return Returns the value length 2622 * @throws IOException 2623 */ 2624 public synchronized int nextRawValue(ValueBytes val) 2625 throws IOException { 2626 2627 // Position stream to current value 2628 seekToCurrentValue(); 2629 2630 if (!blockCompressed) { 2631 int valLength = recordLength - keyLength; 2632 if (decompress) { 2633 CompressedBytes value = (CompressedBytes)val; 2634 value.reset(in, valLength); 2635 } else { 2636 UncompressedBytes value = (UncompressedBytes)val; 2637 value.reset(in, valLength); 2638 } 2639 2640 return valLength; 2641 } else { 2642 int valLength = WritableUtils.readVInt(valLenIn); 2643 UncompressedBytes rawValue = (UncompressedBytes)val; 2644 rawValue.reset(valIn, valLength); 2645 --noBufferedValues; 2646 return valLength; 2647 } 2648 2649 } 2650 2651 private void handleChecksumException(ChecksumException e) 2652 throws IOException { 2653 if (this.conf.getBoolean("io.skip.checksum.errors", false)) { 2654 LOG.warn("Bad checksum at "+getPosition()+". Skipping entries."); 2655 sync(getPosition()+this.conf.getInt("io.bytes.per.checksum", 512)); 2656 } else { 2657 throw e; 2658 } 2659 } 2660 2661 /** disables sync. often invoked for tmp files */ 2662 synchronized void ignoreSync() { 2663 sync = null; 2664 } 2665 2666 /** Set the current byte position in the input file. 2667 * 2668 * <p>The position passed must be a position returned by {@link 2669 * SequenceFile.Writer#getLength()} when writing this file. To seek to an arbitrary 2670 * position, use {@link SequenceFile.Reader#sync(long)}. 2671 */ 2672 public synchronized void seek(long position) throws IOException { 2673 in.seek(position); 2674 if (blockCompressed) { // trigger block read 2675 noBufferedKeys = 0; 2676 valuesDecompressed = true; 2677 } 2678 } 2679 2680 /** Seek to the next sync mark past a given position.*/ 2681 public synchronized void sync(long position) throws IOException { 2682 if (position+SYNC_SIZE >= end) { 2683 seek(end); 2684 return; 2685 } 2686 2687 if (position < headerEnd) { 2688 // seek directly to first record 2689 in.seek(headerEnd); 2690 // note the sync marker "seen" in the header 2691 syncSeen = true; 2692 return; 2693 } 2694 2695 try { 2696 seek(position+4); // skip escape 2697 in.readFully(syncCheck); 2698 int syncLen = sync.length; 2699 for (int i = 0; in.getPos() < end; i++) { 2700 int j = 0; 2701 for (; j < syncLen; j++) { 2702 if (sync[j] != syncCheck[(i+j)%syncLen]) 2703 break; 2704 } 2705 if (j == syncLen) { 2706 in.seek(in.getPos() - SYNC_SIZE); // position before sync 2707 return; 2708 } 2709 syncCheck[i%syncLen] = in.readByte(); 2710 } 2711 } catch (ChecksumException e) { // checksum failure 2712 handleChecksumException(e); 2713 } 2714 } 2715 2716 /** Returns true iff the previous call to next passed a sync mark.*/ 2717 public synchronized boolean syncSeen() { return syncSeen; } 2718 2719 /** Return the current byte position in the input file. */ 2720 public synchronized long getPosition() throws IOException { 2721 return in.getPos(); 2722 } 2723 2724 /** Returns the name of the file. */ 2725 @Override 2726 public String toString() { 2727 return filename; 2728 } 2729 2730 } 2731 2732 /** Sorts key/value pairs in a sequence-format file. 2733 * 2734 * <p>For best performance, applications should make sure that the {@link 2735 * Writable#readFields(DataInput)} implementation of their keys is 2736 * very efficient. In particular, it should avoid allocating memory. 2737 */ 2738 public static class Sorter { 2739 2740 private RawComparator comparator; 2741 2742 private MergeSort mergeSort; //the implementation of merge sort 2743 2744 private Path[] inFiles; // when merging or sorting 2745 2746 private Path outFile; 2747 2748 private int memory; // bytes 2749 private int factor; // merged per pass 2750 2751 private FileSystem fs = null; 2752 2753 private Class keyClass; 2754 private Class valClass; 2755 2756 private Configuration conf; 2757 private Metadata metadata; 2758 2759 private Progressable progressable = null; 2760 2761 /** Sort and merge files containing the named classes. */ 2762 public Sorter(FileSystem fs, Class<? extends WritableComparable> keyClass, 2763 Class valClass, Configuration conf) { 2764 this(fs, WritableComparator.get(keyClass, conf), keyClass, valClass, conf); 2765 } 2766 2767 /** Sort and merge using an arbitrary {@link RawComparator}. */ 2768 public Sorter(FileSystem fs, RawComparator comparator, Class keyClass, 2769 Class valClass, Configuration conf) { 2770 this(fs, comparator, keyClass, valClass, conf, new Metadata()); 2771 } 2772 2773 /** Sort and merge using an arbitrary {@link RawComparator}. */ 2774 public Sorter(FileSystem fs, RawComparator comparator, Class keyClass, 2775 Class valClass, Configuration conf, Metadata metadata) { 2776 this.fs = fs; 2777 this.comparator = comparator; 2778 this.keyClass = keyClass; 2779 this.valClass = valClass; 2780 this.memory = conf.getInt("io.sort.mb", 100) * 1024 * 1024; 2781 this.factor = conf.getInt("io.sort.factor", 100); 2782 this.conf = conf; 2783 this.metadata = metadata; 2784 } 2785 2786 /** Set the number of streams to merge at once.*/ 2787 public void setFactor(int factor) { this.factor = factor; } 2788 2789 /** Get the number of streams to merge at once.*/ 2790 public int getFactor() { return factor; } 2791 2792 /** Set the total amount of buffer memory, in bytes.*/ 2793 public void setMemory(int memory) { this.memory = memory; } 2794 2795 /** Get the total amount of buffer memory, in bytes.*/ 2796 public int getMemory() { return memory; } 2797 2798 /** Set the progressable object in order to report progress. */ 2799 public void setProgressable(Progressable progressable) { 2800 this.progressable = progressable; 2801 } 2802 2803 /** 2804 * Perform a file sort from a set of input files into an output file. 2805 * @param inFiles the files to be sorted 2806 * @param outFile the sorted output file 2807 * @param deleteInput should the input files be deleted as they are read? 2808 */ 2809 public void sort(Path[] inFiles, Path outFile, 2810 boolean deleteInput) throws IOException { 2811 if (fs.exists(outFile)) { 2812 throw new IOException("already exists: " + outFile); 2813 } 2814 2815 this.inFiles = inFiles; 2816 this.outFile = outFile; 2817 2818 int segments = sortPass(deleteInput); 2819 if (segments > 1) { 2820 mergePass(outFile.getParent()); 2821 } 2822 } 2823 2824 /** 2825 * Perform a file sort from a set of input files and return an iterator. 2826 * @param inFiles the files to be sorted 2827 * @param tempDir the directory where temp files are created during sort 2828 * @param deleteInput should the input files be deleted as they are read? 2829 * @return iterator the RawKeyValueIterator 2830 */ 2831 public RawKeyValueIterator sortAndIterate(Path[] inFiles, Path tempDir, 2832 boolean deleteInput) throws IOException { 2833 Path outFile = new Path(tempDir + Path.SEPARATOR + "all.2"); 2834 if (fs.exists(outFile)) { 2835 throw new IOException("already exists: " + outFile); 2836 } 2837 this.inFiles = inFiles; 2838 //outFile will basically be used as prefix for temp files in the cases 2839 //where sort outputs multiple sorted segments. For the single segment 2840 //case, the outputFile itself will contain the sorted data for that 2841 //segment 2842 this.outFile = outFile; 2843 2844 int segments = sortPass(deleteInput); 2845 if (segments > 1) 2846 return merge(outFile.suffix(".0"), outFile.suffix(".0.index"), 2847 tempDir); 2848 else if (segments == 1) 2849 return merge(new Path[]{outFile}, true, tempDir); 2850 else return null; 2851 } 2852 2853 /** 2854 * The backwards compatible interface to sort. 2855 * @param inFile the input file to sort 2856 * @param outFile the sorted output file 2857 */ 2858 public void sort(Path inFile, Path outFile) throws IOException { 2859 sort(new Path[]{inFile}, outFile, false); 2860 } 2861 2862 private int sortPass(boolean deleteInput) throws IOException { 2863 if(LOG.isDebugEnabled()) { 2864 LOG.debug("running sort pass"); 2865 } 2866 SortPass sortPass = new SortPass(); // make the SortPass 2867 sortPass.setProgressable(progressable); 2868 mergeSort = new MergeSort(sortPass.new SeqFileComparator()); 2869 try { 2870 return sortPass.run(deleteInput); // run it 2871 } finally { 2872 sortPass.close(); // close it 2873 } 2874 } 2875 2876 private class SortPass { 2877 private int memoryLimit = memory/4; 2878 private int recordLimit = 1000000; 2879 2880 private DataOutputBuffer rawKeys = new DataOutputBuffer(); 2881 private byte[] rawBuffer; 2882 2883 private int[] keyOffsets = new int[1024]; 2884 private int[] pointers = new int[keyOffsets.length]; 2885 private int[] pointersCopy = new int[keyOffsets.length]; 2886 private int[] keyLengths = new int[keyOffsets.length]; 2887 private ValueBytes[] rawValues = new ValueBytes[keyOffsets.length]; 2888 2889 private ArrayList segmentLengths = new ArrayList(); 2890 2891 private Reader in = null; 2892 private FSDataOutputStream out = null; 2893 private FSDataOutputStream indexOut = null; 2894 private Path outName; 2895 2896 private Progressable progressable = null; 2897 2898 public int run(boolean deleteInput) throws IOException { 2899 int segments = 0; 2900 int currentFile = 0; 2901 boolean atEof = (currentFile >= inFiles.length); 2902 CompressionType compressionType; 2903 CompressionCodec codec = null; 2904 segmentLengths.clear(); 2905 if (atEof) { 2906 return 0; 2907 } 2908 2909 // Initialize 2910 in = new Reader(fs, inFiles[currentFile], conf); 2911 compressionType = in.getCompressionType(); 2912 codec = in.getCompressionCodec(); 2913 2914 for (int i=0; i < rawValues.length; ++i) { 2915 rawValues[i] = null; 2916 } 2917 2918 while (!atEof) { 2919 int count = 0; 2920 int bytesProcessed = 0; 2921 rawKeys.reset(); 2922 while (!atEof && 2923 bytesProcessed < memoryLimit && count < recordLimit) { 2924 2925 // Read a record into buffer 2926 // Note: Attempt to re-use 'rawValue' as far as possible 2927 int keyOffset = rawKeys.getLength(); 2928 ValueBytes rawValue = 2929 (count == keyOffsets.length || rawValues[count] == null) ? 2930 in.createValueBytes() : 2931 rawValues[count]; 2932 int recordLength = in.nextRaw(rawKeys, rawValue); 2933 if (recordLength == -1) { 2934 in.close(); 2935 if (deleteInput) { 2936 fs.delete(inFiles[currentFile], true); 2937 } 2938 currentFile += 1; 2939 atEof = currentFile >= inFiles.length; 2940 if (!atEof) { 2941 in = new Reader(fs, inFiles[currentFile], conf); 2942 } else { 2943 in = null; 2944 } 2945 continue; 2946 } 2947 2948 int keyLength = rawKeys.getLength() - keyOffset; 2949 2950 if (count == keyOffsets.length) 2951 grow(); 2952 2953 keyOffsets[count] = keyOffset; // update pointers 2954 pointers[count] = count; 2955 keyLengths[count] = keyLength; 2956 rawValues[count] = rawValue; 2957 2958 bytesProcessed += recordLength; 2959 count++; 2960 } 2961 2962 // buffer is full -- sort & flush it 2963 if(LOG.isDebugEnabled()) { 2964 LOG.debug("flushing segment " + segments); 2965 } 2966 rawBuffer = rawKeys.getData(); 2967 sort(count); 2968 // indicate we're making progress 2969 if (progressable != null) { 2970 progressable.progress(); 2971 } 2972 flush(count, bytesProcessed, compressionType, codec, 2973 segments==0 && atEof); 2974 segments++; 2975 } 2976 return segments; 2977 } 2978 2979 public void close() throws IOException { 2980 if (in != null) { 2981 in.close(); 2982 } 2983 if (out != null) { 2984 out.close(); 2985 } 2986 if (indexOut != null) { 2987 indexOut.close(); 2988 } 2989 } 2990 2991 private void grow() { 2992 int newLength = keyOffsets.length * 3 / 2; 2993 keyOffsets = grow(keyOffsets, newLength); 2994 pointers = grow(pointers, newLength); 2995 pointersCopy = new int[newLength]; 2996 keyLengths = grow(keyLengths, newLength); 2997 rawValues = grow(rawValues, newLength); 2998 } 2999 3000 private int[] grow(int[] old, int newLength) { 3001 int[] result = new int[newLength]; 3002 System.arraycopy(old, 0, result, 0, old.length); 3003 return result; 3004 } 3005 3006 private ValueBytes[] grow(ValueBytes[] old, int newLength) { 3007 ValueBytes[] result = new ValueBytes[newLength]; 3008 System.arraycopy(old, 0, result, 0, old.length); 3009 for (int i=old.length; i < newLength; ++i) { 3010 result[i] = null; 3011 } 3012 return result; 3013 } 3014 3015 private void flush(int count, int bytesProcessed, 3016 CompressionType compressionType, 3017 CompressionCodec codec, 3018 boolean done) throws IOException { 3019 if (out == null) { 3020 outName = done ? outFile : outFile.suffix(".0"); 3021 out = fs.create(outName); 3022 if (!done) { 3023 indexOut = fs.create(outName.suffix(".index")); 3024 } 3025 } 3026 3027 long segmentStart = out.getPos(); 3028 Writer writer = createWriter(conf, Writer.stream(out), 3029 Writer.keyClass(keyClass), Writer.valueClass(valClass), 3030 Writer.compression(compressionType, codec), 3031 Writer.metadata(done ? metadata : new Metadata())); 3032 3033 if (!done) { 3034 writer.sync = null; // disable sync on temp files 3035 } 3036 3037 for (int i = 0; i < count; i++) { // write in sorted order 3038 int p = pointers[i]; 3039 writer.appendRaw(rawBuffer, keyOffsets[p], keyLengths[p], rawValues[p]); 3040 } 3041 writer.close(); 3042 3043 if (!done) { 3044 // Save the segment length 3045 WritableUtils.writeVLong(indexOut, segmentStart); 3046 WritableUtils.writeVLong(indexOut, (out.getPos()-segmentStart)); 3047 indexOut.flush(); 3048 } 3049 } 3050 3051 private void sort(int count) { 3052 System.arraycopy(pointers, 0, pointersCopy, 0, count); 3053 mergeSort.mergeSort(pointersCopy, pointers, 0, count); 3054 } 3055 class SeqFileComparator implements Comparator<IntWritable> { 3056 @Override 3057 public int compare(IntWritable I, IntWritable J) { 3058 return comparator.compare(rawBuffer, keyOffsets[I.get()], 3059 keyLengths[I.get()], rawBuffer, 3060 keyOffsets[J.get()], keyLengths[J.get()]); 3061 } 3062 } 3063 3064 /** set the progressable object in order to report progress */ 3065 public void setProgressable(Progressable progressable) 3066 { 3067 this.progressable = progressable; 3068 } 3069 3070 } // SequenceFile.Sorter.SortPass 3071 3072 /** The interface to iterate over raw keys/values of SequenceFiles. */ 3073 public static interface RawKeyValueIterator { 3074 /** Gets the current raw key 3075 * @return DataOutputBuffer 3076 * @throws IOException 3077 */ 3078 DataOutputBuffer getKey() throws IOException; 3079 /** Gets the current raw value 3080 * @return ValueBytes 3081 * @throws IOException 3082 */ 3083 ValueBytes getValue() throws IOException; 3084 /** Sets up the current key and value (for getKey and getValue) 3085 * @return true if there exists a key/value, false otherwise 3086 * @throws IOException 3087 */ 3088 boolean next() throws IOException; 3089 /** closes the iterator so that the underlying streams can be closed 3090 * @throws IOException 3091 */ 3092 void close() throws IOException; 3093 /** Gets the Progress object; this has a float (0.0 - 1.0) 3094 * indicating the bytes processed by the iterator so far 3095 */ 3096 Progress getProgress(); 3097 } 3098 3099 /** 3100 * Merges the list of segments of type <code>SegmentDescriptor</code> 3101 * @param segments the list of SegmentDescriptors 3102 * @param tmpDir the directory to write temporary files into 3103 * @return RawKeyValueIterator 3104 * @throws IOException 3105 */ 3106 public RawKeyValueIterator merge(List <SegmentDescriptor> segments, 3107 Path tmpDir) 3108 throws IOException { 3109 // pass in object to report progress, if present 3110 MergeQueue mQueue = new MergeQueue(segments, tmpDir, progressable); 3111 return mQueue.merge(); 3112 } 3113 3114 /** 3115 * Merges the contents of files passed in Path[] using a max factor value 3116 * that is already set 3117 * @param inNames the array of path names 3118 * @param deleteInputs true if the input files should be deleted when 3119 * unnecessary 3120 * @param tmpDir the directory to write temporary files into 3121 * @return RawKeyValueIteratorMergeQueue 3122 * @throws IOException 3123 */ 3124 public RawKeyValueIterator merge(Path [] inNames, boolean deleteInputs, 3125 Path tmpDir) 3126 throws IOException { 3127 return merge(inNames, deleteInputs, 3128 (inNames.length < factor) ? inNames.length : factor, 3129 tmpDir); 3130 } 3131 3132 /** 3133 * Merges the contents of files passed in Path[] 3134 * @param inNames the array of path names 3135 * @param deleteInputs true if the input files should be deleted when 3136 * unnecessary 3137 * @param factor the factor that will be used as the maximum merge fan-in 3138 * @param tmpDir the directory to write temporary files into 3139 * @return RawKeyValueIteratorMergeQueue 3140 * @throws IOException 3141 */ 3142 public RawKeyValueIterator merge(Path [] inNames, boolean deleteInputs, 3143 int factor, Path tmpDir) 3144 throws IOException { 3145 //get the segments from inNames 3146 ArrayList <SegmentDescriptor> a = new ArrayList <SegmentDescriptor>(); 3147 for (int i = 0; i < inNames.length; i++) { 3148 SegmentDescriptor s = new SegmentDescriptor(0, 3149 fs.getFileStatus(inNames[i]).getLen(), inNames[i]); 3150 s.preserveInput(!deleteInputs); 3151 s.doSync(); 3152 a.add(s); 3153 } 3154 this.factor = factor; 3155 MergeQueue mQueue = new MergeQueue(a, tmpDir, progressable); 3156 return mQueue.merge(); 3157 } 3158 3159 /** 3160 * Merges the contents of files passed in Path[] 3161 * @param inNames the array of path names 3162 * @param tempDir the directory for creating temp files during merge 3163 * @param deleteInputs true if the input files should be deleted when 3164 * unnecessary 3165 * @return RawKeyValueIteratorMergeQueue 3166 * @throws IOException 3167 */ 3168 public RawKeyValueIterator merge(Path [] inNames, Path tempDir, 3169 boolean deleteInputs) 3170 throws IOException { 3171 //outFile will basically be used as prefix for temp files for the 3172 //intermediate merge outputs 3173 this.outFile = new Path(tempDir + Path.SEPARATOR + "merged"); 3174 //get the segments from inNames 3175 ArrayList <SegmentDescriptor> a = new ArrayList <SegmentDescriptor>(); 3176 for (int i = 0; i < inNames.length; i++) { 3177 SegmentDescriptor s = new SegmentDescriptor(0, 3178 fs.getFileStatus(inNames[i]).getLen(), inNames[i]); 3179 s.preserveInput(!deleteInputs); 3180 s.doSync(); 3181 a.add(s); 3182 } 3183 factor = (inNames.length < factor) ? inNames.length : factor; 3184 // pass in object to report progress, if present 3185 MergeQueue mQueue = new MergeQueue(a, tempDir, progressable); 3186 return mQueue.merge(); 3187 } 3188 3189 /** 3190 * Clones the attributes (like compression of the input file and creates a 3191 * corresponding Writer 3192 * @param inputFile the path of the input file whose attributes should be 3193 * cloned 3194 * @param outputFile the path of the output file 3195 * @param prog the Progressable to report status during the file write 3196 * @return Writer 3197 * @throws IOException 3198 */ 3199 public Writer cloneFileAttributes(Path inputFile, Path outputFile, 3200 Progressable prog) throws IOException { 3201 Reader reader = new Reader(conf, 3202 Reader.file(inputFile), 3203 new Reader.OnlyHeaderOption()); 3204 CompressionType compress = reader.getCompressionType(); 3205 CompressionCodec codec = reader.getCompressionCodec(); 3206 reader.close(); 3207 3208 Writer writer = createWriter(conf, 3209 Writer.file(outputFile), 3210 Writer.keyClass(keyClass), 3211 Writer.valueClass(valClass), 3212 Writer.compression(compress, codec), 3213 Writer.progressable(prog)); 3214 return writer; 3215 } 3216 3217 /** 3218 * Writes records from RawKeyValueIterator into a file represented by the 3219 * passed writer 3220 * @param records the RawKeyValueIterator 3221 * @param writer the Writer created earlier 3222 * @throws IOException 3223 */ 3224 public void writeFile(RawKeyValueIterator records, Writer writer) 3225 throws IOException { 3226 while(records.next()) { 3227 writer.appendRaw(records.getKey().getData(), 0, 3228 records.getKey().getLength(), records.getValue()); 3229 } 3230 writer.sync(); 3231 } 3232 3233 /** Merge the provided files. 3234 * @param inFiles the array of input path names 3235 * @param outFile the final output file 3236 * @throws IOException 3237 */ 3238 public void merge(Path[] inFiles, Path outFile) throws IOException { 3239 if (fs.exists(outFile)) { 3240 throw new IOException("already exists: " + outFile); 3241 } 3242 RawKeyValueIterator r = merge(inFiles, false, outFile.getParent()); 3243 Writer writer = cloneFileAttributes(inFiles[0], outFile, null); 3244 3245 writeFile(r, writer); 3246 3247 writer.close(); 3248 } 3249 3250 /** sort calls this to generate the final merged output */ 3251 private int mergePass(Path tmpDir) throws IOException { 3252 if(LOG.isDebugEnabled()) { 3253 LOG.debug("running merge pass"); 3254 } 3255 Writer writer = cloneFileAttributes( 3256 outFile.suffix(".0"), outFile, null); 3257 RawKeyValueIterator r = merge(outFile.suffix(".0"), 3258 outFile.suffix(".0.index"), tmpDir); 3259 writeFile(r, writer); 3260 3261 writer.close(); 3262 return 0; 3263 } 3264 3265 /** Used by mergePass to merge the output of the sort 3266 * @param inName the name of the input file containing sorted segments 3267 * @param indexIn the offsets of the sorted segments 3268 * @param tmpDir the relative directory to store intermediate results in 3269 * @return RawKeyValueIterator 3270 * @throws IOException 3271 */ 3272 private RawKeyValueIterator merge(Path inName, Path indexIn, Path tmpDir) 3273 throws IOException { 3274 //get the segments from indexIn 3275 //we create a SegmentContainer so that we can track segments belonging to 3276 //inName and delete inName as soon as we see that we have looked at all 3277 //the contained segments during the merge process & hence don't need 3278 //them anymore 3279 SegmentContainer container = new SegmentContainer(inName, indexIn); 3280 MergeQueue mQueue = new MergeQueue(container.getSegmentList(), tmpDir, progressable); 3281 return mQueue.merge(); 3282 } 3283 3284 /** This class implements the core of the merge logic */ 3285 private class MergeQueue extends PriorityQueue 3286 implements RawKeyValueIterator { 3287 private boolean compress; 3288 private boolean blockCompress; 3289 private DataOutputBuffer rawKey = new DataOutputBuffer(); 3290 private ValueBytes rawValue; 3291 private long totalBytesProcessed; 3292 private float progPerByte; 3293 private Progress mergeProgress = new Progress(); 3294 private Path tmpDir; 3295 private Progressable progress = null; //handle to the progress reporting object 3296 private SegmentDescriptor minSegment; 3297 3298 //a TreeMap used to store the segments sorted by size (segment offset and 3299 //segment path name is used to break ties between segments of same sizes) 3300 private Map<SegmentDescriptor, Void> sortedSegmentSizes = 3301 new TreeMap<SegmentDescriptor, Void>(); 3302 3303 @SuppressWarnings("unchecked") 3304 public void put(SegmentDescriptor stream) throws IOException { 3305 if (size() == 0) { 3306 compress = stream.in.isCompressed(); 3307 blockCompress = stream.in.isBlockCompressed(); 3308 } else if (compress != stream.in.isCompressed() || 3309 blockCompress != stream.in.isBlockCompressed()) { 3310 throw new IOException("All merged files must be compressed or not."); 3311 } 3312 super.put(stream); 3313 } 3314 3315 /** 3316 * A queue of file segments to merge 3317 * @param segments the file segments to merge 3318 * @param tmpDir a relative local directory to save intermediate files in 3319 * @param progress the reference to the Progressable object 3320 */ 3321 public MergeQueue(List <SegmentDescriptor> segments, 3322 Path tmpDir, Progressable progress) { 3323 int size = segments.size(); 3324 for (int i = 0; i < size; i++) { 3325 sortedSegmentSizes.put(segments.get(i), null); 3326 } 3327 this.tmpDir = tmpDir; 3328 this.progress = progress; 3329 } 3330 @Override 3331 protected boolean lessThan(Object a, Object b) { 3332 // indicate we're making progress 3333 if (progress != null) { 3334 progress.progress(); 3335 } 3336 SegmentDescriptor msa = (SegmentDescriptor)a; 3337 SegmentDescriptor msb = (SegmentDescriptor)b; 3338 return comparator.compare(msa.getKey().getData(), 0, 3339 msa.getKey().getLength(), msb.getKey().getData(), 0, 3340 msb.getKey().getLength()) < 0; 3341 } 3342 @Override 3343 public void close() throws IOException { 3344 SegmentDescriptor ms; // close inputs 3345 while ((ms = (SegmentDescriptor)pop()) != null) { 3346 ms.cleanup(); 3347 } 3348 minSegment = null; 3349 } 3350 @Override 3351 public DataOutputBuffer getKey() throws IOException { 3352 return rawKey; 3353 } 3354 @Override 3355 public ValueBytes getValue() throws IOException { 3356 return rawValue; 3357 } 3358 @Override 3359 public boolean next() throws IOException { 3360 if (size() == 0) 3361 return false; 3362 if (minSegment != null) { 3363 //minSegment is non-null for all invocations of next except the first 3364 //one. For the first invocation, the priority queue is ready for use 3365 //but for the subsequent invocations, first adjust the queue 3366 adjustPriorityQueue(minSegment); 3367 if (size() == 0) { 3368 minSegment = null; 3369 return false; 3370 } 3371 } 3372 minSegment = (SegmentDescriptor)top(); 3373 long startPos = minSegment.in.getPosition(); // Current position in stream 3374 //save the raw key reference 3375 rawKey = minSegment.getKey(); 3376 //load the raw value. Re-use the existing rawValue buffer 3377 if (rawValue == null) { 3378 rawValue = minSegment.in.createValueBytes(); 3379 } 3380 minSegment.nextRawValue(rawValue); 3381 long endPos = minSegment.in.getPosition(); // End position after reading value 3382 updateProgress(endPos - startPos); 3383 return true; 3384 } 3385 3386 @Override 3387 public Progress getProgress() { 3388 return mergeProgress; 3389 } 3390 3391 private void adjustPriorityQueue(SegmentDescriptor ms) throws IOException{ 3392 long startPos = ms.in.getPosition(); // Current position in stream 3393 boolean hasNext = ms.nextRawKey(); 3394 long endPos = ms.in.getPosition(); // End position after reading key 3395 updateProgress(endPos - startPos); 3396 if (hasNext) { 3397 adjustTop(); 3398 } else { 3399 pop(); 3400 ms.cleanup(); 3401 } 3402 } 3403 3404 private void updateProgress(long bytesProcessed) { 3405 totalBytesProcessed += bytesProcessed; 3406 if (progPerByte > 0) { 3407 mergeProgress.set(totalBytesProcessed * progPerByte); 3408 } 3409 } 3410 3411 /** This is the single level merge that is called multiple times 3412 * depending on the factor size and the number of segments 3413 * @return RawKeyValueIterator 3414 * @throws IOException 3415 */ 3416 public RawKeyValueIterator merge() throws IOException { 3417 //create the MergeStreams from the sorted map created in the constructor 3418 //and dump the final output to a file 3419 int numSegments = sortedSegmentSizes.size(); 3420 int origFactor = factor; 3421 int passNo = 1; 3422 LocalDirAllocator lDirAlloc = new LocalDirAllocator("io.seqfile.local.dir"); 3423 do { 3424 //get the factor for this pass of merge 3425 factor = getPassFactor(passNo, numSegments); 3426 List<SegmentDescriptor> segmentsToMerge = 3427 new ArrayList<SegmentDescriptor>(); 3428 int segmentsConsidered = 0; 3429 int numSegmentsToConsider = factor; 3430 while (true) { 3431 //extract the smallest 'factor' number of segment pointers from the 3432 //TreeMap. Call cleanup on the empty segments (no key/value data) 3433 SegmentDescriptor[] mStream = 3434 getSegmentDescriptors(numSegmentsToConsider); 3435 for (int i = 0; i < mStream.length; i++) { 3436 if (mStream[i].nextRawKey()) { 3437 segmentsToMerge.add(mStream[i]); 3438 segmentsConsidered++; 3439 // Count the fact that we read some bytes in calling nextRawKey() 3440 updateProgress(mStream[i].in.getPosition()); 3441 } 3442 else { 3443 mStream[i].cleanup(); 3444 numSegments--; //we ignore this segment for the merge 3445 } 3446 } 3447 //if we have the desired number of segments 3448 //or looked at all available segments, we break 3449 if (segmentsConsidered == factor || 3450 sortedSegmentSizes.size() == 0) { 3451 break; 3452 } 3453 3454 numSegmentsToConsider = factor - segmentsConsidered; 3455 } 3456 //feed the streams to the priority queue 3457 initialize(segmentsToMerge.size()); clear(); 3458 for (int i = 0; i < segmentsToMerge.size(); i++) { 3459 put(segmentsToMerge.get(i)); 3460 } 3461 //if we have lesser number of segments remaining, then just return the 3462 //iterator, else do another single level merge 3463 if (numSegments <= factor) { 3464 //calculate the length of the remaining segments. Required for 3465 //calculating the merge progress 3466 long totalBytes = 0; 3467 for (int i = 0; i < segmentsToMerge.size(); i++) { 3468 totalBytes += segmentsToMerge.get(i).segmentLength; 3469 } 3470 if (totalBytes != 0) //being paranoid 3471 progPerByte = 1.0f / (float)totalBytes; 3472 //reset factor to what it originally was 3473 factor = origFactor; 3474 return this; 3475 } else { 3476 //we want to spread the creation of temp files on multiple disks if 3477 //available under the space constraints 3478 long approxOutputSize = 0; 3479 for (SegmentDescriptor s : segmentsToMerge) { 3480 approxOutputSize += s.segmentLength + 3481 ChecksumFileSystem.getApproxChkSumLength( 3482 s.segmentLength); 3483 } 3484 Path tmpFilename = 3485 new Path(tmpDir, "intermediate").suffix("." + passNo); 3486 3487 Path outputFile = lDirAlloc.getLocalPathForWrite( 3488 tmpFilename.toString(), 3489 approxOutputSize, conf); 3490 if(LOG.isDebugEnabled()) { 3491 LOG.debug("writing intermediate results to " + outputFile); 3492 } 3493 Writer writer = cloneFileAttributes( 3494 fs.makeQualified(segmentsToMerge.get(0).segmentPathName), 3495 fs.makeQualified(outputFile), null); 3496 writer.sync = null; //disable sync for temp files 3497 writeFile(this, writer); 3498 writer.close(); 3499 3500 //we finished one single level merge; now clean up the priority 3501 //queue 3502 this.close(); 3503 3504 SegmentDescriptor tempSegment = 3505 new SegmentDescriptor(0, 3506 fs.getFileStatus(outputFile).getLen(), outputFile); 3507 //put the segment back in the TreeMap 3508 sortedSegmentSizes.put(tempSegment, null); 3509 numSegments = sortedSegmentSizes.size(); 3510 passNo++; 3511 } 3512 //we are worried about only the first pass merge factor. So reset the 3513 //factor to what it originally was 3514 factor = origFactor; 3515 } while(true); 3516 } 3517 3518 //Hadoop-591 3519 public int getPassFactor(int passNo, int numSegments) { 3520 if (passNo > 1 || numSegments <= factor || factor == 1) 3521 return factor; 3522 int mod = (numSegments - 1) % (factor - 1); 3523 if (mod == 0) 3524 return factor; 3525 return mod + 1; 3526 } 3527 3528 /** Return (& remove) the requested number of segment descriptors from the 3529 * sorted map. 3530 */ 3531 public SegmentDescriptor[] getSegmentDescriptors(int numDescriptors) { 3532 if (numDescriptors > sortedSegmentSizes.size()) 3533 numDescriptors = sortedSegmentSizes.size(); 3534 SegmentDescriptor[] SegmentDescriptors = 3535 new SegmentDescriptor[numDescriptors]; 3536 Iterator iter = sortedSegmentSizes.keySet().iterator(); 3537 int i = 0; 3538 while (i < numDescriptors) { 3539 SegmentDescriptors[i++] = (SegmentDescriptor)iter.next(); 3540 iter.remove(); 3541 } 3542 return SegmentDescriptors; 3543 } 3544 } // SequenceFile.Sorter.MergeQueue 3545 3546 /** This class defines a merge segment. This class can be subclassed to 3547 * provide a customized cleanup method implementation. In this 3548 * implementation, cleanup closes the file handle and deletes the file 3549 */ 3550 public class SegmentDescriptor implements Comparable { 3551 3552 long segmentOffset; //the start of the segment in the file 3553 long segmentLength; //the length of the segment 3554 Path segmentPathName; //the path name of the file containing the segment 3555 boolean ignoreSync = true; //set to true for temp files 3556 private Reader in = null; 3557 private DataOutputBuffer rawKey = null; //this will hold the current key 3558 private boolean preserveInput = false; //delete input segment files? 3559 3560 /** Constructs a segment 3561 * @param segmentOffset the offset of the segment in the file 3562 * @param segmentLength the length of the segment 3563 * @param segmentPathName the path name of the file containing the segment 3564 */ 3565 public SegmentDescriptor (long segmentOffset, long segmentLength, 3566 Path segmentPathName) { 3567 this.segmentOffset = segmentOffset; 3568 this.segmentLength = segmentLength; 3569 this.segmentPathName = segmentPathName; 3570 } 3571 3572 /** Do the sync checks */ 3573 public void doSync() {ignoreSync = false;} 3574 3575 /** Whether to delete the files when no longer needed */ 3576 public void preserveInput(boolean preserve) { 3577 preserveInput = preserve; 3578 } 3579 3580 public boolean shouldPreserveInput() { 3581 return preserveInput; 3582 } 3583 3584 @Override 3585 public int compareTo(Object o) { 3586 SegmentDescriptor that = (SegmentDescriptor)o; 3587 if (this.segmentLength != that.segmentLength) { 3588 return (this.segmentLength < that.segmentLength ? -1 : 1); 3589 } 3590 if (this.segmentOffset != that.segmentOffset) { 3591 return (this.segmentOffset < that.segmentOffset ? -1 : 1); 3592 } 3593 return (this.segmentPathName.toString()). 3594 compareTo(that.segmentPathName.toString()); 3595 } 3596 3597 @Override 3598 public boolean equals(Object o) { 3599 if (!(o instanceof SegmentDescriptor)) { 3600 return false; 3601 } 3602 SegmentDescriptor that = (SegmentDescriptor)o; 3603 if (this.segmentLength == that.segmentLength && 3604 this.segmentOffset == that.segmentOffset && 3605 this.segmentPathName.toString().equals( 3606 that.segmentPathName.toString())) { 3607 return true; 3608 } 3609 return false; 3610 } 3611 3612 @Override 3613 public int hashCode() { 3614 return 37 * 17 + (int) (segmentOffset^(segmentOffset>>>32)); 3615 } 3616 3617 /** Fills up the rawKey object with the key returned by the Reader 3618 * @return true if there is a key returned; false, otherwise 3619 * @throws IOException 3620 */ 3621 public boolean nextRawKey() throws IOException { 3622 if (in == null) { 3623 int bufferSize = getBufferSize(conf); 3624 Reader reader = new Reader(conf, 3625 Reader.file(segmentPathName), 3626 Reader.bufferSize(bufferSize), 3627 Reader.start(segmentOffset), 3628 Reader.length(segmentLength)); 3629 3630 //sometimes we ignore syncs especially for temp merge files 3631 if (ignoreSync) reader.ignoreSync(); 3632 3633 if (reader.getKeyClass() != keyClass) 3634 throw new IOException("wrong key class: " + reader.getKeyClass() + 3635 " is not " + keyClass); 3636 if (reader.getValueClass() != valClass) 3637 throw new IOException("wrong value class: "+reader.getValueClass()+ 3638 " is not " + valClass); 3639 this.in = reader; 3640 rawKey = new DataOutputBuffer(); 3641 } 3642 rawKey.reset(); 3643 int keyLength = 3644 in.nextRawKey(rawKey); 3645 return (keyLength >= 0); 3646 } 3647 3648 /** Fills up the passed rawValue with the value corresponding to the key 3649 * read earlier 3650 * @param rawValue 3651 * @return the length of the value 3652 * @throws IOException 3653 */ 3654 public int nextRawValue(ValueBytes rawValue) throws IOException { 3655 int valLength = in.nextRawValue(rawValue); 3656 return valLength; 3657 } 3658 3659 /** Returns the stored rawKey */ 3660 public DataOutputBuffer getKey() { 3661 return rawKey; 3662 } 3663 3664 /** closes the underlying reader */ 3665 private void close() throws IOException { 3666 this.in.close(); 3667 this.in = null; 3668 } 3669 3670 /** The default cleanup. Subclasses can override this with a custom 3671 * cleanup 3672 */ 3673 public void cleanup() throws IOException { 3674 close(); 3675 if (!preserveInput) { 3676 fs.delete(segmentPathName, true); 3677 } 3678 } 3679 } // SequenceFile.Sorter.SegmentDescriptor 3680 3681 /** This class provisions multiple segments contained within a single 3682 * file 3683 */ 3684 private class LinkedSegmentsDescriptor extends SegmentDescriptor { 3685 3686 SegmentContainer parentContainer = null; 3687 3688 /** Constructs a segment 3689 * @param segmentOffset the offset of the segment in the file 3690 * @param segmentLength the length of the segment 3691 * @param segmentPathName the path name of the file containing the segment 3692 * @param parent the parent SegmentContainer that holds the segment 3693 */ 3694 public LinkedSegmentsDescriptor (long segmentOffset, long segmentLength, 3695 Path segmentPathName, SegmentContainer parent) { 3696 super(segmentOffset, segmentLength, segmentPathName); 3697 this.parentContainer = parent; 3698 } 3699 /** The default cleanup. Subclasses can override this with a custom 3700 * cleanup 3701 */ 3702 @Override 3703 public void cleanup() throws IOException { 3704 super.close(); 3705 if (super.shouldPreserveInput()) return; 3706 parentContainer.cleanup(); 3707 } 3708 3709 @Override 3710 public boolean equals(Object o) { 3711 if (!(o instanceof LinkedSegmentsDescriptor)) { 3712 return false; 3713 } 3714 return super.equals(o); 3715 } 3716 } //SequenceFile.Sorter.LinkedSegmentsDescriptor 3717 3718 /** The class that defines a container for segments to be merged. Primarily 3719 * required to delete temp files as soon as all the contained segments 3720 * have been looked at */ 3721 private class SegmentContainer { 3722 private int numSegmentsCleanedUp = 0; //track the no. of segment cleanups 3723 private int numSegmentsContained; //# of segments contained 3724 private Path inName; //input file from where segments are created 3725 3726 //the list of segments read from the file 3727 private ArrayList <SegmentDescriptor> segments = 3728 new ArrayList <SegmentDescriptor>(); 3729 /** This constructor is there primarily to serve the sort routine that 3730 * generates a single output file with an associated index file */ 3731 public SegmentContainer(Path inName, Path indexIn) throws IOException { 3732 //get the segments from indexIn 3733 FSDataInputStream fsIndexIn = fs.open(indexIn); 3734 long end = fs.getFileStatus(indexIn).getLen(); 3735 while (fsIndexIn.getPos() < end) { 3736 long segmentOffset = WritableUtils.readVLong(fsIndexIn); 3737 long segmentLength = WritableUtils.readVLong(fsIndexIn); 3738 Path segmentName = inName; 3739 segments.add(new LinkedSegmentsDescriptor(segmentOffset, 3740 segmentLength, segmentName, this)); 3741 } 3742 fsIndexIn.close(); 3743 fs.delete(indexIn, true); 3744 numSegmentsContained = segments.size(); 3745 this.inName = inName; 3746 } 3747 3748 public List <SegmentDescriptor> getSegmentList() { 3749 return segments; 3750 } 3751 public void cleanup() throws IOException { 3752 numSegmentsCleanedUp++; 3753 if (numSegmentsCleanedUp == numSegmentsContained) { 3754 fs.delete(inName, true); 3755 } 3756 } 3757 } //SequenceFile.Sorter.SegmentContainer 3758 3759 } // SequenceFile.Sorter 3760 3761} // SequenceFile