With std::filesystem::resize_file in C++, it is possible to change the size of a file without opening the file.
Is there any similar function in C#, which allows changing the size of a file without opening it?
I think opening a file as a FileStream and saving it again with a new size will be slower.
Using FileStream.SetLength() will be about as fast as you can make it.
It ends up calling the Windows API to set the length of the file, the same as the std::filesystem::resize_file().
So you just need to do something like this, and it will be fast enough:
using (var file = File.Open(myFilePath, FileMode.Open))
{
file.SetLength(myRequiredFileSize);
}
The implementation of FileStream.SetLength() is:
private void SetLengthCore(long value)
{
Contract.Assert(value >= 0, "value >= 0");
long origPos = _pos;
if (_exposedHandle)
VerifyOSHandlePosition();
if (_pos != value)
SeekCore(value, SeekOrigin.Begin);
if (!Win32Native.SetEndOfFile(_handle)) {
int hr = Marshal.GetLastWin32Error();
if (hr==__Error.ERROR_INVALID_PARAMETER)
throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_FileLengthTooBig"));
__Error.WinIOError(hr, String.Empty);
}
// Return file pointer to where it was before setting length
if (origPos != value) {
if (origPos < value)
SeekCore(origPos, SeekOrigin.Begin);
else
SeekCore(0, SeekOrigin.End);
}
}
(Note that SeekCore() just calls the the Windows API SetFilePointer() function.)
Doing this does NOT read the file into memory.
Also, the Windows API function SetEndOfFile() does not write to the extended region, so it is fast. The documentation states If the file is extended, the contents of the file between the old end of the file and the new end of the file are not defined. - this is as a result of data not being written to the extended region.
As test, I tried the following code:
using System;
using System.Diagnostics;
using System.IO;
namespace Demo
{
public class Program
{
public static void Main()
{
string filename = #"e:\tmp\test.bin";
File.WriteAllBytes(filename, new byte[0]); // Create empty file.
var sw = Stopwatch.StartNew();
using (var file = File.Open(filename, FileMode.Open))
{
file.SetLength(1024*1024*1024);
}
Console.WriteLine(sw.Elapsed);
}
}
}
My E:\ drive is a hard drive, not an SSD.
The output was: 00:00:00.0003574
So it took less than a hundreth of a second to extend the file to 1GB in size.
Related
Code:
public void mergeFiles(string dir)
{
for (int i = 0; i < parts; i++)
{
if (!File.Exists(dir))
{
File.Create(dir).Close();
}
var output = File.Open(dir, FileMode.Open);
var input = File.Open(dir + ".part" + (i + 1), FileMode.Open);
input.CopyTo(output);
output.Close();
input.Close();
File.Delete(dir + ".part" + (i + 1));
}
}
dir variable is for example /path/file.txt.gz
I have a file packed into a .gz archive. This archive is divided into e.g. 8 parts and I want to get this file.
The problem is that I don't know how to combine these files "file.gz.part1..." to extract them later.
When I use the above function, the archive is corrupted.
I have been struggling with it for a week, looking on the Internet, but this is the best solution I have found and it does not work.
Anyone have any advice on how to combine archive parts into one file?
Your code has a few problems. If you look at the documentation for System.IO.Stream.Close you will see the following remark (emphasis mine):
Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. Instead of calling this method, ensure that the stream is properly disposed.
So, per the docs, you want to dispose your streams rather than calling close directly (I'll come back to that in a second). Ignoring that, your main problem lies here:
var output = File.Open(dir, FileMode.Open);
You're using FileMode.Open for your output file. Again from the docs:
Specifies that the operating system should open an existing file. The ability to open the file is dependent on the value specified by the FileAccess enumeration. A FileNotFoundException exception is thrown if the file does not exist.
That's opening a stream at the beginning of the file. So, you're writing each partial file over the beginning of your output file repeatedly. I'm sure you noticed that your combined file size was only as large as the largest partial file. Take a look at FileMode.Append on the other hand:
Opens the file if it exists and seeks to the end of the file, or creates a new file. This requires Append permission. FileMode.Append can be used only in conjunction with FileAccess.Write. Trying to seek to a position before the end of the file throws an IOException exception, and any attempt to read fails and throws a NotSupportedException exception.
OK - but backing up even a step further, this:
if (!File.Exists(dir))
{
File.Create(dir).Close();
}
var output = File.Open(dir, FileMode.Open);
... is ineffecient. Why would we check for the file existing n number of times, then open/close it n number of times? We can just create the file as the first step, and leave that output stream open until we have appended all of our data to it.
So, how would we refactor your code to use IDisposable while fixing your bug? Check out the using statement. Putting all of this together, your code might look like this:
public void mergeFiles(string dir)
{
using (FileStream combinedFile = File.Create(dir))
{
for (int i = 0; i < parts; i++)
{
// Since this string is referenced more than once, capture as a
// variable to lower risk of copy/paste errors.
var splitFileName = dir + ".part" + (i + 1);
using (FileStream filePart = File.Open(splitFileName, FileMode.Open))
{
filePart.CopyTo(combinedFile);
}
// Note that it's safe to delete the file now, because our filePart
// stream has been disposed as it is out of scope.
File.Delete(splitFileName);
}
}
}
Give that a try. And here's an entire working program with a contrived example that you can past into a new console app and run:
using System.IO;
using System.Text;
namespace temp_test
{
class Program
{
static int parts = 10;
static void Main(string[] args)
{
// First we will generate some dummy files.
generateFiles();
// Next, open files and combine.
combineFiles();
}
/// <summary>
/// A contived example to generate some files.
/// </summary>
static void generateFiles()
{
for (int i = 0; i < parts; i++)
{
using (FileStream newFile = File.Create("splitfile.part" + i))
{
byte[] info = new UTF8Encoding(true).GetBytes($"This is File # ${i.ToString()}");
newFile.Write(info);
}
}
}
/// <summary>
/// A contived example to combine our files.
/// </summary>
static void combineFiles()
{
using (FileStream combinedFile = File.Create("combined"))
{
for (int i = 0; i < parts; i++)
{
var splitFileName = "splitfile.part" + i;
using (FileStream filePart = File.Open(splitFileName, FileMode.Open))
{
filePart.CopyTo(combinedFile);
}
// Note that it's safe to delete the file now, because our filePart
// stream has been disposed as it is out of scope.
File.Delete(splitFileName);
}
}
}
}
}
Good luck and welcome to StackOverflow!
So the question is pretty simple. I'm using Xamarin.Android and I have a zip file in the Assets folder named "MyZipFile.zip", which I want extracted to the following path: System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
It sounds simple enough, but I cannot figure out how to read the Asset into memory through the AssetManager and then unzip it at the targeted location.
Is there a simple way to do this?
The Android Java framework includes a Java.Util.Zip package, so without adding any additional app libraries, I directly use it instead of using C# framework code, thus no bloat that linking can not remove.
So basically you are creating an asset stream and feeding that to a ZipInputStream and iterating over each ZipEntry in that zip stream to either create directories or files to your destination path.
UnZipAssets
public void UnZipAssets(string assetName, string destPath)
{
byte[] buffer = new byte[1024];
int byteCount;
var destPathDir = new Java.IO.File(destPath);
destPathDir.Mkdirs();
using (var assetStream = Assets.Open(assetName, Android.Content.Res.Access.Streaming))
using (var zipStream = new ZipInputStream(assetStream))
{
ZipEntry zipEntry;
while ((zipEntry = zipStream.NextEntry) != null)
{
if (zipEntry.IsDirectory)
{
var zipDir = new Java.IO.File(Path.Combine(destPath, zipEntry.Name));
zipDir.Mkdirs();
continue;
}
// Note: This is deleting existing entries(!!!) for debug purposes only...
#if DEBUG
if (File.Exists(Path.Combine(destPath, zipEntry.Name)))
File.Delete(Path.Combine(destPath, zipEntry.Name));
#endif
using (var fileStream = new FileStream(Path.Combine(destPath, zipEntry.Name), FileMode.CreateNew))
{
while ((byteCount = zipStream.Read(buffer)) != -1)
{
fileStream.Write(buffer, 0, byteCount);
}
}
Log.Debug("UnZipAssets", zipEntry.Name);
zipEntry.Dispose();
}
}
}
Usage:
UnZipAssets("gameModLevels.zip", Path.Combine(Application.Context.CacheDir.AbsolutePath, "assets"));
Note: Even through the asset/zip steam is fast, depending upon number/size of the zip entries and the speed of the flash the entry is being written to, this should be done on a background thread as not to block UI thread and cause an ANR
Here is my code:
enter image description here
using System.IO;
namespace Randoms
{
public class Program
{
public static void Main()
{
byte[] buffer = new byte[10240]; // buffer size
string path = #"C:\Users\RAHUL\Desktop\file.txt";
using (FileStream source = new FileStream(path, FileMode.Open, FileAccess.Read))
{
long fileLength = source.Length;
using (FileStream dest = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
{
long totalBytes = 0;
int currentBlockSize = 0;
while ((currentBlockSize = source.Read(buffer, 0, buffer.Length)) > 0)
{
totalBytes += currentBlockSize;
double percentage = (double)totalBytes * 100.0 / fileLength;
dest.Write(buffer, 0, currentBlockSize);
}
}
}
}
}
}
Please check the image which shows the error which I am getting.I have tried to change the FileAccess multiple times but not getting any luck.
This post explains how to both read and write to a file using a single stream:
How to both read and write a file in C#
Consider you may have the file still open from a previous erroneous run. Use a tool like Sysinternals Process Monitor or Unlocker to verify it isn't open by another instance.
How to use Process Monitor:
http://www.manuelmeyer.net/2013/09/tooltips-unlocking-files-with-sysinternals-process-monitor/
Both source and dest are referencing the same file. In the first instance (source) you open it exclusively (ie not shared).
In the second instance (dest) you now want to create the file which you opened in the first instance, but allow it to be shared.
Since the source is already open an in use you cannot write over the top of it using dest.
I think what you may be really want is to have the path parameter for the dest to be different to path parameter for the source, since you are essentially trying to re-write the same data into the same file at the same location right now.
While troubleshooting a performance problem, I came across an issue in Windows 8 which relates to file names containing .dat (e.g. file.dat, file.data.txt).
I found that it takes over 6x as long to create them as any file with any other extension.
The same issue occurs in windows explorer where it takes significantly longer when copying folders containing .dat* files.
I have created some sample code to illustrate the issue.
internal class DatExtnIssue
{
internal static void Run()
{
CreateFiles("txt");
CreateFiles("dat");
CreateFiles("dat2");
CreateFiles("doc");
}
internal static void CreateFiles(string extension)
{
var folder = Path.Combine(#"c:\temp\FileTests", extension);
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
var sw = new Stopwatch();
sw.Start();
for (var n = 0; n < 500; n++)
{
var fileName = Path.Combine(folder, string.Format("File-{0:0000}.{1}", n, extension));
using (var fileStream = File.Create(fileName))
{
// Left empty to show the problem is due to creation alone
// Same issue occurs regardless of writing, closing or flushing
}
}
sw.Stop();
Console.WriteLine(".{0} = {1,6:0.000}secs", extension, sw.ElapsedMilliseconds/1000.0);
}
}
Results from creating 500 files with the following extensions
.txt = 0.847secs
.dat = 5.200secs
.dat2 = 5.493secs
.doc = 0.806secs
I got similar results using:
using (var fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
{ }
and:
File.WriteAllText(fileName, "a");
This caused a problem as I had a batch application which was taking far too long to run. I finally tracked it down to this.
Does anyone have any idea why this would be happening? Is this by design? I hope not, as it could cause problems for high-volume application creating .dat files.
It could be something on my PC but I have checked the windows registry and found no unusual extension settings.
If all else fails, try a kludge:
Write all files out as .txt and then rename *.txt to .dat. Maybe it will be faster :)
Basically I'm trying to compress a file "sample.doc" into the .gz file format. When this happens, it is told to remove the extension of the file so instead of appearing as
"sample.doc.gz" it appears as "sample.gz". However, when the file is extracted it has also lost its ".doc" file extension. eg. filename is just "sample". Any ideas?
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
namespace gzipexample
{
class Program
{
public static void Main()
{
CompressFile(#"C:\sample.doc");
}
//Compresses the file into a .gz file
public static void CompressFile(string path)
{
string compressedPath = path;
Console.WriteLine("Compressing: " + path);
int extIndex = compressedPath.LastIndexOf(".");
FileStream sourceFile = File.OpenRead(path);
FileStream destinationFile = File.Create(compressedPath.Replace(compressedPath.Substring(extIndex), "") + ".gz");
byte[] buffer = new byte[sourceFile.Length];
sourceFile.Read(buffer, 0, buffer.Length);
using (GZipStream output = new GZipStream(destinationFile,
CompressionMode.Compress))
{
Console.WriteLine("Compressing {0} to {1}.", sourceFile.Name,
destinationFile.Name, false);
output.Write(buffer, 0, buffer.Length);
}
// Close the files.
sourceFile.Close();
destinationFile.Close();
}
}
}
If I'm understanding your question correctly, there is no solution as stated. A gzip'ed file (at least, a file gzip'ed the way you're doing it) doesn't store its name, so if you compress a file named sample.doc and the output is named sample.gz, the ".doc" part is gone forever. That's why if you compress a file with the gzip command-line utility, it the compressed version sample.doc.gz.
In some constrained situations, you might be able to guess an appropriate extension by looking at the contents of the file, but that isn't very reliable. If you just need compression, and the file format isn't constrained, you could just build a .zip file instead, which does store filenames.