最近我一直在了解Java substring方法的一些缺陷,特别是与内存有关的问题,以及Java如何保留对原始字符串的引用。具有讽刺意味的是,我正在开发一个服务器应用程序,它每秒钟使用C# .Net的substring实现多次。这让我想到...
- C# (.Net)
string.Substring
是否存在内存问题? string.Substring
的性能如何?是否有更快的方法基于起始/结束位置来拆分字符串?
最近我一直在了解Java substring方法的一些缺陷,特别是与内存有关的问题,以及Java如何保留对原始字符串的引用。具有讽刺意味的是,我正在开发一个服务器应用程序,它每秒钟使用C# .Net的substring实现多次。这让我想到...
string.Substring
是否存在内存问题?string.Substring
的性能如何?是否有更快的方法基于起始/结束位置来拆分字符串?查看 .NET 的 String.Substring 实现,一个子字符串不与原始字符串共享内存。
private unsafe string InternalSubString(int startIndex, int length, bool fAlwaysCopy)
{
if (((startIndex == 0) && (length == this.Length)) && !fAlwaysCopy)
{
return this;
}
// Allocate new (separate) string
string str = FastAllocateString(length);
// Copy chars from old string to new string
fixed (char* chRef = &str.m_firstChar)
{
fixed (char* chRef2 = &this.m_firstChar)
{
wstrcpy(chRef, chRef2 + startIndex, length);
}
}
return str;
}
仅提供另一种观点。
内存不足(大多数情况下)并不意味着您已经使用完所有内存。它意味着您的内存已经被分段,下一次您想要分配一块内存时,系统无法找到连续的内存块来适应您的需求。
频繁的分配/释放会导致内存碎片化。由于您进行的操作类型,GC可能无法及时进行碎片整理。我知道.NET中的Server GC在进行内存整理方面非常出色,但是您可以通过编写糟糕的代码来饥饿(防止GC进行收集)系统。
尝试并测量经过的毫秒总是很好的。
Stopwatch watch = new Stopwatch();
watch.Start();
// run string.Substirng code
watch.Stop();
watch.ElapsedMilliseconds();
在开发过程中,您可以使用以下代码对内存进行分析:
bool forceFullCollection = false;
Int64 valTotalMemoryBefore = System.GC.GetTotalMemory(forceFullCollection);
//call String.Substring
Int64 valTotalMemoryAfter = System.GC.GetTotalMemory(forceFullCollection);
Int64 valDifferenceMemorySize = valTotalMemoryAfter - valTotalMemoryBefore;
祝你好运!;)
我记得Java中的字符串是存储实际字符以及起始位置和长度。
这意味着子字符串可以共享相同的字符(因为它们是不可变的),只需要维护单独的起始位置和长度。
所以我不确定您在Java字符串方面的内存问题是什么。
关于您编辑发布的那篇文章,我认为这似乎不是什么大问题。
除非您有制作大字符串并留下小子字符串的习惯,否则这对内存几乎没有影响。
即使您有一个10M的字符串并且制作了400个子字符串,您只使用了那个10M的基础字符数组 - 它并没有制作400个该子字符串的副本。唯一的内存影响是每个子字符串对象的起始/长度位。
作者似乎在抱怨他们将一个巨大的字符串读入内存,然后只想要其中的一部分,但整个字符串都被保留了下来 - 我的建议是他们可能需要重新考虑如何处理他们的数据 :-)
将这称为Java bug也是牵强附会。Bug是指不符合规格的东西。这是一个故意的设计决策,旨在提高性能,因为您不理解事物的工作原理而导致内存不足并不是bug,IMNSHO。而且这绝对不是内存泄漏。
在那篇文章的评论中,有一个可能是好的建议,即GC可以通过压缩未使用的字符串来更积极地回收它们。
这不是第一次GC时想做的事情,因为它相对昂贵。然而,在每个其他GC操作都无法回收足够空间的情况下,您可以这样做。
不幸的是,这几乎肯定意味着底层的char
数组需要记录所有引用它的字符串对象,以便它既能找出哪些位是未使用的又修改所有字符串对象的起始和长度字段。
这本身可能会引入不可接受的性能影响,而且如果您的内存如此短缺以至于这成为问题,您甚至可能无法为较小版本的字符串分配足够的空间。
我认为,如果内存不足,我可能更喜欢不维护这种char数组到字符串映射,以使这种GC水平成为可能,而是希望将该内存用于我的字符串。
既然有一个完全可接受的解决方法,而且好的程序员应该知道他们选择的语言的怪癖,我认为作者是正确的 - 这个问题不会被修复。
这不是因为Java开发人员太懒,而是因为这不是一个问题。
你可以自由地实现与C#相匹配的字符串方法(除了某些特定的场景外,它们不共享底层数据)。这将解决你的内存问题,但代价是性能下降,因为每次调用子字符串时都必须复制数据。就像IT(和生活)中的大多数事情一样,这是一个权衡。
返回一个新字符串,它是此字符串的子字符串
。它没有提供任何提示,表明返回的字符串会在内存中固定原始字符串。因此,文档应该清楚地说明实际行为,或者应该避免这种"优化"。由你决定 - 要么文档有缺陷,要么实现有问题。开发人员不应该必须检查此类方法的内部实现才能正确使用它们。 - LBushkinCLR(因此C#)对Substring
的实现不会保留源字符串的引用,因此它不具有Java字符串的“内存泄漏”问题。
http://msdn.microsoft.com/en-us/library/2839d5h5(VS.71).aspx
请注意,真正的问题在于内存分配,而不是 CPU,尽管过多的内存分配会占用 CPU...
.Substring
这样的核心框架操作应该被认为是高效的,直到你看到实际的减速并将其追踪到该操作。 - jball