diff --git a/src/read.rs b/src/read.rs
index c4ee2f1a..7b38daba 100644
--- a/src/read.rs
+++ b/src/read.rs
@@ -209,7 +209,7 @@ fn make_reader<'a>(
 impl<R: Read + io::Seek> ZipArchive<R> {
     /// Get the directory start offset and number of files. This is done in a
     /// separate function to ease the control flow design.
-    fn get_directory_counts(
+    pub(crate) fn get_directory_counts(
         reader: &mut R,
         footer: &spec::CentralDirectoryEnd,
         cde_start_pos: u64,
@@ -514,7 +514,8 @@ fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T> {
     Err(ZipError::UnsupportedArchive(detail))
 }
 
-fn central_header_to_zip_file<R: Read + io::Seek>(
+/// Parse a central directory entry to collect the information for the file.
+pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
     reader: &mut R,
     archive_offset: u64,
 ) -> ZipResult<ZipFileData> {
diff --git a/src/write.rs b/src/write.rs
index bc688172..ca2e3fb1 100644
--- a/src/write.rs
+++ b/src/write.rs
@@ -1,7 +1,7 @@
 //! Types for creating ZIP archives
 
 use crate::compression::CompressionMethod;
-use crate::read::ZipFile;
+use crate::read::{central_header_to_zip_file, ZipArchive, ZipFile};
 use crate::result::{ZipError, ZipResult};
 use crate::spec;
 use crate::types::{DateTime, System, ZipFileData, DEFAULT_VERSION};
@@ -68,7 +68,7 @@ pub struct ZipWriter<W: Write + io::Seek> {
     files: Vec<ZipFileData>,
     stats: ZipWriterStats,
     writing_to_file: bool,
-    comment: String,
+    comment: Vec<u8>,
     writing_raw: bool,
 }
 
@@ -194,6 +194,43 @@ impl ZipWriterStats {
     }
 }
 
+impl<A: Read + Write + io::Seek> ZipWriter<A> {
+    /// Initializes the archive from an existing ZIP archive, making it ready for append.
+    pub fn new_append(mut readwriter: A) -> ZipResult<ZipWriter<A>> {
+        let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut readwriter)?;
+
+        if footer.disk_number != footer.disk_with_central_directory {
+            return Err(ZipError::UnsupportedArchive(
+                "Support for multi-disk files is not implemented",
+            ));
+        }
+
+        let (archive_offset, directory_start, number_of_files) =
+            ZipArchive::get_directory_counts(&mut readwriter, &footer, cde_start_pos)?;
+
+        if let Err(_) = readwriter.seek(io::SeekFrom::Start(directory_start)) {
+            return Err(ZipError::InvalidArchive(
+                "Could not seek to start of central directory",
+            ));
+        }
+
+        let files = (0..number_of_files)
+            .map(|_| central_header_to_zip_file(&mut readwriter, archive_offset))
+            .collect::<Result<Vec<_>, _>>()?;
+
+        let _ = readwriter.seek(io::SeekFrom::Start(directory_start)); // seek directory_start to overwrite it
+
+        Ok(ZipWriter {
+            inner: GenericZipWriter::Storer(readwriter),
+            files,
+            stats: Default::default(),
+            writing_to_file: false,
+            comment: footer.zip_file_comment,
+            writing_raw: true, // avoid recomputing the last file's header
+        })
+    }
+}
+
 impl<W: Write + io::Seek> ZipWriter<W> {
     /// Initializes the archive.
     ///
@@ -204,7 +241,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
             files: Vec::new(),
             stats: Default::default(),
             writing_to_file: false,
-            comment: String::new(),
+            comment: Vec::new(),
             writing_raw: false,
         }
     }
@@ -214,7 +251,15 @@ impl<W: Write + io::Seek> ZipWriter<W> {
     where
         S: Into<String>,
     {
-        self.comment = comment.into();
+        self.set_raw_comment(comment.into().into())
+    }
+
+    /// Set ZIP archive comment.
+    ///
+    /// This sets the raw bytes of the comment. The comment
+    /// is typically expected to be encoded in UTF-8
+    pub fn set_raw_comment(&mut self, comment: Vec<u8>) {
+        self.comment = comment;
     }
 
     /// Start a new file for with the requested options.
@@ -485,7 +530,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
                 number_of_files: self.files.len() as u16,
                 central_directory_size: central_size as u32,
                 central_directory_offset: central_start as u32,
-                zip_file_comment: self.comment.as_bytes().to_vec(),
+                zip_file_comment: self.comment.clone(),
             };
 
             footer.write(writer)?;
diff --git a/tests/end_to_end.rs b/tests/end_to_end.rs
index b826f548..c658a6f5 100644
--- a/tests/end_to_end.rs
+++ b/tests/end_to_end.rs
@@ -46,6 +46,25 @@ fn copy() {
     check_zip_file_contents(&mut tgt_archive, COPY_ENTRY_NAME);
 }
 
+// This test asserts that after appending to a `ZipWriter`, then reading its contents back out,
+// both the prior data and the appended data will be exactly the same as their originals.
+#[test]
+fn append() {
+    let mut file = &mut Cursor::new(Vec::new());
+    write_to_zip(file).expect("file written");
+
+    {
+        let mut zip = zip::ZipWriter::new_append(&mut file).unwrap();
+        zip.start_file(COPY_ENTRY_NAME, Default::default()).unwrap();
+        zip.write_all(LOREM_IPSUM).unwrap();
+        zip.finish().unwrap();
+    }
+
+    let mut zip = zip::ZipArchive::new(&mut file).unwrap();
+    check_zip_file_contents(&mut zip, ENTRY_NAME);
+    check_zip_file_contents(&mut zip, COPY_ENTRY_NAME);
+}
+
 fn write_to_zip(file: &mut Cursor<Vec<u8>>) -> zip::result::ZipResult<()> {
     let mut zip = zip::ZipWriter::new(file);