Auto merge of #113106 - marcospb19:improve-path-with-extension-function, r=thomcc
std: remove an allocation in `Path::with_extension` `Path::with_extension` used to reallocate (and copy) paths twice per call, now it does it once, by checking the size of the previous and new extensions it's possible to call `PathBuf::with_capacity` and pass the exact capacity required. This also reduces the memory consumption of the path returned from `Path::with_extension` by using exact capacity instead of using amortized exponential growth.
This commit is contained in:
commit
1a44b45987
2 changed files with 66 additions and 8 deletions
|
@ -2608,9 +2608,27 @@ impl Path {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _with_extension(&self, extension: &OsStr) -> PathBuf {
|
fn _with_extension(&self, extension: &OsStr) -> PathBuf {
|
||||||
let mut buf = self.to_path_buf();
|
let self_len = self.as_os_str().len();
|
||||||
buf.set_extension(extension);
|
let self_bytes = self.as_os_str().as_os_str_bytes();
|
||||||
buf
|
|
||||||
|
let (new_capacity, slice_to_copy) = match self.extension() {
|
||||||
|
None => {
|
||||||
|
// Enough capacity for the extension and the dot
|
||||||
|
let capacity = self_len + extension.len() + 1;
|
||||||
|
let whole_path = self_bytes.iter();
|
||||||
|
(capacity, whole_path)
|
||||||
|
}
|
||||||
|
Some(previous_extension) => {
|
||||||
|
let capacity = self_len + extension.len() - previous_extension.len();
|
||||||
|
let path_till_dot = self_bytes[..self_len - previous_extension.len()].iter();
|
||||||
|
(capacity, path_till_dot)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut new_path = PathBuf::with_capacity(new_capacity);
|
||||||
|
new_path.as_mut_vec().extend(slice_to_copy);
|
||||||
|
new_path.set_extension(extension);
|
||||||
|
new_path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produces an iterator over the [`Component`]s of the path.
|
/// Produces an iterator over the [`Component`]s of the path.
|
||||||
|
|
|
@ -1394,6 +1394,46 @@ pub fn test_set_extension() {
|
||||||
tfe!("/", "foo", "/", false);
|
tfe!("/", "foo", "/", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_with_extension() {
|
||||||
|
macro_rules! twe (
|
||||||
|
($input:expr, $extension:expr, $expected:expr) => ({
|
||||||
|
let input = Path::new($input);
|
||||||
|
let output = input.with_extension($extension);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
output.to_str() == Some($expected),
|
||||||
|
"calling Path::new({:?}).with_extension({:?}): Expected {:?}, got {:?}",
|
||||||
|
$input, $extension, $expected, output,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
);
|
||||||
|
|
||||||
|
twe!("foo", "txt", "foo.txt");
|
||||||
|
twe!("foo.bar", "txt", "foo.txt");
|
||||||
|
twe!("foo.bar.baz", "txt", "foo.bar.txt");
|
||||||
|
twe!(".test", "txt", ".test.txt");
|
||||||
|
twe!("foo.txt", "", "foo");
|
||||||
|
twe!("foo", "", "foo");
|
||||||
|
twe!("", "foo", "");
|
||||||
|
twe!(".", "foo", ".");
|
||||||
|
twe!("foo/", "bar", "foo.bar");
|
||||||
|
twe!("foo/.", "bar", "foo.bar");
|
||||||
|
twe!("..", "foo", "..");
|
||||||
|
twe!("foo/..", "bar", "foo/..");
|
||||||
|
twe!("/", "foo", "/");
|
||||||
|
|
||||||
|
// New extension is smaller than file name
|
||||||
|
twe!("aaa_aaa_aaa", "bbb_bbb", "aaa_aaa_aaa.bbb_bbb");
|
||||||
|
// New extension is greater than file name
|
||||||
|
twe!("bbb_bbb", "aaa_aaa_aaa", "bbb_bbb.aaa_aaa_aaa");
|
||||||
|
|
||||||
|
// New extension is smaller than previous extension
|
||||||
|
twe!("ccc.aaa_aaa_aaa", "bbb_bbb", "ccc.bbb_bbb");
|
||||||
|
// New extension is greater than previous extension
|
||||||
|
twe!("ccc.bbb_bbb", "aaa_aaa_aaa", "ccc.aaa_aaa_aaa");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_eq_receivers() {
|
fn test_eq_receivers() {
|
||||||
use crate::borrow::Cow;
|
use crate::borrow::Cow;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue